Minimal Email Receiving

May 1, 2018

How we receive email for our domain using email forwarding on Amazon SES, with minimum effort.

Our web-app sends emails no problem, but we also want to receive emails into the same accounts. For example our web-app sends an automatic email from, but if it receives an email sent to, we want to handle that too.

The simplest way is to implement email forwarding.

(The hard way would be to build an email client inside our web-app, then decide how and when to notify whoever needs to handle it).

You can get a comprehensive understanding of receiving emails on AWS SES here: Receiving email concepts

We are following Use AWS SES to receive emails with lambda and S3. Thanks to Anil.

Things can change fast at AWS, so this blog post is about how we got it working.

Here is the flow:

  1. Amazon SES rule -> writes to S3
  2. Amazon SES rule -> executes Lambda function -> reads S3 and sends email.

Here are the required permissions:

  1. Grant Amazon SES permission to write to your Amazon S3 bucket
  2. Grant your Lambda function permission to write to logs
  3. Grant your Lambda function permission to read and write to your Amazon S3 bucket
  4. Grant your Lambda function permission to send email

How we are doing it:

  • Part 1: Set up an AWS lambda function for SES
  • Part 2: Verify domain name
  • Part 3: Create a Receipt Rule for Amazon SES Email Receiving
  • Part 4: Complete the S3 bucket policy
  • Part 5: Troubleshooting

Part 1: Set up an AWS Lambda function for SES

We followed Anils steps to create Lambda function “S3LambdaSESForwarder”. In particular we did the following:

  • Used the latest Node Runtime version.
  • Used 10 seconds as the Timeout.
  • For the Function Code we pasted into the inline code the contents of
  • Selected “Create a custom role” for the execution role and named it “S3LambdaSES”
  • For the “S3LambdaSES” role, add the AWSLambdaRole and AWSLambdaBasicExecutionRole ‘AWS managed’ policies.
  • For the “S3LambdaSES” role, create a “LambdaS3SesForwarder” policy, as follows:
# LambdaS3SesForwarder policy:
    "Version": "2012-10-17",
    "Statement": [
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ses:SendRawEmail",
            "Resource": "*"
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "*"

We modified the Function Code using ‘Edit code inline’ to update the config values as follows:

# index.js
var defaultConfig = {
  fromEmail: "",
  subjectPrefix: "FWD: ",
  emailBucket: "",
  emailKeyPrefix: "",
  forwardMapping: {
    "": process.env.PERSON_ONE.split(" "),
    "": process.env.PERSON_TWO.split(" "),
    "": process.env.PERSON_THREE.split(" "),
    "": process.env.CATCH_ALL.split(" ")

We added email address values as Environment Variables:


Part 2: Verify domain name

We had already done this.

Part 3: Create Receipt Rules for Amazon SES Email Receiving

We followed Anils steps to create ruleset “forward-all-email” and created one rule. In particular while creating the rule we did the following:

  • Left the ‘recipients’ box blank.
  • Left the optional ‘object key prefix’ as blank.
  • Created a new bucket: on creating a new bucket from within a SES rule, the bucket automatically gets most of the required permissions.
  • Added a Lambda action, choose the “S3LambdaSESForwarder” created earlier.

Remember to set the “forward-all-email” ruleset as “active” instead of the default one.

Part 4: Complete the S3 bucket policy

On the ‘permissions’ tab of the newly created S3 bucket, the ‘bucket policy’ automatically got most of the required permissions: we found both “s3:GetObject” and “s3:PutObject” actions are required, so updated it thus:

# bucket policy:
    "Version": "2012-10-17",
    "Statement": [
            "Sid": "AllowSESPuts-1234567890123",
            "Effect": "Allow",
            "Principal": {
                "Service": ""
            "Action": [
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:Referer": "123456789012"

Part 5: Troubleshooting

You can see how the Lambda function executed by clicking the ‘monitor’ tab. If the errors graph shows errors you can click ‘jump to logs’ then in Cloudwatch expand any lines that report errors.

Further reading

  1. For simple forwarding:
  1. For heavy use cases: - also based on
  2. For handling within your Rails app:
  3. Receiving and Parsing Email in Rails 5:
  4. Receiving Email in Your Rails App With Griddler:

© 2020 Keith P | Follow on Twitter | Git