読者です 読者をやめる 読者になる 読者になる

簡易コメントシステムからのメール通知 w/ DynamoDB streams + Lambda + SES

はい、ネタも尽きかけた今日このごろ。捻出したのは、5日目に書いたネタの拡張です。

5日目はS3(Hosting) + Cognito(AWSリソースへの時限式アクセス権の提供) + DynamoDB(Storage)と言った構成でした。これをさらに拡張して、コメント投稿があった場合には管理者にメールで通知するというものです。この拡張部分に利用するのがDynamoDBの更新イベントなどを通知するDynamoDB Streamsとメール配信サービスのSESです(どこからともなくSESではなくSendGrid*1を使ってくださいと言われそうですが、時間に限りがあるので今回はSESです)。

人間というのは面倒くさがりなので、Pushで更新通知などが無いとなかなか更新があったことに気づかないものです。そんな人でも、サーバレスで簡単に通知システムを組むことができちゃうので、安心してサイト運営ができます(サイト運営と言うとちょっとだいぶ言い過ぎですね)。

Lambda Functionの枠を用意する

今回もまずはLambda Functionの枠だけ用意して、Eventとしてどんな構造のものが来るのかを確認できるようにしましょう。BluePrintから「dynamodb-process-stream-python」を選択します。

f:id:matetsu:20151209003420p:plain

扱うデータも頻繁に更新もされませんし、更新された順に通知してくれればいよいので、Batch Sizeは「1」、Starting Positionは「Trim horizon」にします。

f:id:matetsu:20151209004059p:plain

Lambda Functionはとりあえずはサンプルのままにしておきます。

このLambda Functionで利用するIAM Roleはdynamodbを扱える必要があるのと、SESからメールを送信できる必要があるので以下のようにします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetRecords",
        "dynamodb:GetShardIterator",
        "dynamodb:DescribeStream",
        "dynamodb:ListStreams",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail"
      ],
      "Resource": "*"
    }
  ]
}

これは、「DynamoDB event stream role」というサンプルロールにSESへの権限を追加したものになります。サンプルは「role」のところから、こんな感じで選ぶ。

f:id:matetsu:20151209005220p:plain

あとは、Timeoutを10秒位にしておいて、有効にしつつ作成完了。

DynamoDBのトリガ設定

DynamoDB StreamsとLambdaを結びつけるために、トリガの設定をします。対象のテーブルでトリガを作成すると自動的にStreamが有効になります。

f:id:matetsu:20151209005712p:plain

StreamからくるLambdaのEvent内容を確認

DynamoDBに新規アイテムを追加して、どのようなEventが来るのかを確認します。適当にコメントを追記して、CloudWatchLogsを確認します。

f:id:matetsu:20151209010325p:plain

jsonの構造がわかりましたので、実際にSESで投稿内容をお知らせするLambda Functionを作成しましょう。なお、SESの設定は省略します。

Lambda Functionの修正

こんな感じ。

# coding: utf-8
import boto3
import json
import datetime

print('Loading function')


def lambda_handler(event, context):
    record = event['Records'][0]
    ses = boto3.client('ses', region_name='us-east-1')
    response = ses.send_email(
        Source = "matetsu@gmail.com",
        Destination = {
            "ToAddresses": ["matetsu@gmail.com"],
            },
        Message = {
            "Subject": {
                "Data": u"コメント投稿がありました"
                },
            "Body": {
                "Text": {
                    "Data": u"""コメント投稿がありました。
                    
                    Article: %s
                    Timestamp: %s
                    Comment: %s
                    """ % (record['dynamodb']['NewImage']['article']['S'], datetime.datetime.fromtimestamp(int(record['dynamodb']['NewImage']['timestamp']['N'])), record['dynamodb']['NewImage']['data']['S'])
                    }
                }
            }
        )

    return "Sent message id: %s" % response['MessageId']

コメントをして、メールが来るか確認

こんな投稿をしてみると。。。

f:id:matetsu:20151209015552p:plain

こんなメールが届きます。

f:id:matetsu:20151209015614p:plain

おわりに

はい、このネタもパクリやんかと言われそうですが、まあパクリと言ってしまえばパクリです。ネタがないんです。さらに、ほとんどはブログの会社さんが書いてしまっているんです。

いいわけでした。

ということで、なんとか8日目も終わりました。また明日、どんなネタが飛び出すか、お楽しみに!