Sending emails with attachments with AWS Lambda and Node.js
One of the greatest services provided by AWS is Lambda. In a single sentence and as stated on the official AWS page:
Lambda lets you run code without provisioning or managing servers
How’s that possible? Well, as the reader probably knows already:
So basically Lambda let’s you define a piece of code that executes a specific functionality, following a defined signature (more on that later). Then whenever you want, you can tell AWS to execute your function and the magic will happen. The magic is basically Amazon allocating some resources in their cloud infrastructure to execute that piece of code you wrote.
The great thing about this, is that you can build specific server side functionality without the need of managing a server yourself. AWS will take care of allocating the needed resources for your Lambda function to execute successfully.
The email use case
As a software development company, at XOOR we build websites and web applications for many different purposes. But among all of them, one very common use case is the typical contact form on a company website. It happens that company websites are usually fully static, meaning that we don’t need a web server as we already explained in this post. But whenever you want to add some extra functionality to the website, like a contact form where people can reach out to you, you end up needing some backend code to take care of the email sending.
What happens is that setting up a whole web server for the purpose of sending a few emails every day feels a lot like killing a fly with a sledgehammer… So why not define this behavior in a Lambda function that will execute only when someone uses our contact form, and thus reducing costs as Amazon will charge only based on the resources used by the function execution. Sounds like a plan? Then let’s stop the chatter and let’s see how to set that up.
Choosing an email sending service
First things first. If we don’t have a service that will send emails for us, then we’re in trouble. There are several options around: Mailgun, Mandrill, Mailjet, AWS SES, and many others. Here a good summary of available tools.
We love to keep things simple, and as we’ll be working with AWS Lambda, we think it’s a great idea in this case to go with AWS SES (Simple Email Service).
AWS SES Configuration
So head over to your AWS accpimt and open the SES management console. The first thing you’ll have to do is to verify an email address which SES will use to send emails from. So under Identity Management, click Email Addresses.
You should see something like this:
Go ahead and click the Verify a New Email Address button and follow the steps. If everything goes well, you should get in your inbox a mail with a link to verify that the email address belongs to you. Click the link and your address will become verified.
Setting up the IAM Role
Our Lambda function will be executed under a specific IAM role. If you’re not familiar with IAM roles you should read a bit about it on AWS official documentation as it’s the base for all permissions under your AWS account.
In order to create a new Role, we need to think of what kind of permissions will our Lambda function need. So basically we will need this:
- Access to SES to send emails
- Access to S3 to send attachments
- Access to CloudWatch to store execution logs
So in our IAM console we;ll click the Roles option on the left side menu, and then click the Create Role button. This will start a pretty straightforward wizard that will simplify the role creation for us:
Step 1: We need to select here what kind of entity will be using this role. In our case it will be a Lambda function, so go ahead and select Lambda.
Step 2: Now we need to assign the permissions we listed before. AWS permissions list is HUGE. You can type on the search box to make things faster. Go ahead and search for AmazonS3FullAccess, AmazonSESFullAccess and CloudWatchLogsFullAccess, check the 3 of them and then hit the Next button.
Step 3: Assign a name to the role and a description, review the policies attached and confirm that everything is ok. Then click Create Role.
We’re done with permissions which means we’re ready to setup the Lambda function.
Setting up the Lambda function
Now it’s time to create our Lambda function. Go to your Lambda management console and click the Create function button. You’ll need to fill in a few fields:
- Name: this is your function name, choose something that will help you recognize easily what the function does like sendEmailWithAttachment
- Runtime: there are many runtimes available, in our case we’ll be coding in Node.js, so choose Node.js 6.10.
- Role: Your Lambda function will be executed under a specific IAM role. We’ll choose here the role that was created in the previous section of this article.
After you hit create, you’ll be taken to the function management console which looks pretty cool, specially the Designer panel that gives a nice big picture of the services that trigger our Lambda function and the services our function has access to:
Right below the Designer panel, you’ll find the Function code panel. There’s where we’ll be coding our function.
Lambda Function Signature
Whatever your function does, it must follow a signature known by AWS which is already provided in the Function code panel by default and looks like this:
We’re not covering details of Lambda here, so if interested you can find more about this handler signature here. The default coding panel is good enough if you don’t need any extra node modules. In our case we’re going to use a module called Nodemailer because it will make our life much easier to send emails with attachments. The good thing is that we can either code our function directly on the Lambda coding panel, or we can code it locally in our preferred IDE (a.k.a. VSCode?) and upload later a ZIP file with the code. The latter is what we’ll be doing.
If you’re too lazy to create a new project and code everything, you can always fork our Github repo and use that as a base.
Setting up the local project
Go ahead and initialize a new node project anywhere in your computer by using
As said before, we’re using nodemailer, so go ahead and install it with
npm i nodemailer
Now create an index.js file and paste initially the lambda function signature we talked about before.
On the very top of the index.js file, we’ll import and create a few things we’ll be using:
First thing we require is the AWS SDK for Node.js which is available for us by default within the Lambda context. Then we import the nodemailer library. And finally we create instances of AWS services that we’ll be using, in this case SES for sending emails and S3 to get attachment files.
Our function still does nothing, but you can try it by zipping the project and uploading it by choosing the corresponding option on the Function code panel:
Once uploaded you can test the function with the Test button in the top:
So, time to make something interesting with our function!
Sending a simple email
Let’s put directly the whole code that sends the email here and we’ll review details afterwards. So go ahead and paste the following into your Lambda function in index.js:
Our handler is now doing some interesting stuff:
- We first define our mailOptions object. Here we define a few things:
- from: is the email address that will be sending the email. This must be a verified SES email address, so use here whatever address you verified in the very first step of this post.
- subject: the email subject
- html: the email content. Notice how we’re using here the Lambda event object to send in the email content the address of the person who wanted to contact with us. Here is where you’d build an email with all the details you require in your contact form, like full name of the person, the contact message, etc.
- to: similar to from, this is the email address to which you want to send the email. Since this is supposed to be a function that will be executed when people is trying to reach me (or my company) on my personal website, I will use the same address I already had verified. It looks weird to define an email sent and received by the same email address, but in our use case it doesn’t matter, as all we care about is the content of the email from a contact form on our website.
- We then create a new transporter. Transporters are objects that know how to do certain things depending on the underlying email sending service we use. In our case we’re using AWS SES, and Nodemailer provides already a transporter that understands how to send emails using the AWS SES API. All we have to do is provide a SES client to the transporter and it will use it when needed.
- Finally we call the sendEmail function on the transporter and we pass the mailOptions defined at the beginning. If everything goes well, the callback function will be called with no error.
All set, go ahead and test again the function. This time make sure you define a property called emailAddress in the test event, otherwise you’ll see a good ol’ undefined in the email you’ll receive. If everything goes well, you should get an email in your inbox!
Adding an attachment from S3
In one of our latest projects at XOOR we had to create a function similar to the one we already coded in this post, but it had to send some attachments to the user based on a survey result.
An important note is that so far, we can just send emails to our verified email addresses. In our case we wanted to send emails to any user without the need of verifying their email address. If you have the same requirement, you’ll need to complete a process on AWS to be authorized. More about this here: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html
For the purpose of this post, we’ll just show how you can get a file you have stored on any S3 bucket of your own and send it as an attachment in the email that the Lambda function sends. So grab any file you want to send as an attachment and upload it to an S3 bucket you own. Let’s suppose that in our case the file is in the bucket xoor-email-attachments and the file key is attachment.pdf.
We need to first code a function that will grab the file from S3 for us:
We put this function before the exported handler in our Lambda function code. See how we use the s3 object we defined before (var s3 = new ws.S3()). Our function receives the bucket and file key as parameters, and returns a promise that will resolve with the file data, or reject with an error in case something goes wrong.
Now let’s use the function in our Lambda function handler which will finally look like this:
As you can see, we use the getS3File() function to get the attachment and we then use it in the mailOptions object in the added attachments property.
This property accepts an array, meaning you can send as many attachments as you want. Each attachment has a filename which will be the name of the file as received in the email, and a content where you should put the file raw data.
So, all set! go ahead, test your function again and you should get the email with the attachment successfully as I just did :)
We’ll also learn how to protect the API Gateway using CORS headers and how to add validation for the request body.
Thanks for reading and look forward to hear your thoughts and comments about this implementation! Cheers!
PS: If you don’t want to scroll all the way up, here is the Github repo with the full lambda function code: https://github.com/xoor-io/lambda-send-attachment