Use SES to Forward Any Email Address


February 18, 2019

Here is a great way to forward any email address you create from your domains hosted on Route53 to any other email server (Gmail, Outlook, etc.) without having to pay for Workmail!

If you aren’t familiar with the Simple Email Service (SES):

Amazon Simple Email Service (Amazon SES) is a cloud-based email sending service designed to help digital marketers and application developers send marketing, notification, and transactional emails. It is a reliable, cost-effective service for businesses of all sizes that use email to keep in contact with their customers.

You can use our SMTP interface or one of the AWS SDKs to integrate Amazon SES directly into your existing applications. You can also integrate the email sending capabilities of Amazon SES into the software you already use, such as ticketing systems and email clients.

In addition to SES you will need to employ SNS, Lambda, and of course IAM.

Enter CloudFormation

Run the Cloudformation Template in order to provision these services:

  1. SNS Topic to forward, received emails from SES
  2. IAM Role to invoke Lambda function from SNS
  3. Lambda function with NodeJS code to forward the emails to a defined from and to address
  4. IAM Role allowing Lambda to forward emails.

SES_Forward_CloudFormation

CloudFormation Template

(Select all and copy into CloudFormation)

### CloudFormation template to forward the AWS Simple Email Service (SES) emails to personal email server (Gmail, Outlook, etc.)
# Created by Ashan Fernando, updated and commented by Cole Russell

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Cloudformation to forward SES email to another address'

# Receive input email address parameters from AWS console
Parameters:
  FromAddress:
    Description: Email forwarded from address
    Type: String
  ToAddress:
    Description: Email forwarded to address
    Type: String

# Forward SES to Lambda Function and pass input variables.
Resources:

    # Create SES Email Forward Function with Lambda
    SESEmailForwardFunction:
      Type: AWS::Lambda::Function
      Properties:
        Handler: index.handler
        Role: !GetAtt SESEmailForwardRole.Arn
        Timeout: 30
        Environment:
          Variables:
            from_address: { "Fn::Sub": [ "${FromAddress}", { "FromAddress": {"Ref" : "FromAddress" }} ]}
            to_address: { "Fn::Sub": [ "${ToAddress}", { "ToAddress": {"Ref" : "ToAddress" }} ]}

        #Lambda code to forward emails
        Code:
          ZipFile: !Sub |
            var AWS = require('aws-sdk');
            var forwardFrom = process.env.from_address;
            var forwardTo = process.env.to_address;
            exports.handler = function(event, context) {
                var msgInfo = JSON.parse(event.Records[0].Sns.Message);

                // don't process spam messages
                if (msgInfo.receipt.spamVerdict.status === 'FAIL' || msgInfo.receipt.virusVerdict.status === 'FAIL') {
                    console.log('Message is spam or contains virus, ignoring.');
                    context.succeed();
                }

                var email = msgInfo.content, headers = "From: "+forwardFrom+"\r\n";
                headers += "Reply-To: "+msgInfo.mail.commonHeaders.from[0]+"\r\n";
                headers += "X-Original-To: "+msgInfo.mail.commonHeaders.to[0]+"\r\n";
                headers += "To: "+forwardTo+"\r\n";
                headers += "Subject: Fwd: "+msgInfo.mail.commonHeaders.subject+"\r\n";

                if (email) {
                    var res;
                    res = email.match(/Content-Type:.+\s*boundary.*/);
                    if (res) {
                        headers += res[0]+"\r\n";
                    }
                    else {
                        res = email.match(/^Content-Type:(.*)/m);
                        if (res) {
                            headers += res[0]+"\r\n";
                        }
                    }

                    res = email.match(/^Content-Transfer-Encoding:(.*)/m);
                    if (res) {
                        headers += res[0]+"\r\n";
                    }

                    res = email.match(/^MIME-Version:(.*)/m);
                    if (res) {
                        headers += res[0]+"\r\n";
                    }

                    var splitEmail = email.split("\r\n\r\n");
                    splitEmail.shift();

                    email = headers+"\r\n"+splitEmail.join("\r\n\r\n");
                }
                else {
                    email = headers+"\r\n"+"Empty email";
                }

                new AWS.SES().sendRawEmail({
                    RawMessage: { Data: email }
                }, function(err, data) {
                    if (err) context.fail(err);
                    else {
                        console.log('Sent with MessageId: ' + data.MessageId);
                        context.succeed();
                    }
                });
            }
        Runtime: nodejs8.10   #FIXED for latest NodeJS version in Lambda

    # Create SNS Receive Topic
    SNSEmailReceiveTopic:
      Type: AWS::SNS::Topic
      Properties:
        Subscription:
        - Endpoint:
            !GetAtt SESEmailForwardFunction.Arn
          Protocol: lambda

    # Create SES Email Forward Role
    SESEmailForwardRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
            - sts:AssumeRole
        Path: "/"
        Policies:
        - PolicyName: SESEmailForward
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
              Resource: arn:aws:logs:*:*:*
            - Effect: Allow
              Action:
              - ses:SendEmail
              - ses:SendRawEmail
              Resource: "*"

    # Create SNS invoke Lambda Role
    SNSLambdaInvokeRole:
      Type: AWS::Lambda::Permission
      Properties:
        Action: lambda:InvokeFunction
        Principal: sns.amazonaws.com
        SourceArn:
          Ref: SNSEmailReceiveTopic
        FunctionName:
          !GetAtt SESEmailForwardFunction.Arn

# Output to lambda console
Outputs:
  LambdaFunction:
    Value: { Ref : SESEmailForwardFunction }

Cloudformation creation should be “successful”.

It will ask you for the “from address” and the “to address”

SES_Forward_Emails

SES Rule Sets

  • Create “Rule Sets” under Email Receiving
  • Create a Rule inside Rule Sets specifying the email address you are expecting to receive emails from: test@yourdomain.com
  • In Actions, create an Action for SNS and Select the SNS topic (from Cloudformation) from the list.
  • After creating the rule, make this Rule Set the default rule.

Test your new emails!

Additional things to consider

References