I’ve been using this subflow for actionable notifications in Node-RED… or, something similar. I’m having trouble passing dynamic message content into the flow such that it makes it into the notification. Here’s the subflow:
[{"id":"35055683ae253203","type":"function","z":"67e867a7844ca017","name":"create service call","func":"msg._originalPayload = msg.payload;\n\nconst now = Date.now();\nconst getEnv = key => env.get(key) || ''; // Helper to fetch env values\nconst safeString = str => str.replace(/[^\\w\\s]/gi, '').replace(/\\s+/g, '_').toUpperCase(); // Sanitization\n\nlet flow_msg_variables = {\n tag: '',\n service: '',\n message: msg,\n date_created: now\n}\n\n// Extract overrides or fallback to env values\nconst override = msg.notificationOverride ?? {};\nconst xTitle = override.title ?? getEnv('title');\nconst xSubtitle = override.subtitle ?? getEnv('subtitle');\nconst xMessage = override.message ?? getEnv('message');\nconst xUrl = override.url ?? getEnv('notificationUrl');\nconst xServices = override.services ?? getEnv('service');\nconst xCameraEntity = override.cameraEntity ?? getEnv('cameraEntity');\nconst xInterruptionLevel = override.interruptionLevel ?? getEnv('interruptionLevel');\nconst xCustomSound = override.customSound ?? getEnv('customSound');\nconst xGroup = override.group ?? getEnv('group');\n// Map Information\nconst xLatitudeFirst = override.latitudeFirst ?? getEnv('latitudeFirst');\nconst xLongitudeFirst = override.longitudeFirst ?? getEnv('longitudeFirst');\nconst xLatitudeSecond = override.latitudeSecond ?? getEnv('latitudeSecond');\nconst xLongitudeSecond = override.longitudeSecond ?? getEnv('longitudeSecond');\n// Media Information\nconst xContentUrl = override.contentUrl ?? getEnv('contentUrl');\nconst xImagePath = override.imagePath ?? getEnv('imagePath');\nconst xVideoPath = override.videoPath ?? getEnv('videoPath');\nconst xAudioPath = override.audioPath ?? getEnv('audioPath');\nconst xLazyLoading = override.lazyLoading ?? getEnv('lazyLoading');\nconst xHideThumbnail = override.hideThumbnail ?? getEnv('hideThumbnail');\n\n\n// Tag setup\nflow_msg_variables.tag = safeString(override.tag ?? getEnv('tag')) || `${safeString(getEnv('title'))}_${flow.get('random')}`;\nflow_msg_variables.service = xServices;\n \n// Process messages array\nlet all_flow_messages = flow.get('flow_messages') || [];\nlet new_flow_messages = all_flow_messages.filter(msg => \n !(msg.tag === flow_msg_variables.tag && msg.service === flow_msg_variables.service) &&\n (now - msg.date_created < 86400000) // keep messages less than 24h old\n);\n\nnew_flow_messages.push(flow_msg_variables);\nflow.set('flow_messages',new_flow_messages);\n\n// Services check\nif (!xServices.trim()) {\n node.status({ text: 'no services defined', shape: 'ring', fill: 'red' });\n return;\n} else if (xServices.trim() === \"NOONE\") {\n node.status({ text: 'No one to send to', shape: 'ring', fill: 'yellow' });\n return;\n}\n\n// Actions setup\nconst actions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(i => {\n const title = override[`action${i}Title`] ?? getEnv(`action${i}Title`);\n if (!title) return null;\n\n const actionObj = {\n action: safeString(title),\n title\n };\n\n // Conditionally add properties if they are not empty\n const properties = [\n 'activationMode', 'uri', 'textInputButtonTitle',\n 'textInputPlaceholder', 'authenticationRequired',\n 'destructive', 'behavior', 'icon'\n ];\n\n properties.forEach(prop => {\n const value = override[`action${i}${prop.charAt(0).toUpperCase() + prop.slice(1)}`] ?? getEnv(`action${i}${prop.charAt(0).toUpperCase() + prop.slice(1)}`);\n if (value) {\n actionObj[prop] = value;\n }\n });\n\n return actionObj;\n}).filter(Boolean);\n\n\n// Create msg object\nmsg.payload = {\n data: {\n title: xTitle,\n message: xMessage,\n data: {\n tag: flow_msg_variables.tag,\n ...(actions.length > 0 && {actions}),\n ...(actions.length > 0 && {\n action_data: { tag: flow_msg_variables.tag }\n }),\n ...(xUrl && { url: xUrl }),\n ...(xSubtitle && { subtitle: xSubtitle }),\n ...(xCameraEntity && { entity_id: xCameraEntity }),\n ...(xGroup && { group: xGroup }),\n push: {\n sound: {\n name: xCustomSound || getEnv('customSoundPreInstalled') || 'default',\n ...(env.get('isCriticalNotification') && { critical: 1, volume: 1.0 })\n },\n ...(xInterruptionLevel && { \"interruption-level\": xInterruptionLevel })\n }\n }\n }\n};\n\n// Map Information\nif (xLatitudeFirst && xLongitudeFirst) {\n msg.payload.data.data.action_data = {\n ...(actions.length > 0 && { tag: flow_msg_variables.tag }),\n latitude: xLatitudeFirst,\n longitude: xLongitudeFirst,\n ...(xLatitudeSecond && xLongitudeSecond && {\n second_latitude: xLatitudeSecond,\n second_longitude: xLongitudeSecond,\n shows_line_between_points: override.showLineBetweenPoints ?? getEnv('showLineBetweenPoints'),\n shows_compass: override.showCompass ?? getEnv('showCompass'),\n shows_points_of_interest: override.showPointsOfInterest ?? getEnv('showPointsOfInterest'),\n shows_scale: override.showScale ?? getEnv('showScale'),\n shows_traffic: override.showTraffic ?? getEnv('showTraffic'),\n shows_user_location: override.showUserLocation ?? getEnv('showUserLocation')\n })\n };\n}\n\n// Media Information\nif (xContentUrl || xImagePath || xVideoPath || xAudioPath) {\n msg.payload.data.data = {\n ...msg.payload.data.data,\n contentUrl: xContentUrl || undefined,\n image: xImagePath || undefined,\n video: xVideoPath || undefined,\n audio: xAudioPath || undefined,\n lazy: xLazyLoading || undefined,\n attachment: xHideThumbnail ? { 'hide-thumbnail': xHideThumbnail } : undefined\n };\n}\n\n// Send notifications\nxServices.trim().split(/,\\s*/).forEach(service => {\n if (!service) return;\n msg.payload.action = `notify.${service}`;\n node.send(msg);\n});\n\nnode.done();","outputs":1,"timeout":"","noerr":0,"initialize":"flow.set('random',Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 5).toUpperCase());","finalize":"","libs":[],"x":430,"y":80,"wires":[["7636c6f3fe2966be","25cd950f148bdb21"]]},{"id":"1d36a2652523a737","type":"switch","z":"67e867a7844ca017","name":"which action?","property":"responseIndex","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"},{"t":"eq","v":"4","vt":"num"}],"checkall":"true","repair":false,"outputs":4,"x":1280,"y":480,"wires":[[],[],[],[]]},{"id":"144460bb41facc58","type":"status","z":"67e867a7844ca017","name":"","scope":["35055683ae253203","e21fb9e4eee3a768","b57a6c93227eba63","c3e63297b4ba36b2"],"x":100,"y":320,"wires":[[]]},{"id":"e21fb9e4eee3a768","type":"function","z":"67e867a7844ca017","name":"build message","func":"const latestMessage = flow.get('latestMessage');\nconst event = msg.payload.event;\n\nconst getEnv = key => env.get(key) || ''; // Helper to fetch env values\nconst safeString = str => str.replace(/[^\\w\\s]/gi, '').replace(/\\s+/g, '_').toUpperCase(); // Sanitization\n\n// Extract overrides or fallback to env values\nconst override = latestMessage.notificationOverride ?? {};\n\nlatestMessage.payload = latestMessage._originalPayload;\nlatestMessage.eventData = msg.payload;\ndelete latestMessage._originalPayload;\n\nif (env.get('userInfo')) {\n latestMessage.userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\n}\n\nconst actionNames = [1, 2, 3, 4].map(i => {\n const title = override[`action${i}Title`] ?? getEnv(`action${i}Title`);\n return safeString(title);\n});\n\nlatestMessage.actionNames = actionNames;\n\nconst index = actionNames.findIndex(name => name === event.actionName) + 1;\n\nif (index) {\n flow.set(\"responseIndex\", index);\n node.status({\n text: `${actionNames[index - 1]} at: ${getPrettyDate()}`,\n shape: 'dot',\n fill: 'green'\n });\n}\n\nreturn latestMessage;\n\nfunction getPrettyDate() {\n return new Date().toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n hour12: false,\n hour: 'numeric',\n minute: 'numeric',\n });\n}\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":480,"wires":[["1d36a2652523a737","4c98d1addbc534b6"]]},{"id":"808cf2c07196978a","type":"ha-api","z":"67e867a7844ca017","name":"get user info","server":"296c0678.b5f9ca","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\": \"config/auth/list\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":950,"y":420,"wires":[["e21fb9e4eee3a768"]]},{"id":"449ff3ce4d1a7ca8","type":"switch","z":"67e867a7844ca017","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":800,"y":480,"wires":[["808cf2c07196978a"],["e21fb9e4eee3a768"]]},{"id":"35a428ec0772074d","type":"server-events","z":"67e867a7844ca017","name":"ios.notification_action_fired","server":"296c0678.b5f9ca","version":3,"exposeAsEntityConfig":"","eventType":"ios.notification_action_fired","eventData":"","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":164,"y":256,"wires":[["a0b4e6bc9c85191e"]]},{"id":"b57a6c93227eba63","type":"api-call-service","z":"67e867a7844ca017","name":"Send Notifications","server":"296c0678.b5f9ca","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"callServiceData","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":false,"domain":"notify","service":"","x":830,"y":80,"wires":[[]]},{"id":"c3e63297b4ba36b2","type":"function","z":"67e867a7844ca017","name":"create CLEAR service call","func":"msg._originalPayload = msg.payload;\n\nlet tag_to_clear = \"\";\nlet services = \"\";\nlet clearAll = false;\n\nif (msg.notificationOverride) {\n clearAll = !!msg.notificationOverride.clear;\n services = msg.notificationOverride.services || \"\";\n tag_to_clear = msg.notificationOverride.tag\n ? msg.notificationOverride.tag.replace(/[^\\w\\s]/gi, '').replace(/\\s+/g, '_').toUpperCase()\n : \"\";\n}\n\nif (!tag_to_clear) { // No specific tag sent, clear the last message.\n let all_flow_messages = flow.get('flow_messages');\n if (all_flow_messages && all_flow_messages.length > 0) {\n let last_message = all_flow_messages.pop();\n tag_to_clear = last_message.tag;\n services = last_message.service;\n flow.set('flow_messages', all_flow_messages);\n }\n}\n\nif (!services) {\n node.status({\n text: 'no services defined',\n shape: 'ring',\n fill: 'red'\n });\n return;\n}\n\nif (!tag_to_clear) {\n node.status({\n text: 'no messages to delete',\n shape: 'ring',\n fill: 'red'\n });\n return;\n}\n\n// Create iOS msg object\nmsg.payload = {\n data: {\n message: \"clear_notification\",\n data: {\n tag: tag_to_clear\n }\n }\n};\n\nfunction getPrettyDate() {\n return new Date().toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n hour12: false,\n hour: 'numeric',\n minute: 'numeric',\n });\n}\n\nif (clearAll) {\n delete msg.notificationOverride;\n}\n\nlet xCountCleared = 0;\nservices.trim().split(/,\\s*/).forEach(service => {\n if (!service) return;\n\n if (clearAll || !service.includes(msg._originalPayload.event.sourceDeviceID)) {\n msg.payload.action = `notify.${service}`;\n node.send(msg);\n xCountCleared++;\n }\n});\n\nnode.status({\n text: `${xCountCleared > 0 ? `${xCountCleared} messages cleared` : 'No messages cleared'} at: ${getPrettyDate()}`,\n shape: 'dot',\n fill: xCountCleared > 0 ? 'blue' : 'red'\n});\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":300,"wires":[["6e891f0aa83ce24b","467014ff139a7f81"]]},{"id":"a6ef52fc22ff6572","type":"switch","z":"67e867a7844ca017","name":"Clear Notification on Action?","property":"isClearNotificationsOnAction","propertyType":"env","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":460,"y":440,"wires":[["c3e63297b4ba36b2","4ab47be24d35236a"],["449ff3ce4d1a7ca8"]]},{"id":"4ff24e3880801123","type":"api-call-service","z":"67e867a7844ca017","name":"Clear Notifications","server":"296c0678.b5f9ca","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"json","mergeContext":"callServiceData","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":false,"domain":"notify","service":"","x":1210,"y":240,"wires":[[]]},{"id":"4ab47be24d35236a","type":"delay","z":"67e867a7844ca017","name":"","pauseType":"delay","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":720,"y":420,"wires":[["449ff3ce4d1a7ca8"]]},{"id":"e91478eee0eb39da","type":"switch","z":"67e867a7844ca017","name":"clear?","property":"notificationOverride.clear","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"null"},{"t":"false"},{"t":"true"}],"checkall":"false","repair":false,"outputs":4,"x":210,"y":80,"wires":[["35055683ae253203"],["35055683ae253203"],["35055683ae253203"],["c3e63297b4ba36b2"]]},{"id":"6e891f0aa83ce24b","type":"delay","z":"67e867a7844ca017","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":990,"y":240,"wires":[["4ff24e3880801123"]]},{"id":"7636c6f3fe2966be","type":"delay","z":"67e867a7844ca017","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":630,"y":80,"wires":[["b57a6c93227eba63"]]},{"id":"a0b4e6bc9c85191e","type":"function","z":"67e867a7844ca017","name":"belongs here?","func":"let msg_tag = msg.payload.event.action_data.tag.trim();\nlet all_flow_messages = flow.get('flow_messages');\n\nif (all_flow_messages) {\n let matchedMessage = all_flow_messages.find(\n message => message.tag.trim() === msg_tag\n );\n if (matchedMessage) {\n flow.set('latestMessage', matchedMessage.message);\n return [msg, null];\n }\n}\n\nreturn [null, msg];","outputs":2,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":260,"wires":[["a6ef52fc22ff6572"],[]]},{"id":"f443cc500f68294f","type":"debug","z":"67e867a7844ca017","name":"iOS Notify Debug Input Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":200,"y":200,"wires":[]},{"id":"9e2c1eb9fcd4891d","type":"debug","z":"67e867a7844ca017","name":"iOS Notify Debug Notify Service Call","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":870,"y":160,"wires":[]},{"id":"128f1b2bd0699e31","type":"debug","z":"67e867a7844ca017","name":"iOS Notify Debug Clear Service Call","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1270,"y":300,"wires":[]},{"id":"4ac9e496e3ab0de9","type":"debug","z":"67e867a7844ca017","name":"iOS Notify Debug Output Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1120,"y":600,"wires":[]},{"id":"28115a32dc285e97","type":"switch","z":"67e867a7844ca017","name":"Debug?","property":"debugMode","propertyType":"env","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":140,"y":140,"wires":[["f443cc500f68294f"]]},{"id":"4c98d1addbc534b6","type":"switch","z":"67e867a7844ca017","name":"Debug?","property":"debugMode","propertyType":"env","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":1040,"y":540,"wires":[["4ac9e496e3ab0de9"]]},{"id":"467014ff139a7f81","type":"switch","z":"67e867a7844ca017","name":"Debug?","property":"debugMode","propertyType":"env","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":980,"y":300,"wires":[["128f1b2bd0699e31"]]},{"id":"25cd950f148bdb21","type":"switch","z":"67e867a7844ca017","name":"Debug?","property":"debugMode","propertyType":"env","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":620,"y":160,"wires":[["9e2c1eb9fcd4891d"]]},{"id":"296c0678.b5f9ca","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]
In the message field of the actionable notification, I’ll put {{{payload}}}. That works for simple notify actions in action nodes, but not in this subflow. Any ideas that might help?
Thanks.