A long, long, long time ago, sending email via your website was really horrible. Alongside the static HTML powering your Guestbook, you had some copy/pasted CGI script your ISP somehow allowed you to use that probably didn’t work, but oh crap it started working I hope it doesn’t break now.
A long, long time ago, sending email suddenly became easier. It kind of just worked accidentally. You install WordPress or another app on a shared host and you got emails when people left comments.
A while ago, things started to get hard again. When it’s easy for everyone to send email, it’s also really easy for people to send massive amounts of spam. So larger email clients got smart and started requiring things like DKIM and SPF to really guarantee mail delivery. Without these configured on your domain, you’re at the mercy of the algorithm. Thankfully, places like Digital Ocean had excellent documentation for configuring stuff like this and with a bit of elbow grease, you could get there on a $10/month Linode server.
But then it got super easy! Mandrill offered a free tier for transactional emails that had a limit nobody would reach with a standard blog/comment configuration. You could sign up for an account and use a wp_mail
drop-in that handled the API interactions for you.
Of course, as with all free services, there’s a limit. Mandrill reached that limit this year and changed directions into a transactional email add-on for MailChimp accounts.
It happens, especially when it’s free. ¯\_(ツ)_/¯
And so it goes. On to the next service, hopefully configured in a structure that’s prepared for long-term use.
Why Postmark
It looks nice, I’ve seen it mentioned in a handful of conversations, the API seems straight forward, and I didn’t run into anything that made it hard to setup an account. I’m easy.
You get 25,000 free emails with Postmark. After that you pay a really reasonable rate. If you send a ton of emails, the rate gets more reasonable. I think this is a good model and they should probably even charge earlier because it’s going to take me a while to send 25,000 emails.
Once you sign up, Postmark is just as easy as Mandrill. There’s an official plugin that provides a settings screen for you to add your API key and a wp_mail
replacement that handles the API calls. If you’re like me, you’ll skip the full plugin and grab only the wp_mail
drop-in, add some static configuration, and toss it into mu-plugins
.
The catch…
As it should, Postmark requires that you add Sender Signatures for any email address from which you’ll be sending email. So before I can send email from jeremy@chipconf.com
, I need to show that I can already receive email on that same address.
At this point, a normal person decides to enable email forwarding through their domain registrar or host. That is the easy way, but it was Saturday and I was looking for a party.
Receiving email through Amazon
Amazon has 9 million AWS services. It only takes 3 of them to solve the email receipt problem and not one involves setting up a server. The hardest part is keeping track of all the open tabs.
- Amazon Simple Email Service (SES) was originally built to handle the sending of transactional emails. In September 2015, they added support for receiving email through the same service.
- Amazon Simple Storage Service (S3) is a place to store things. In this case, it will be where we drop incoming emails to be processed.
- Amazon’s AWS Lambda is the cool new kid on the block and allows for “serverless” computing. You define a function and are charged only for the computing time that the function actually uses.
To get through this, you’re going to need a verified AWS account and access to your domain’s DNS settings via whichever name server you use. I use DNSimple, which has made every DNS configuration in the last 5 years a pleasant experience. That’s an affiliate link even though it’s already only $7/month for me. 🍻
Let’s do it.
Configuring SES to receive and forward email
- Go to SES via the Services menu your AWS Console and select Domains under Identity Management.
- Click on Verify a New Domain at the top of the screen.
- Enter the root of the domain you’re verifying, in my case
chipconf.com
, check the Generate DKIM Settings option, and click Verify This Domain. - You’ll be presented with an overlay containing the new records that need to be attached to your domain as part of your DNS configuration. Carefully enter all of these as any mistakes may add extra time to the verification process. I set all of mine with a 10 minute TTL so that any errors may resolve sooner.
- A TXT record that acts as domain verification.
- Three CNAME records for the DKIM record set, which SES rotates through automatically.
- And an MX record to route incoming email on your domain to AWS.
- Click Close on the DNS overlay. You’ll now need to be patient as the domain is verified. Amazon says this may take 72 hours, but it’s taken 5 minutes for 3 of my domains and 20 minutes for one where I had an error in the config at first. You’ll get a couple emails as soon as the verification goes through.
In the meantime, you’ll want to verify any email addresses that you will be forwarding email to. As part of the initial SES configuration, you’re locked in the Amazon SES sandbox and can only send emails to addresses you have verified ahead of time.
- Select Email Addresses under Identity Management.
- Click on Verify a New Email Address at the top of the screen.
- Enter the address you’ll be forwarding mail to and click Verify This Email Address.
- Once you receive an email from AWS, click on the link to complete the verification.
Note: You’re also limited to sending 200 messages every 24 hours and a maximum of one per second. Because transactional emails will be sent using Postmark, and only replies to those emails will become through SES, that shouldn’t be a huge deal. If you do reach that limit, you’ll need to request access for a sending limit increase for SES. If you think you’ll be receiving large volumes of email, you may want to also consider using SES for all of your transactional email (HumanMade has a plugin) and not use Postmark at all.
Ok, go back to Domains under Identity Management and check that the status for your domain is listed as verified. Once it is, we can continue. If you’re concerned that something isn’t working properly, use a command like dig
to double check the TXT record’s response.
› dig TXT _amazonsesbroken.chipconf @ns1.dnsimple.com +short › dig TXT _amazonses.chipconf.com @ns1.dnsimple.com +short "4QdRWJvCM6j1dS4IdK+csUB42YxdCWEniBKKAn9rgeM="
The first example returns nothing because it’s an invalid record. The second returns the expected value.
Note that I’m using ns1.dnsimple.com above. I can change that to ns2.dnsimple.com, ns3.dnsimple.com, etc… to verify that the record has saved properly throughout all my name servers. You should use your domain’s name server when checking dig.
- Once domain verification has processed, click on Rule Sets under Email Receiving on the left.
- Click on View Active Rule Set to view the default rule set. If a default rule set does not exist, create a new one.
- Click Create Rule to create a receipt rule for this domain.
- For recipient, enter the base of your domain (e.g.
chipconf.com
) rather than a full email address so that all addresses at that domain will match. Click Next Step. - Select S3 as the first action.
- Choose Create S3 bucket in the S3 Bucket dropdown and enter a bucket name. Click Create Bucket.
- Leave Object key prefix blank and Encrypt Message unchecked.
- Choose Create SNS Topic in the SNS Topic dropdown and enter a Topic Name and Display Name. Click Create Topic.
- Click Next Step. We’ll need to do some things before adding the Lambda function.
- Give the rule a name, make sure Enabled is checked, Require TLS is unchecked, and Enable spam and virus scanning is checked. Click Next Step.
- Review the details and click Create Rule.
Now head over to Lambda via the Services menu in the top navigation. Before completing the rule, we need to add the function used to forward emails that are stored in the S3 bucket to one of the verified email addresses.
Luckily, the hard legwork for this has already been done. We’ll be use the appropriately named and MIT licensed AWS Lambda SES Email Forwarder function. The README on that repository is worth reading as well, it provides more detail for the instructions involved with this section.
- Click Create a Lambda function.
- Click Skip on the next screen without selecting a blueprint.
- Enter a name and description for the function. Make sure Runtime is set at Node.js 4.3. Paste the contents of the AWS Lambda SES Email Forwarder index.js file into the Lambda function code area.
- Edit the
defaultConfig
object at the top of this file to reflect your configuration.fromEmail
should be something likenoreply@chipconf.com
emailBucket
should be the name of the S3 bucket you created earlier.emailKeyPrefix
should be an empty string.forwardMapping
is used to configure one or more relationships between the incoming email address and the one the email is forwarded to. Use something like@chipconf.com
as a catch-all for the last rule.
- Leave Handler set to index.handler.
- Select Basic Execution Role from the role list. A new window will appear to grant Lambda permissions to other AWS resources.
- Choose Create a new IAM Role from the IAM Role drop down and provide a Role Name.
- Click View Policy Document and then Edit to edit the policy document. Copy and paste the below policy document, also taken from the AWS Lambda SES Email Forwarder repository, into that text area. Make sure to change the S3 bucket name in that policy to match yours. In the below policy document, I replaced
S3-BUCKET-NAME
withchipconf-emails
.-
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": "ses:SendRawEmail", "Resource": "*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*" } ] }
-
- Click Allow. You should be transferred back to the Lambda screen.
- Under Advanced Settings, set Memory to 128MB and Timeout to 10 seconds. You can leave VPC set to No VPC.
- Click Next.
- Review the new function details and click Create function.
Whew. Almost there.
Now head back to SES via the Services menu in the top navigation. We need to edit the rule set to use the new Lambda function.
- Click Rule Sets under Email Receiving and then View Active Rule Set to see the existing rules.
- Click on the name of the rule from the previous steps.
- Select Lambda as an action type next to Add Action.
- Select the new function you created next to Lambda function. Leave Event selected for the Invocation type. Leave None selected for SNS topic.
- Click Save Rule.
- A permissions overlay will appear to request access for SES to invoke the function on Lambda. Click Add permissions.
Voilà!
Now I can go back to Postmark and add jeremy@chipconf.com
as a valid Sender Signature so that the server can use the Postmark API to send emails on behalf of jeremy@chipconf.com
to any address.
If someone replies to one of those emails (or just sends one to jeremy@chipconf.com
), it is now received by Amazon SES. The email is then processed and stored as an object in Amazon S3. SES then notifies Amazon Lambda, which fires the stored function used to process that email and forward it via SES to the mapped email address.
Now that you have 1800 words to guide you through the process, I’m going to dump a bunch of screenshots that may help provide some context. Feel free to leave a comment if one of these steps isn’t clear enough.
Responses and reactions
Replies
I still don't get it
Thank you, Jeremy. I was stunned when I learned that receiving email through SES required me to decode the MIME myself. This solution is exactly what I needed and it couldn't have been easier to follow.
Hello Jeremy,
The post was very helpful but I am stuck at a point. I am getting the raw email object from S3 bucket in the lambda function. I want to get the attachment out of it. I am not able to understand the structure of it. Can you please help me with that?
You dont need the s3+lambda setup any more. SES can directly forward the received email to you :)
How do you do that? I cannot find an option to that end.
hello,
thank for great article. i want get email from s3 to my inbox. i made all what what you said. email are receive my s3 but i can not get them from s3 bucket.
Thanks for this post, Jeremy. I used it to whip up a quick tutorial on how to use Serverless to accomplish email forwarding with SES. https://habd.as/serverless-email-forwards-ses-lambda-crash-course/
Thanks for the walkthrough. It helps demystify how to set-up SES, which can be a major pain. I really hope someone creates something to automate it the way the s3_website Gem helps automate creation of static websites using s3 and CloudFront.
As for using the forwarder script plopped into Lambda, I'm planning to keep it under source control with my website and using Serverless to manage it so it can be updated/managed, and contained/visible within my app, so that might be a good thing to point out to readers. And given SES is set-up with Spam filtering I'm hoping I can skip use of Google's reCAPTCHA and assume people are human without forcing them to prove it. Would love to hear your thoughts.
Interesting solution, but why not send outgoing mail through Amazon SES? Wouldn't that be cheaper than Postmark, and actually more straightforward since you're starting from Amazon ecosystem anyway? Maybe I missed something.
For the volume of email I'm sending, Postmark will be free for a long time with the initial 25000 email credit. Once I cross that barrier, then it would definitely be worth reevaluating.
Additionally - Amazon has some restrictions on sending mail, especially at the beginning. So that would be another process to have to go through as the volume of email went up.
The only requirement for your mention to be recognized is a link to this post in your post's content. You can update or delete your post and then re-submit the URL in the form to update or remove your response from this page.
Learn more about Webmentions.