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:
- SNS Topic to forward, received emails from SES
- IAM Role to invoke Lambda function from SNS
- Lambda function with NodeJS code to forward the emails to a defined from and to address
- IAM Role allowing Lambda to forward emails.
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 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
- Make sure to run the Cloudformation stack in the same region as your SES rule sets.
- Check MX records in Route 53 against: https://docs.aws.amazon.com/ses/latest/DeveloperGuide/regions.html#region-endpoints-receiving