About

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.

Diagram

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.

Configuring Samba

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.

Node-Red Flow

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"]]}]

Formats

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
  • the rest is the E-Mail body that can contain any amount of text

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.

Developer Notes

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.


iot/creating_an_e-mail_distribution_system.txt · Last modified: 2023/07/03 11:41 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.