Amazon SESの通知結果をAmazon SNS、Lambdaを使用してDynamoDBに保存する


AmazonSESの送信結果を取得するために、今回はAmazonSNSをトリガーとして、LambdaからDyanamoDBに保存する仕組みを作っていきたいと思います。

この方法はAWSのサポートでもありましたので、今回はそれを試してみます。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-sns-ses-dynamodb/

前提として、AmazonSESに関しては既に設定済としています。

こちら、ブログでも公開しております。

DynamoDBの設定

まずは、DynamoDBのコンソールからテーブルを「SESNotifiations」という名前で作成します。

  • テーブル名に「SESNotifications
  • パーティションキーに「SESMessageId
  • ソートキーに「SESMailSendTime」

LambdaのIAMロールを設定

IAMロールの作成

次にLambda関数に付与する権限を作成するために、IAMコンソールからロールを作成していきます。

  • 信頼されたエンティティの種類から「AWSサービス」を選択
  • ユースケースの選択から「Lambda」を選択

Attachアクセス権限ポリシー

ロールにアタッチするポリシーを選択します。

  • 「AWSLambdaBasicExecutionRole」を選択

タグの追加

今回はタグ無しで次へ

確認

ロール名を「lambda-ses-execution」としてロールを作成します。

インラインポリシーの追加

作成したロールを選択して、DynamoDBへアクセスできるようにインラインポリシーを追加していきます。

ロールの一覧から「lambda-ses-execution」を選択して、「インラインポリシーの追加」をクリックします。

ポリシーの作成

DynamoDBへの書き込み権限を与えるために「ビジュアルエディタ」から権限を設定していきます。

  • サービスから「DyanamoDB」を選択
  • アクションから「書き込み」の「PutItem」を選択します
  • リソースの「ARNの追加」をクリックします。
  • 「ARNの追加」から先程作成したDynamoDBの「ARN」を設定します。

Lambda関数

それでは、実際に関数を作成していきます。ここでは、先程IAMで作成したロールを選択します。

  • 関数名に「sesnotificationscode」
  • ランタイムは「Node.js 12.x」を選択
  • 実行ロールは、先程作成した「lambda-ses-execution」を選択する

コード

SES通知の内容には「mailオブジェクト」「bonceオブジェクト」「deliveryオブジェクト」「complaintオブジェクト」があります。
その中身を解析してDynamoDBに保存していきます。

SES通知の内容についてもこちらを参照にして下さい。

https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/notification-contents.html

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 SESMessage = event.Records[0].Sns.Message
  SESMessage = JSON.parse(SESMessage);
  
  var SESMessageId = SESMessage.mail.messageId;
  var SESMailSendTime = SESMessage.mail.timestamp;
  var SESMessageType = SESMessage.notificationType;
  var SESMailObject = JSON.stringify(SESMessage.mail);
  var SESMailAddress = SESMessage.mail.destination.toString();
  
  var LambdaReceiveTime = new Date().toString();
  if (SESMessageType == 'Bounce')
  {
    var SESBounceObject = JSON.stringify(SESMessage.bounce);
    var SESbounceType = SESMessage.bounce.bounceType;
    var SESbounceSubType = SESMessage.bounce.bounceSubType;
    var SESBounceFeedbackId = SESMessage.bounce.feedbackId;
    
    var itemParams = {
      Item: {
        SESMessageId: {S: SESMessageId}, 
        SESMailSendTime: {S: SESMailSendTime},
        SESMailAddress: {S: SESMailAddress}, 
        SESMessageType: {S: SESMessageType},
        
        SESBounceObject: {S: SESBounceObject},
        SESbounceType: {S: SESbounceType},
        SESbounceSubType: {S: SESbounceSubType},
        SESBounceFeedbackId: {S: SESBounceFeedbackId}
      }};
    ddb.putItem(itemParams, function(err, data)
    {
      if(err) { context.fail(err)}
      else {
           console.log(data);
           context.succeed();
      }
    });
  }
  else if (SESMessageType == 'Delivery')
  {
    var SESDeliveryObject = JSON.stringify(SESMessage.delivery);
    var SESDeliveryTime = SESMessage.delivery.timestamp;
    var SESProcessingTimeMillis = SESMessage.delivery.processingTimeMillis.toString();
    
    var itemParamsdel = {
      Item: {
        SESMessageId: {S: SESMessageId}, 
        SESMailSendTime: {S: SESMailSendTime}, 
        SESMailAddress: {S: SESMailAddress }, 
        SESMessageType: {S: SESMessageType},
        
        SESDeliveryObject: {S: SESDeliveryObject},
        SESProcessingTimeMillis: {N: SESProcessingTimeMillis},
        SESDeliveryTime: {S: SESDeliveryTime}
    }};
    ddb.putItem(itemParamsdel, function(err, data)
    {
      if(err) { context.fail(err)}
      else {
          console.log(data);
          context.succeed();
      }
    });
  }
  else if (SESMessageType == 'Complaint')
  {
    var SESComplaintObject = JSON.stringify(SESMessage.complaint);
    
    var SESComplaintFeedbackType = SESMessage.complaint.complaintFeedbackType;
    var SESComplaintFeedbackId = SESMessage.complaint.feedbackId;
    var itemParamscomp = {
      Item: {
        SESMessageId: {S: SESMessageId}, 
        SESMailSendTime: {S: SESMailSendTime}, 
        SESComplaintFeedbackType: {S: SESComplaintFeedbackType},
        SESComplaintFeedbackId: {S: SESComplaintFeedbackId},
        SESMailAddress: {S: SESMailAddress }, 
        SESMessageType: {S: SESMessageType},
        
        SESComplaintObject: {S: SESComplaintObject}
      }};
    ddb.putItem(itemParamscomp, function(err, data)
    {
      if(err) { context.fail(err)}
      else {
          console.log(data);
          context.succeed();
      }
    });
  }
};

SNSトピック

SNSトピックの作成

続いて、Lambda関数のトリガーとなるSNSトピックを「ses_notifications_repo」という名称で作成していきます。

トリガーの設定

Lambdaコンソールの「トリガーを追加」から、先程作成したSNSのトピックを選択すると簡単に設定がされます。

SES Notificationの設定

最後にSESからSNSのトピックを設定していきます。

SESコンソールのEamail Addressで今回対象となるメール選択して、NotificationsからSNSトピックの「ses_notifications_repo」を選択します。Bounes、Complaints、Deliveriesのそれぞれで設定します。

これで、SESから送信したメールの通知結果を、SNSがトリガーとなってLambda関数を実行して、DynamoDBに保存する仕組みができました。

テスト

「Send Test Email」からテスト配信をして、DynamoDBに保存されるか確認していきます。

toに宛先を指定すると確認することができます。

https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-simulator.html