What are transactional emails?

Many applications need to send emails. Usually these emails are triggered in code in response to a given event, such as a product purchase. For this reason they are often called Transactional Emails. But an app or backend service can't just send an email, it needs to have an SMTP server setup. Setting up email servers can be tricky, so a number of services exist for sending emails with API requests. The main players are: MailSlurp, MailChimp (Mandrill), Sendgrid, MailGun, and SendinBlue.

Recently however more developers have been using Amazon's "simple email service" or SES. It's not as famous as the others but is developer focused and fits nicely into any existing AWS infrastructure. Plus, a lot of the time SES is easier and cheaper to use. Here is a simple guide for setting up an SES domain, sending emails, and receiving emails. Later, we'll go over testing your email actions with real email addresses.

Building the infrastructure in AWS

You can use the terraform aws_lambda_function module to create serverless functions in AWS.

Registering a domain

First off, you need a domain to send and receive email from. If you already have one that's great, if not go ahead and get one. You also need an AWS account.

Adding Route53 domain records

Next we need to add MX records for this domain to Route53. I'm going to use Terraform to set up my AWS infrastructure because it is a lot more maintainable in the long run.

First let's create some variables in Terraform:

Now let's create an SES domain identity and the Route53 records associated with it. This will let us send emails from the domain.

Setting up SES receipt rules

Great, now we can send and receive from the domain we registered. But we still need to tell SES to use this domain with a set of email addresses. These configurations are called receipt rule sets. Let's create a rule that catches all inbound emails to any address in our domain and saves them to S3 and notifies an SNS topic.

We might also want to catch email errors. Let's add a rule for that and send them to a cloudwatch log.

How SNS fits in

So in the above rulesets we defined a catch all for all inbound email. It's actions were to save the email to S3 and then notify an SNS topic. SNS is a simple notification service that we can subscribe to within our application or with a lambda to handle inbound emails. The event we receive in these handlers will contain a link to the S3 bucket item containing the email. Here is how we can set up an SNS topic in Terraform with a lambda to handle the event.

Create a Lambda to handle your emails

You could at this point do anything you like with the SNS topic to handle emails. You could subscribe to it within your application, or you could hook it up to a Lambda using an aws_lambda_function resource. I'll show you how you might right a Lambda for this kind of event and act on inbound emails.

Defining the code

An aws lambda function is a basic python function that is invoked when an event occurs. We def lambda handler event context and then handle the event. Here is how we might handle an inbound email event.

Deploying lambdas with AWS and terraform

And here is how we can deploy the Lambda using Terraform.

The function was uploaded as a zip file using the archive_file data source. When using the type zip source the whole folder is included when we use terraform apply. This means we can include any dependencies we need in the folder and they will be included in the zip file. This is useful for python lambdas that have dependencies or need to read local files.

The source code has data.archive is used to version the upload so we only change the lambda function when the source code changes. This is useful for CI/CD pipelines.

Notice how the aws lambda permission sets permissions? You may need let the lambda assume role create a terraform resource aws iam role policy attachment or iam roles.

Using SES

Receiving an email

We now have Route53, SES, SNS and a Lambda (using aws_lambda_function) in place to handle inbound emails. We can now send an email from some email client to "test@mydomain.com" and watch our Lambda get invoked. From this point it's up to you how you want to handle inbound email events.

Sending a transactional email

Now to the crux of the article: sending transactional emails. With the setup we created above we can now send emails from any address on our registered domain with a simple API call to AWS. For this, the best approach is to use the AWS SDK in your given language. I use a lot of Kotlin for MailSlurp so I'll show you my approach using the AWS SES SDK for Java.

And here is how you would send an email with the given client.

Pros and cons of AWS SES

SES is great but there are some downsides:

  • There is no GUI for non-coders to send and receive emails
  • There is no transactional email contact management
  • Lacking many features that make other providers great

Positives include:

  • Cheaper than other providers
  • More control
  • Fits with existing infrastructure

Testing your transactional emails

So all this infrastructure wouldn't be much use if we we're sure of it's reliability. That's why end-to-end testing email functionality is so important. We can test whether our infra receives and handles real emails using MailSlurp!

How does email testing work?

Basically, MailSlurp is an API that lets you create new email addresses for use with testing. You can create as many as you want and send and receive emails from them via an API or SDKs.

Writing a test for inbound emails

First you need to sign up for MailSlurp and obtain a free API Key. Then let's use one of the SDKs to test our domain.

We can install the SDK for javascript using . Then in a test framework like Jest or Mocha we can write:

If we run this test and it passes, that means that all our infrastructure is working and our Lambda is correctly handling our events.

We just tested our inbound email handling code with a real email address and real emails.

Testing transactional emails (are they actually sent?)

Lastly let's test whether our app can actually send emails and whether they are properly received. We could do this by sending an email to our own personal account each time and manually confirming its existance but that won't fit well into an automated CD test suite. With MailSlurp we can test the receiving or real transactional emails with real addresses during automated testing. This ensures that key parts of our application (emails) are actually working.

What does it look like?

Now if this test passes we can rest assured that our application will send transactional emails are the correct times and that those emails are actually delivered! This is fantastic!

Wrapping it all up

In summary, transactional emails are an important part of many applications. Doing so with AWS SES is a great way to gain more control over other services like MailChimp and MailSlurp. However, which ever solution you choose remember to test your transactional email sending and receiving with real email addresses. MailSlurp makes that easy and automated so you know for sure that your app is sending and receiving what it should.

I hope that helped!


Jack from MailSlurp