The following page describes an E-Mail distribution list that will send an E-Mail message to a list of recipients using Node-Red to create the necessary logic that couples all the components together and exposing only files as the user interface based on Samba. While a classical mailing list typically also allows users to receive incoming messages, the distribution list is more of a single-user to many users model where one user wishes to distribute documents.
The system can be configured by the end user by just adding or editing files that are shared via a network share.
The network share exposes two directories:
contacts
, that will hold text files representing distribution lists of contacts, with one E-Mail address per line,outbox
, where a file will be placed that will be sent to all the contacts within a distribution list stored within the contacts
folder
Node-Red will use inotify to monitor the folders corresponding to the network share and when a file is stored inside the outbox
folder, depending on the contents of the file, Node-Red will pull a distribution list from the contacts
folder and send an E-Mail to every address within the distribution list.
A file share definition is established following the same network share definitions that have been used for the Samba standalone template:
[iot-distribution-list] comment = E-Mail Distribution List path = /opt/iot-distribution-list valid users = root system # Create file under this user - useful for seamlessly copying files. force user = node-red force group = node-red read only = No create mask = 0664 force create mode = 0664 force directory mode = 0755
such that the entire distribution system will be placed on the server under the directory /opt/iot-distribution-list
.
The Node-Red flow can be described using the upper part of the flow that is responsible for reading in files from the outbox
folder (corresponding to the /opt/iot-distribution-list/outbox
path on the Samba server) while the lower part of the flow is responsible for additionally reading in the list of contacts from the contacts
folder (corresponding to the /opt/iot-distribution-list/contacts
path on the Samba server) and then, for each contact in the distribution list, an E-Mail is sent to each contact.
[{"id":"9ec96f01e8763f24","type":"tab","label":"Press","disabled":false,"info":"","env":[]},{"id":"ebae327d73eed19e","type":"file in","z":"9ec96f01e8763f24","name":"Contacts","filename":"payload","filenameType":"msg","format":"lines","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":360,"y":520,"wires":[["f897a4309cfd7694"]]},{"id":"dc3ac04d4d1ca883","type":"watch","z":"9ec96f01e8763f24","name":"","files":"/opt/iot-distribution-list/outbox","recursive":"","x":240,"y":300,"wires":[["472c0fa08c0b20a4"]]},{"id":"0f01ec576706517d","type":"file in","z":"9ec96f01e8763f24","name":"","filename":"filename","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":780,"y":300,"wires":[["6d9b1a12c74cfd95","ca7f3923688f10a8"]]},{"id":"8a0cc80ff22fd840","type":"catch","z":"9ec96f01e8763f24","name":"","scope":["0f01ec576706517d"],"uncaught":false,"x":770,"y":360,"wires":[["cbe22be797976bb4"]]},{"id":"cbe22be797976bb4","type":"switch","z":"9ec96f01e8763f24","name":"","property":"error.message","propertyType":"msg","rules":[{"t":"cont","v":"ENOENT","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":930,"y":360,"wires":[[],["2ca5aea41369c892"]]},{"id":"2ca5aea41369c892","type":"debug","z":"9ec96f01e8763f24","name":"debug 32","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1020,"y":440,"wires":[]},{"id":"472c0fa08c0b20a4","type":"switch","z":"9ec96f01e8763f24","name":"","property":"event","propertyType":"msg","rules":[{"t":"eq","v":"update","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":450,"y":300,"wires":[["9a6eb2bc0a72abe1"],[]]},{"id":"f224ee5b89d6ad64","type":"debug","z":"9ec96f01e8763f24","name":"debug 33","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1600,"y":240,"wires":[]},{"id":"f897a4309cfd7694","type":"split","z":"9ec96f01e8763f24","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":510,"y":520,"wires":[["4fa688455c642db4"]]},{"id":"0def05d6d0466dc7","type":"debug","z":"9ec96f01e8763f24","name":"debug 35","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":580,"wires":[]},{"id":"2dae17bca4aa47b7","type":"delay","z":"9ec96f01e8763f24","name":"","pauseType":"rate","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":810,"y":520,"wires":[["08984dfdd7beaccf"]]},{"id":"08984dfdd7beaccf","type":"function","z":"9ec96f01e8763f24","name":"function 33","func":"let contact = msg.payload.trim()\nmsg = {\n 'to': contact,\n 'topic': flow.get('subject'),\n 'payload': flow.get('body')\n}\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":520,"wires":[["0def05d6d0466dc7","d1e68716335ae622"]]},{"id":"d1e68716335ae622","type":"e-mail","z":"9ec96f01e8763f24","server":"smtp.gmail.com","port":"465","authtype":"BASIC","saslformat":true,"token":"oauth2Response.access_token","secure":true,"tls":true,"name":"","dname":"","x":1150,"y":520,"wires":[]},{"id":"6d9b1a12c74cfd95","type":"split","z":"9ec96f01e8763f24","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":true,"addname":"","x":930,"y":300,"wires":[["0e4a30a3732d75a2"]]},{"id":"0e4a30a3732d75a2","type":"switch","z":"9ec96f01e8763f24","name":"","property":"payload","propertyType":"msg","rules":[{"t":"regex","v":"^to:","vt":"str","case":true},{"t":"regex","v":"^subject:","vt":"str","case":true},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":1070,"y":300,"wires":[["7d202953ba44b4be"],["c8450da3d3434e84"],["1c014d5126a7a578","f3a0de1b6fa3a89d"]]},{"id":"5817ef78fb3e0c69","type":"debug","z":"9ec96f01e8763f24","name":"debug 36","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1640,"y":300,"wires":[]},{"id":"7d202953ba44b4be","type":"function","z":"9ec96f01e8763f24","name":"function 34","func":"msg.payload = msg.payload.split(':').at(-1).trim()\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1250,"y":240,"wires":[["9adf51ed01d9e004"]]},{"id":"c8450da3d3434e84","type":"function","z":"9ec96f01e8763f24","name":"function 35","func":"msg.payload = msg.payload.split(':').at(-1).trim()\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1250,"y":300,"wires":[["d81a3445c3a45f60"]]},{"id":"d81a3445c3a45f60","type":"change","z":"9ec96f01e8763f24","name":"","rules":[{"t":"set","p":"subject","pt":"flow","to":"payload","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":1460,"y":300,"wires":[["5817ef78fb3e0c69"]]},{"id":"9adf51ed01d9e004","type":"change","z":"9ec96f01e8763f24","name":"","rules":[{"t":"set","p":"to","pt":"flow","to":"payload","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":1440,"y":240,"wires":[["f224ee5b89d6ad64"]]},{"id":"d694ebc2f0158ddf","type":"function","z":"9ec96f01e8763f24","name":"function 36","func":"let contact = flow.get('to')\nmsg = {}\nmsg.payload = `/opt/iot-distribution-list/contacts/${contact}.txt`\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":190,"y":520,"wires":[["ebae327d73eed19e"]]},{"id":"e1bb9e4eb4370c7a","type":"link out","z":"9ec96f01e8763f24","name":"link out 11","mode":"link","links":["81fe614f1004bc0a"],"x":1395,"y":360,"wires":[]},{"id":"81fe614f1004bc0a","type":"link in","z":"9ec96f01e8763f24","name":"link in 15","links":["e1bb9e4eb4370c7a"],"x":75,"y":520,"wires":[["d694ebc2f0158ddf"]]},{"id":"1c014d5126a7a578","type":"trigger","z":"9ec96f01e8763f24","name":"","op1":"","op2":"","op1type":"nul","op2type":"date","duration":"1","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1240,"y":360,"wires":[["e1bb9e4eb4370c7a","1ed6ece8ce217fec"]]},{"id":"1ed6ece8ce217fec","type":"debug","z":"9ec96f01e8763f24","name":"debug 38","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1460,"y":420,"wires":[]},{"id":"f3a0de1b6fa3a89d","type":"function","z":"9ec96f01e8763f24","name":"function 39","func":"let body = flow.get(\"body\")\nbody = body + [msg.payload + os.EOL ]\nflow.set(\"body\", body)\nmsg.payload = body\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"os","module":"os"}],"x":1250,"y":440,"wires":[[]]},{"id":"9a6eb2bc0a72abe1","type":"change","z":"9ec96f01e8763f24","name":"","rules":[{"t":"set","p":"body","pt":"flow","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":300,"wires":[["0f01ec576706517d"]]},{"id":"4fa688455c642db4","type":"switch","z":"9ec96f01e8763f24","name":"","property":"payload","propertyType":"msg","rules":[{"t":"nempty"},{"t":"empty"},{"t":"else"}],"checkall":"true","repair":false,"outputs":3,"x":650,"y":520,"wires":[["2dae17bca4aa47b7"],[],[]]},{"id":"4fab195379c69496","type":"fs-ops-delete","z":"9ec96f01e8763f24","name":"","path":"","pathType":"str","filename":"filename","filenameType":"msg","x":1110,"y":160,"wires":[[]]},{"id":"ca7f3923688f10a8","type":"delay","z":"9ec96f01e8763f24","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":940,"y":160,"wires":[["4fab195379c69496"]]}]
The flow can be extended in order to be able to attach files whilst distributing E-Mails. One elegant way to do this is to just scan the payload directory for files and consider that any files with the text/plain
Mime encoding consists in the actual payload to be sent and any other files are then the attachments. Compared to actually accounting which files are referenced within an E-Mail and building up some sort of parsing and handling of files within the payload, this is a much better solution because it is known anyway that the folder will have to be empty upon completion such that all other files except the payload can only be E-Mail attachments.
The flow is somewhat more complicated but not too much, and some enhancements are made where the mailer is given a notion of state, with two nodes Count Messages
and Zero Check
tracking the E-Mails as they are sent such that it is now easy to determine when the sending starts and when the sending ends. After the last E-Mail, the Remove Files
node will delete all the files within the outbox
folder and reset all variables.
Another important change was the switch to "chokidar", a node-module that provides an inotify
compatibility shim, instead of relying on the built-in node-red inotify
node because inotify
does not work on mounted volumes via NFS such that the provided node-red inotify
capabilities will fail within very popular environments such as docker. Obviously, as a compatibility shim, "chokidar" does not provide the elegance of filesystem-assisted file change notifications yet implements traditional polling under the covers in order to emulate inotify
. Without this, the flow would just immediately fail under docker.
[{"id":"db243db33b30cf44","type":"tab","label":"Press","disabled":false,"info":"","env":[]},{"id":"0beb10795c93bd48","type":"file in","z":"db243db33b30cf44","name":"","filename":"payload","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":660,"y":220,"wires":[["a6139d34b7483b56"]]},{"id":"209dc41a5fe1614f","type":"catch","z":"db243db33b30cf44","name":"","scope":["0beb10795c93bd48"],"uncaught":false,"x":270,"y":540,"wires":[["192a136891cb583b"]]},{"id":"192a136891cb583b","type":"switch","z":"db243db33b30cf44","name":"","property":"error.message","propertyType":"msg","rules":[{"t":"cont","v":"ENOENT","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":430,"y":540,"wires":[[],["2ff8a7d7c60e94f7"]]},{"id":"2ff8a7d7c60e94f7","type":"debug","z":"db243db33b30cf44","name":"debug 32","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":540,"wires":[]},{"id":"4b1040627eef79f1","type":"debug","z":"db243db33b30cf44","name":"debug 35","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1380,"y":540,"wires":[]},{"id":"df086cb7f2953b3e","type":"delay","z":"db243db33b30cf44","name":"","pauseType":"rate","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":980,"y":460,"wires":[["4ad5439c1271e6bc"]]},{"id":"4ad5439c1271e6bc","type":"function","z":"db243db33b30cf44","name":"Compose Message","func":"let payload = {\n 'to': msg.payload.trim(),\n 'topic': flow.get('subject'),\n 'payload': flow.get('body')\n}\n\nvar attachments = flow.get('attachments')\nif (attachments != null) {\n payload.attachments = attachments\n}\n\nreturn payload","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1190,"y":460,"wires":[["4b1040627eef79f1","eb7413646cfaef2b","ea11fd8ecd0c1bf3"]]},{"id":"eb7413646cfaef2b","type":"e-mail","z":"db243db33b30cf44","server":"smtp.gmail.com","port":"465","authtype":"BASIC","saslformat":true,"token":"oauth2Response.access_token","secure":true,"tls":true,"name":"","dname":"","x":1370,"y":460,"wires":[]},{"id":"d27bf2b5c5237fcd","type":"function","z":"db243db33b30cf44","name":"Set Contacts File","func":"msg = {}\nmsg.payload = `/projects/iot-distribution-list/contacts/${flow.get('to').trim()}.txt`\nreturn msg\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":210,"y":460,"wires":[["65927332baa08f0d"]]},{"id":"4fab1544c8542869","type":"change","z":"db243db33b30cf44","name":"","rules":[{"t":"set","p":"body","pt":"flow","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":220,"wires":[["0beb10795c93bd48"]]},{"id":"0df5f65890f626af","type":"inject","z":"db243db33b30cf44","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"/projects/iot-distribution-list/outbox/execute.txt","payloadType":"str","x":170,"y":300,"wires":[["4fab1544c8542869"]]},{"id":"f3d8802be8142018","type":"function","z":"db243db33b30cf44","name":"Watch","func":"","outputs":3,"timeout":0,"noerr":0,"initialize":"let watcher = chokidar\n .watch('/projects/iot-distribution-list/outbox/', \n {\n persistent: true,\n usePolling: true,\n useFsEvents: false,\n awaitWriteFinish: {\n stabilityThreshold: 250,\n pollInterval: 100\n },\n depth: 1\n }\n )\n\nwatcher\n .on('add', (path) => node.send([{ payload: path }, null, null]))\n .on('change', (path) => node.send([null, { payload: path }, null]))\n .on('unlink', (path) => node.send([null, null, { payload: path }]))\n","finalize":"","libs":[{"var":"chokidar","module":"chokidar"}],"x":170,"y":220,"wires":[["90cd598c3a083b64","dfa55c0bc2bc1ea7","3648a0c4809e91f5"],[],[]]},{"id":"90cd598c3a083b64","type":"debug","z":"db243db33b30cf44","name":"debug 75","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":420,"y":80,"wires":[]},{"id":"7e7661eba987dbb4","type":"link in","z":"db243db33b30cf44","name":"link in 28","links":["9fc5cfeac136f7b3"],"x":65,"y":460,"wires":[["d27bf2b5c5237fcd"]]},{"id":"9fc5cfeac136f7b3","type":"link out","z":"db243db33b30cf44","name":"link out 23","mode":"link","links":["7e7661eba987dbb4"],"x":1215,"y":220,"wires":[]},{"id":"a6139d34b7483b56","type":"function","z":"db243db33b30cf44","name":"Generate E-Mail Parameters","func":"let messageTo, messageSubject = null\nlet messageBody = []\nlet gotTo, gotSubject = false\nlet lines = msg.payload.split(/\\r?\\n/)\nlet line = null\ndo {\n let line = lines.pop()\n let match = /^To:(.+?)$/i.exec(line)\n if(match !== null && !gotTo) {\n messageTo = match[1].trim()\n gotTo = true\n continue\n }\n match = /^Subject:(.+?)$/i.exec(line)\n if(match !== null && !gotSubject) {\n messageSubject = match[1].trim()\n gotSubject = true\n continue\n }\n\n messageBody.push(line)\n} while(lines.length !== 0)\nline = lines.pop()\nmessageBody.push(line)\nmessageBody.reverse()\n\nflow.set('to', messageTo)\nflow.set('subject', messageSubject)\nflow.set('body', messageBody.join('\\n'))\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":220,"wires":[["807bf6cbae679852","56ce9c137f6415ed"]]},{"id":"65927332baa08f0d","type":"file in","z":"db243db33b30cf44","name":"","filename":"payload","filenameType":"msg","format":"lines","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":400,"y":460,"wires":[["d367ebda8584172e"]]},{"id":"d367ebda8584172e","type":"function","z":"db243db33b30cf44","name":"Generate Addresses","func":"function isEmail(address) {\n return address != null && \n address.length != 0 && \n address.includes('@')\n}\n\nlet lines = msg.payload.split(/\\r?\\n/)\nlet line = null\ndo {\n let line = lines.pop()\n if(!isEmail(line)) {\n continue\n }\n node.send({payload: line.trim()})\n} while(lines.length != 0)\nline = lines.pop()\nif(!isEmail(line)) {\n return\n}\nnode.send({payload: line.trim()})\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":460,"wires":[["16bf6ea6b5523485","05bef57aaaa31917","ef4649fd325297af"]]},{"id":"16bf6ea6b5523485","type":"debug","z":"db243db33b30cf44","name":"debug 76","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":800,"y":400,"wires":[]},{"id":"dfa55c0bc2bc1ea7","type":"function","z":"db243db33b30cf44","name":"Filter Text","func":"let mimeType = mime.lookup(msg.payload)\nif (mimeType != 'text/plain') {\n node.send([null, msg])\n return\n}\nnode.send([msg, null])","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"mime","module":"mime-types"}],"x":320,"y":220,"wires":[["4fab1544c8542869"],["a720b400c0cdd296"]]},{"id":"a720b400c0cdd296","type":"function","z":"db243db33b30cf44","name":"Update Attachments","func":"let filename = path.basename(msg.payload)\nlet attachments = flow.get('attachments')\n\nif(attachments == null) {\n attachments = []\n}\n\nattachments.push({\n path: msg.payload,\n filename: filename\n\n})\n\nflow.set('attachments', attachments)\n\nmsg.payload = attachments\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"path","module":"path"}],"x":500,"y":300,"wires":[["34d7fad873e737a5"]]},{"id":"34d7fad873e737a5","type":"debug","z":"db243db33b30cf44","name":"debug 82","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":700,"y":300,"wires":[]},{"id":"05bef57aaaa31917","type":"function","z":"db243db33b30cf44","name":"Count Messages","func":"let deliveries = flow.get('deliveries')\nif(deliveries == null) {\n deliveries = 0\n}\n++deliveries\nflow.set('deliveries', deliveries)\n\nmsg.payload = deliveries\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":540,"wires":[["eb7025ba9bb0502f"]]},{"id":"d52f3616b76210d6","type":"function","z":"db243db33b30cf44","name":"Zero Check","func":"let deliveries = flow.get('deliveries')\n--deliveries\nflow.set('deliveries', deliveries)\n\nif(deliveries == 0) {\n node.send([ null, { payload: 0 }])\n return\n}\n\nnode.send([{ payload: deliveries}, null])\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1170,"y":620,"wires":[[],["c7e4b1b0d9e6d05e","92df7fd0a6508b30"]]},{"id":"1bb912d50d762d25","type":"change","z":"db243db33b30cf44","name":"","rules":[{"t":"set","p":"to","pt":"flow","to":"","tot":"str"},{"t":"set","p":"subject","pt":"flow","to":"","tot":"str"},{"t":"set","p":"message","pt":"flow","to":"","tot":"str"},{"t":"set","p":"attachments","pt":"flow","to":"[]","tot":"jsonata"},{"t":"set","p":"deliveries","pt":"flow","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":1180,"y":780,"wires":[[]]},{"id":"f7781cc9be120916","type":"inject","z":"db243db33b30cf44","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":950,"y":820,"wires":[["1bb912d50d762d25"]]},{"id":"807bf6cbae679852","type":"debug","z":"db243db33b30cf44","name":"debug 83","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1140,"y":140,"wires":[]},{"id":"56ce9c137f6415ed","type":"delay","z":"db243db33b30cf44","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1100,"y":220,"wires":[["9fc5cfeac136f7b3"]]},{"id":"3648a0c4809e91f5","type":"function","z":"db243db33b30cf44","name":"Store Files","func":"let files = flow.get('files')\nif(files == null) {\n files = []\n}\nfiles.push(msg.payload)\nflow.set('files', files)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":380,"wires":[[]]},{"id":"c7e4b1b0d9e6d05e","type":"function","z":"db243db33b30cf44","name":"Remove Files","func":"let files = flow.get('files')\ndo {\n let file = files.pop()\n fs.unlink(file, (error) => {})\n} while(files.length != 0)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"fs","module":"fs"}],"x":1180,"y":700,"wires":[["1bb912d50d762d25"]]},{"id":"92df7fd0a6508b30","type":"debug","z":"db243db33b30cf44","name":"debug 84","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1380,"y":660,"wires":[]},{"id":"ef4649fd325297af","type":"delay","z":"db243db33b30cf44","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":800,"y":460,"wires":[["df086cb7f2953b3e"]]},{"id":"eb7025ba9bb0502f","type":"debug","z":"db243db33b30cf44","name":"debug 85","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":800,"y":600,"wires":[]},{"id":"ea11fd8ecd0c1bf3","type":"delay","z":"db243db33b30cf44","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":1160,"y":540,"wires":[["d52f3616b76210d6"]]}]
The usage of the template is rather straightforward, simply drop into the outbox
folder all the files at once and ensure that there is only one plain text file that contains the correctly formatted document, as explained within this page, in order to send an E-Mail with all the other non-text files being attached.
The network share contains two folders contacts
and outbox
both being meant to contain files that are formatted given a specific pattern.
The files contained within the contacts
directory contain a list of contact E-Mail addresses line by line, for example, list_1.txt
could have the following contents:
a@a.a b@b.b
etc, whilst the list list_2.txt
could have the same or different contents:
a@a.a c@c.c
while the files contained within the outbox
directory carry a specific format that has to be used in order to allow Node-Red to send E-Mails to lists of contacts.
For example, if a user wishes to E-Mail all the contacts within the file list_1.txt
that is placed within the contacts
directory of the network share, then the user will have to create a file, for example named send.txt
that uses the following format:
To: list_1 Subject: some subject body of the E-Mail
where:
To:
followed by the name of a file (without the extension .txt
) within the contacts
folder of the network share will make Node-Red send this E-Mail to all the recipients within the file,Subject:
will be followed by the typical E-Mail subject
In order to send the E-Mail, all that the user has to do is take the created file named send.txt
and place it within the outbox
folder of the network share and Node-Red will handle the rest.
Similarly, in order to change the distribution lists, the user can access the contacts
directory under the network share and add or remove E-Mail addresses from the stored distribution lists.
All the nodes used for the Node-Red flow are standard nodes thereby reducing the dependency tree of the project. The only exception is the email
node that corresponds to the node-red-node-email project that is meant to send an E-Mail to the recipients.
The E-Mail node is needed in order to be able to send E-Mail via Google that has recently shut down its SMTP service thereby breaking a bunch of projects relying on standard services. Fortunately, Google now went back on that decision and added something called "app passwords" that are, in effect, a second password that can then be used to access the standard SMTP service (of course, why not just remove "app passwords" and just use one single password is everyone's best guess). That being said, there are now no extra complications needed to configure the email
node (no OAuth tokens) such that the authentication type is basic and SMTPs (not STARTTLS) is used to send E-Emails using Google services.
Files that are placed within the outbox
folder are read by Node-Red and then have to be processed. This is done by reading the file line-by-line, loosely following a raw E-Mail header format comprised of headers such as To
or Subject
, and then setting flow globals corresponding to the To
, Subject
and to the body of the E-Mail. On the Node-Red flow, the nodes function 39
and trigger 1s
are used together in order to recompose the body of the well-formatted file supposed to be placed inside the outbox
folder of the Samba share using an UNIX alarm programming pattern. When the entire message is read, and the body of the E-Mail is recomposed into one single string, a signal is sent via the Link Out node to the Link In node of the bottom part of the Node-Red flow. Since the To
header was previously read, the flow now knows which distribution list to select and will then start reading E-Mail addresses line-by-line and sending them out to the various contacts.
A 1 message per second throttle is added via the limit 1 msg/s
node that could be used to peg the E-Mail sending rate to the allowed sending rate of the E-Mail provider by changing the settings of the node.