Sarath walks you through
storing SNS notifications for SES
in DynamoDB

lambda-sns-ses-dynamodb-sarath

I use the Amazon Simple Notification Service (Amazon SNS) to receive notifications about bounces, complaints, and deliveries for email sent through the Amazon Simple Email Service (Amazon SES). I would like to store these notifications in an Amazon DynamoDB database using AWS Lambda. How can I accomplish this?

I have configured Amazon SES Notifications Through Amazon SNS and would like to use an AWS Lambda function to write these notifications to a DynamoDB database. I would like to be able to download these notifications from the DynamoDB database to a CSV file.

Note
The Lambda function created in this sample can be used as a template for writing data to a CRM or other destinations based on where you host your mailing list.

Complete each of the steps summarized below to use an AWS Lambda function to store SNS notification contents for SES to a DynamoDB database:

Prerequisites

  • An SES account (email or domain) that is verified and has an SNS topic configured to receive SES notifications. For more information about using SNS to receive SES notifications, see Amazon SES Notifications Through Amazon SNS.
  • Read and write access to a DynamoDB database.

Complete these steps

  1. Create a DynamoDB table
  2. Customize the Lambda template for SES bounce notifications
  3. Add permissions for the Lambda IAM role to write to the DynamoDB table
  4. Prepare the Lambda function for invocation by SNS
  5. Add one or more SNS topics as the event source for the Lambda function
  6. Test by sending an SES message to invoke the Lambda function
  7. Download a report from DynamoDB to view SES notifications

Begin by creating a table in DynamoDB named SESNotifications with a primary hash key named SESMessageId and a primary range key named SnsPublishTime.

To query the table and generate an SES report, you need the notification type and subtype listed in the following table. Additional fields can be added as necessary.

  Index name

  Hash key

  Range key

SESMessageType-index

SESMessageId (String)

SnsPublishTime (String)

SESMessageSubType-index

SESMessageId (String)

SnsPublishTime (String)

For information about creating a table in DynamoDB, see Getting Started with Amazon DynamoDB.

Use the AWS Lambda console to create a Lambda function named sesnotificationscode.

The following sample code should be entered as a new function that is based on the sns-message blueprint, to help ensure that you choose an Event source type of SNS and the SNS topic that you have already created per the prerequisites. This sample code checks for the 3 types of SNS notifications described at Amazon SNS Notification Examples for Amazon SES.

Note
Replace the value of the TableName parameter (SESNotifications) in the following sample code with the name of your table.

--------------------------Lambda Code Begins------------------------

console.log('Loading event');

var aws = require('aws-sdk');

var ddb = new aws.DynamoDB({params: {TableName: 'SESNotifications'}});

exports.handler = function(event, context)

{

  console.log('Received event:', JSON.stringify(event, null, 2));

  var SnsPublishTime = event.Records[0].Sns.Timestamp;

  var SnsTopicArn = event.Records[0].Sns.TopicArn;

  var SESMessage = event.Records[0].Sns.Message;

  SESMessage = JSON.parse(SESMessage);

  var SESMessageType = SESMessage.notificationType;

  var SESMessageId = SESMessage.mail.messageId;

  var SESDestinationAddress = SESMessage.mail.destination.toString();

  var LambdaReceiveTime = new Date().toString();

  if (SESMessageType == 'Bounce')

  {

  var SESreportingMTA = SESMessage.bounce.reportingMTA;

  var SESbounceSummary = JSON.stringify(SESMessage.bounce.bouncedRecipients);

  var itemParams = {Item: {SESMessageId: {S: SESMessageId}, SnsPublishTime: {S: SnsPublishTime},

  SESreportingMTA: {S: SESreportingMTA}, SESDestinationAddress: {S: SESDestinationAddress}, SESbounceSummary: {S: SESbounceSummary},

  SESMessageType: {S: SESMessageType}}};

ddb.putItem(itemParams, function(err, data)

{

  if(err) { context.fail(err)}

  else {

           console.log(data);

           context.succeed();

      }

  });

}

else if (SESMessageType == 'Delivery')

{

  var SESsmtpResponse1 = SESMessage.delivery.smtpResponse;

  var SESreportingMTA1 = SESMessage.delivery.reportingMTA;

  var itemParamsdel = {Item: {SESMessageId: {S: SESMessageId}, SnsPublishTime: {S: SnsPublishTime}, SESsmtpResponse: {S: SESsmtpResponse1},

  SESreportingMTA: {S: SESreportingMTA1},

  SESDestinationAddress: {S: SESDestinationAddress }, SESMessageType: {S: SESMessageType}}};

  ddb.putItem(itemParamsdel, function(err, data)

{

  if(err) { context.fail(err)}

  else {

          console.log(data);

          context.succeed();

      }

  });

}

else if (SESMessageType == 'Complaint')

{

var SESComplaintFeedbackType = SESMessage.complaintFeedbackType;

var SESFeedbackId = SESMessage.feedbackId;

var itemParamscomp = {Item: {SESMessageId: {S: SESMessageId}, SnsPublishTime: {S: SnsPublishTime}, SESComplaintFeedbackType: {S: SESComplaintFeedbackType},

SESFeedbackId: {S: SESFeedbackId},

SESDestinationAddress: {S: SESDestinationAddress }, SESMessageType: {S: SESMessageType}}};

ddb.putItem(itemParamscomp, function(err, data)

{

  if(err) { context.fail(err)}

  else {

          console.log(data);

          context.succeed();

      }

  });

}

};

------------------------Lambda Code Ends----------------------------

The role used for the Lambda function in this sample is "role/lambda_ses_execution" rather than the default role, lambda_exec_role.

Note
IAM best practices are to create and use a role for each Lambda function rather than sharing the default 'lambda_exec_role'. For more information, see Delegate by using roles instead of by sharing credentials.

The following example policy is added to the lambda_ses_execution role to grant access to the DynamoDB database us-east-1:12345678912:table/SESNotifications created in Step 1:

------------------------IAM Policy Begins---------------------------

{

    "Version": "2012-10-17",

    "Statement": [

         {

            "Sid": "Stmt1428510662000",

            "Effect": "Allow",

            "Action": [

                "DynamoDB:*"

            ],

            "Resource": [

                "arn:aws:DynamoDB:us-east-1:12345678912:table/SESNotifications"

            ]

        }

    ]

}

------------------------IAM Policy Ends-----------------------------

After the Lambda function is created, assign a policy document to the Lambda function to allow it to be invoked by SNS. This is done with the AddPermission API, which takes a policy document as an argument. Here is an example policy document for the Lambda function sesnotificationscode and an SNS topic named ses_notifications_repo:

Note
If you are using the AWS Lambda console, a policy is automatically assigned and you do not need to perform this step.

Note
For more information about using the AddPermission API to add permissions to a resource policy of a Lambda function, see Granting Permissions Using a Resource Policy.

------------------------Lambda Policy Begins------------------------

{

  "Statement":[

    {"Condition":

      {"ArnLike":{"AWS:SourceArn":"arn:aws:sns:us-east-1:123456789012:ses_notifications_repo"}},

      "Resource":"arn:aws:lambda:us-east-1:123456789023:function:sesnotificationscode",

      "Action":"lambda:invokeFunction",

      "Principal":{"Service":"sns.amazonaws.com"},

      "Sid":"sns invoke","Effect":"Allow"

     }],

  "Id":"default",

  "Version":"2012-10-17"

}

------------------------Lambda Policy Ends--------------------------

This article assumes that you have already created at least one SNS topic and have configured an SES email domain to use that SNS topic for feedback notifications.

  1. In the SNS console navigation pane, choose Topics. Identify the SNS topic that is used in SES for bounces. In this example, we use an SNS topic named ses_notifications_repo.
  2. Choose the ARN of the SNS topic to open the Topic Details page.
  3. Choose Create Subscription.
  4. For Protocol, choose AWS Lambda.
  5. For Endpoint, type the ARN of the Lambda function, and then choose Create Subscription.

This creates a subscription with Lambda as the protocol and the Lambda function ARN as the endpoint.

Now that the Lambda function is subscribed to an SNS topic, the next step is to publish an SES message and observe the invocation of the Lambda function in the Lambda console. You can use the Amazon SES console to send a test message. You can also use the AWS CLI send-email command for this purpose.

After the message is published to the SNS topic, the topic attempts to deliver the message to the subscribed Lambda function. This invokes the function and passes the message as a JSON-formatted SNSEvent input parameter. For sample JSON-formatted SNSEvent input parameters that describe SNS bounce notifications, complaint notifications, and delivery notifications, see Amazon SNS Notification Examples.

It is easy to query, sort, and download the contents of the table as a .CSV file by using the DynamoDB console. For more information, see the links at What Is Amazon DynamoDB? You can also use AWS Data Pipeline to schedule a download of the file to an S3 bucket at regular intervals for compliance and record keeping. For more information, see Export DynamoDB Table to S3 and Part Two: Export Data from DynamoDB.


Did this page help you? Yes | No

Back to the AWS Support Knowledge Center

Need help? Visit the AWS Support Center

Published: 2016-03-11