Sending an email attachment with Node-RED

You can use the Node-RED email node package to send emails with attachments as soon as it’s installed, but there are a few small details that are critical in getting it to work smoothly. In this post I’ll go over a basic flow to read in a specific file on a regular schedule and email that file out as an email attachment to yourself or other recipients:


This flow was written using Node-RED v1.0.3 on groov hardware, but this technique will work for many Node-RED versions running on other Linux or even Windows systems after some small modifications.


The first key is the format of the attachment JSON object, which according to the email node info tab must follow the nodemailer format. In this case I only have one attachment, so I provide just one file JSON object that holds the file name and file content, following this basic format template:

msg.attachments =
     { filename : "name_of_attachment.txt", // name of the file that will be attached
        content : msg.payload };            // content should be a file binary buffer

The second key part of this flow is to get the msg.payload “binary buffer”. To do that I just need to read the data in using the pre-installed file node. For the simplicity of this example, this file is hard coded in the file node (I’ll share an example of variable file names in the thread below):

image
The most important things here are that I include the entire file path, and that I return a single buffer object that will then be written to msg.payload and handed in as the attachment content in the next function node.

Once this msg.attachment object is made I just need to set the email subject in msg.topic, create a new email body in msg.payload, and configure the email-write node. I can handle most of this in the function node, and can even add the day and time to the email subject. In this example I use the end of the file path to get the file name that will be sent in the email – this way a file path of /home/dev/unsecured/hello_world.txt will automatically name the file attachment hello_world.txt

Note: If you are using gmail, make sure you set up the sending account with an application password. Because of this requirement I recommend using an email account separate from your personal or work email accounts just for Node-RED.


The flow JSON text is pasted below if you want to import this and try it for yourself. Just make sure you install the “node-red-node-email” package before importing this flow (since the email node is not a preinstalled core node as of Node-RED v1.0+).
Please feel free to modify this for your needs and don’t hestitate to post any questions or suggestions to this thread.

And as always, happy coding!

[{"id":"7f372dd4.9ac984","type":"file in","z":"eedf3627.4e1558","name":"get file","filename":"/home/dev/unsecured/hello_world.txt","format":"","chunk":false,"sendError":false,"encoding":"none","x":430,"y":1760,"wires":[["892f85fd.ddf898"]]},{"id":"892f85fd.ddf898","type":"function","z":"eedf3627.4e1558","name":"write email","func":"file = msg.filename;    // create local file variable for convenient reference\nvar d = new Date();     // create current date object for the time string\nvar tstring = d.toString().substring(0,4) + d.toString().substring(15,21);\n\nmsg.attachments =\n     { filename : file.substring(file.lastIndexOf('/')+1,file.length),\n        content : msg.payload };    // content should be a file binary buffer\n        \nmsg.topic = \"Your Daily Report for \" + tstring; // email subject\n\nmsg.payload = \"See attached text file: `\" + msg.attachments.filename + \"`\"; // email body\n\nreturn msg;","outputs":1,"noerr":0,"x":590,"y":1760,"wires":[["3d2eae0.1c8b452"]]},{"id":"e56d7627.05dcc8","type":"e-mail","z":"eedf3627.4e1558","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"recipient@mail.com","dname":"","x":830,"y":1760,"wires":[]},{"id":"5fb69172.b3471","type":"inject","z":"eedf3627.4e1558","name":"6:00PM","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"00 18 * * *","once":false,"onceDelay":0.1,"x":280,"y":1760,"wires":[["7f372dd4.9ac984"]]},{"id":"3d2eae0.1c8b452","type":"debug","z":"eedf3627.4e1558","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":770,"y":1720,"wires":[]}]
3 Likes

Like most things with Node-RED there are many ways to do the same thing.
Here is how I build my reports to send via email…

The switch node in this case is optional, its just how I trap for a key word that I want to log to my file.
The cool stuff is in the date/time formatter node aka the ‘Moment’ node. Note, you will need to install this node since its not a core node.
What we are doing here is building the file name that will be used to write the file to the hard drive in the computer.
It will automatically roll over at midnight and so you will end up with 1 file per day all in the file path that you hard code into the node.

One thing to note, its critical that you use the correct ‘Output Tz’ name. You can find your TZ database name from the list.

Note that in this screen shot, I am running Node-RED on a Windows PC, so the path will need to be changed to match your platform and file path you want your data logged.
Best practices dictates that you should NOT have any spaces in your file name.

The file node is very straight forward;

2 Likes

If your file name changes for any reason, or if you regularly add new files and want to email the latest one, you can also take advantage of the fact that the file-in node can use a message property msg.filename to determine which file it will read in.

For example you may want to just enter the value into a groov View gadget, read it from a database, or just check the list of local files and send out an email with the most recent file as an attachment.
Here is a way to send out any recent file as soon as it’s created:

The logic flows like this:

  1. Regularly inject to get the most up-to-date file list.
  2. Use the exec node to find out what files are in a specific folder.
  3. Use JavaScript to easily grab the last item in the file list (sorted alphabetically).
  4. Check to see if that that file is new or not (stop the flow if the file is has already been sent).
  5. Set the msg.filename property to be the path to that new file.
  6. Read the file binary data into the Node-RED flow.
  7. Set the email properties.
  8. Send the email.

This process is ideal if you regularly generate files with the date in the name, for example "daily_report_2020_09_01.txt", so that the newest report is always at the bottom of this list.


Here’s a more detailed breakdown of each step and node along the way so that you know what to tweak for your own applications:

  1. The regular inject just checks the file list every minute, so that within 1 minute of a new file being added to the list, an email with that file attached will go out.

  2. The exec node will check the folder location where you have the files stored. In this case, running on a groov EPIC, I am storing my reports in the unsecured file area which is at /home/dev/unsecured, so that is where I run the file list command ls.
    Note: If you are running this flow on a Windows system you will need to usedir /b C:\\Path\\to\\your\\folderto work with Windows commands and file pathing.

  3. The JavaScript code splits the returned string list into an array that can easily be navigated. In this case I want to grab the very last item, since it is the most up-to-date report. You may want to offset differently depending on which file you want to attach.

  4. Report By Exception (rbe) will block any repeated file names, and only let new files through. This way only new files added to the end of the folder will be sent out.

  5. Since we only have the file name and not the complete file path required by the file-in node, make sure you add that file folder path to the file name (in this case that’s /home/dev/unsecured/).

  6. Use that complete file path to read in the file binary data to msg.payload

  7. Fill in the email msg.attachment property, the email msg.topic subject property, and finally the new msg.payload email body property.

  8. Finally, send the entire email object using the node-red-node-email “e-mail” node (not email-in).


Here is the example flow text so you can import and modify it for your own needs:

[{"id":"8370947d.7de308","type":"e-mail","z":"eedf3627.4e1558","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"recipient@mail.com","dname":"","x":1030,"y":1340,"wires":[]},{"id":"67c472c2.2518ec","type":"function","z":"eedf3627.4e1558","name":"write email","func":"file = msg.filename;    // create local file variable for convenient reference\nvar d = new Date();     // create current date object for the time string\nvar tstring = d.toString().substring(0,4) + d.toString().substring(15,21);\n\nmsg.attachments =\n     { filename : file.substring(file.lastIndexOf('/')+1,file.length),\n        content : msg.payload };    // content should be a file binary buffer\n        \nmsg.topic = \"Your Daily AT1 Report for \" + tstring; // email subject\n\nmsg.payload = \"See attached text file: `\" + msg.attachments.filename + \"`\"; // email body\n\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":1340,"wires":[["9e6f5035.4e164","8370947d.7de308"]]},{"id":"e3dd220e.ef90c","type":"file in","z":"eedf3627.4e1558","name":"","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":670,"y":1340,"wires":[["67c472c2.2518ec"]]},{"id":"9e6f5035.4e164","type":"debug","z":"eedf3627.4e1558","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":990,"y":1300,"wires":[]},{"id":"93e223dd.0cfc1","type":"change","z":"eedf3627.4e1558","name":"","rules":[{"t":"set","p":"filename","pt":"msg","to":"\"/home/dev/unsecured/\" & msg.myFile","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":1340,"wires":[["8de6b803.6520a8","e3dd220e.ef90c"]]},{"id":"1f7d5e26.c0d8f2","type":"rbe","z":"eedf3627.4e1558","name":"new file?","func":"rbe","gap":"","start":"","inout":"out","property":"myFile","x":480,"y":1300,"wires":[["93e223dd.0cfc1"]]},{"id":"8de6b803.6520a8","type":"debug","z":"eedf3627.4e1558","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":690,"y":1300,"wires":[]},{"id":"cfcec2ba.db0d2","type":"function","z":"eedf3627.4e1558","name":"split and extract","func":"fileList = msg.payload.split('\\n');          // split the text list of files into an array\n\nmsg.myFile = fileList[fileList.length-2];      // the last item in the list (excluding trailing newline) is the file I want\n\nreturn msg;","outputs":1,"noerr":0,"x":480,"y":1260,"wires":[["1f7d5e26.c0d8f2"]]},{"id":"76c60a6a.65ddd4","type":"exec","z":"eedf3627.4e1558","command":"ls /home/dev/unsecured","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"get file list","x":300,"y":1280,"wires":[["cfcec2ba.db0d2"],[],[]]},{"id":"69ce3ba2.239194","type":"inject","z":"eedf3627.4e1558","name":"1min","topic":"","payload":"","payloadType":"date","repeat":"60","crontab":"","once":true,"onceDelay":0.1,"x":170,"y":1280,"wires":[["76c60a6a.65ddd4"]]}]
1 Like

Going off @torchards last post, what if you are running Node-RED on a Windows PC?
What in his flow needs to be tweaked?

(Nodes 2 and 5 in Terry’s flow screenshot).

The two nodes I have pointed out are the two that you need to change the file path to match between Windows, EPIC and RIO.
The file path is used in both of those nodes so just adjust it to suit your platform and so on.

First up, the red exec node;

We need to do a directory listing, but a brief one. We dont want all the usual Windows cruft from a normal listing, so hence the /b, don’t discard it, the flow will break.

Next the change node.

This is a little tricky for Windows platform. You need to escape the \ so double them all up.
Of course for Linux, its a / and so you don’t need to double them. (Or put the drive letter).

So there are the changes you need to make to Terry’s flow to get it working on your Windows PC or EPIC or RIO.

1 Like

Hii ,

I am doing the same, my mail is sent with body content but the attachment is not going with it.

Welcome to the forums!

Are you using Terrys flow? Did you test your email with Terrys flow?
We know it works and it should be the seed for your application.

We also will need more information about what you have tired and how you are testing the flow to try and help you if we can.

1 Like

This is a great article and very helpful. Many thanks solved a challenge for me!

Hi Team, I usually read up and get the solution on my own. But this one I am stomped and going to ask for help. I followed the method and created my file using the date and format exactly like this.


So the purpose of the timestamp is to automatically trigger once a day to create a file with, YYYY-MM-DD then a few characters. As you can see, the file name I used is just edited from your file.

Write file is also very similar but I used “msg.filename.”

Here is the catch, I was able to create the csv file but I am stomped how to write to it. If you can see from my 1st photo, namely, “function 18” is trying to write to the csv file I just created. Data packet I need saved to the CSV file is msg.payload. But the write file only has 1 space to either accept msg.filename or path. For reference, my message payload is just the MM-DD-YYYY; HH:MM:SS; a 1.

The coding I am trying to put together is to track the peak times my tenants use my bus. After solving this, I still need to send automatically my CSV file on a daily basis automatically… Well that’s for another day. Hope you guys can help.

Hi Albert. Welcome to the forums!

You cant have two wires going into the file node. That’s the root cause of the issue.
The nodes cant read your mind on which msg comes first and put the two messages together in the order you expect.

So what you need to do is come out of ‘Date to File name’ moment node and go into the ‘function 18’ node.
Then build the filename in that function node and that will then go to the write file node just as it does now.
That way your filename and file contents will arrive together to the file node and it will know what to write and where.

2 Likes