Edited at

SES で受信したメールを Lambda で処理してみた

More than 3 years have passed since last update.

この記事は今年もやるよ!AWS Lambda縛り Advent Calendar 2015の12/12日分の記事です。

Amazon SES のメール受信機能からの Lambda 連携をやったことがなかったので、ちょっとした処理をしてみました。


処理の内容

特定の人からメールを受信した時にツイッター等で通知したいことがありますよね?(ないですか、そうですか)

今までは単純に IFTTT で Gmail と Twitter をつなげてやっていましたが、何回目とか何日ぶりかを含めたツイートにしたかったので、Lambda で作ることにしました。


構成

当初 Gmail -> IFTTT -> Maker.ch -(HTTP POST)-> API Gateway -> Lambda -(HTTP POST)-> Maker.ch -> IFTTT -> Twitter と考えていましたが、なぜか Maker channel の REST request が API Gateway を呼び出せず(憶測ですが、SNI非対応?)、ふと SES でメール受信できたなーと思って使ってみる事にしました。

最終的な構成は以下のような感じです。

構成図

※流行りのCLOUDCRAFTで描いてみました(余談ですけど、このサイト AWS じゃなくて Digital Oceanで動いてるんですね)。


セットアップ

大まかな流れは、


  1. IFTTT に Recipe を作成

  2. Lambda ファンクションを仮作成(受信したメールが確認出来る様に)

  3. SES の Rule 作成

  4. (上記の流れで) Rouet53 に SES認証用のレコードと MX レコードを設定

  5. Gmail から メール転送を設定

  6. DynamoDB Table を作成

  7. Lambda ファンクションを本番コードで更新

といった順番になります。


IFTTT に Recipe を作成


  • レシピ作成で、this に Maker 、That を Twitter で作成します

  • Maker の Event Name には、"twitter" と指定しました

  • Twitter の Action には、"Post a tweet" を選択し、Tweet text には、

{{Value1}}からメール着信、{{Value2}}日ぶり {{Value3}}回目

と入れました

作成が終わったら、Maker Channel の設定画面から、テスト実行を行う事が出来ます。

ifttt_16.png

実際に Twitter に下記のようにツイートされました。

ifttt_17.png


Lambda ファンクション仮作成

後ほど設定する Gmail のメール転送設定のために、先に Lambda の Function を作ってしまいます。

SESのリージョンと合わせる必要があるので、us-east-1 に作成しました。名前は ses としました。

exports.handler = function(event, context) {

console.log(JSON.stringify(event));
context.succeed(event);
};

入ってきた event object をそのままダンプするだけのスクリプトですので、特に解説はいらないと思います。


SESのルール作成 & Route53設定

SES のコンソールの左下から、Email Receiving > Rules Sets をクリックし、Create a Receipt Rule をクリック。

ses_01.png

次にメールを受信する対象のドメインまたはアドレスを指定します。今回は jaws.ninja ドメイン宛のメール全てを受け付ける設定とします。

ドメインの認証を行う必要がある場合には、下記のように SES から Route53 の Hosted Zone に必要なレコードを追加してくれます。便利ですねー。

ses_02.png

次に、実行する Action として、先ほど作成した Lambda Function を指定します。

ses_03.png

あとはデフォルトのまま進めて、作成完了です。


Gmailの転送設定


転送先アドレスの追加

まず転送が出来るように、転送先アドレスを追加します。


  • Gmail の設定の「メール転送とPOP/IMAP」タブから、「転送先アドレスを追加」を押します。

  • アドレスは適当に、lambda@jaws.ninja などとします

  • 転送確認のためのメールが、SES を通じて lambda に渡されるので、CloudWatch logs から受け取った内容を確認します

gmail_01.png

subject のところに、確認用コードが書いてあるので、Gmail の方に入力します。

これで転送先アドレスの追加は完了です。


フィルタの設定

特定のアドレスから来たメールが、先ほど追加したアドレスに転送されるように設定します。

まずは抽出条件を指定し、

gmail_02.png

次に処理の内容(先ほど追加したアドレスへの転送)を指定し、フィルタを作成します。

gmail_03.png

以上で Gmail 側の設定は完了です。


DynamoDB の設定

今回は単純に、メールアドレス を Partition Key とするテーブルを作成します。

てっとり早くCLIで作成しました。

$ aws dynamodb create-table --table-name email-count ¥

--attribute-definitions '[{"AttributeName":"email", "AttributeType":"S"}]' ¥
--key-schema '[{"AttributeName":"email","KeyType":"HASH"}]' ¥
--provisioned-throughput '{"ReadCapacityUnits":1, "WriteCapacityUnits":1}'
{
"TableDescription": {
"TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/email-count",
"AttributeDefinitions": [
{
"AttributeName": "email",
"AttributeType": "S"
}
],
"ProvisionedThroughput": {
"NumberOfDecreasesToday": 0,
"WriteCapacityUnits": 1,
"ReadCapacityUnits": 1
},
"TableSizeBytes": 0,
"TableName": "email-count",
"TableStatus": "CREATING",
"KeySchema": [
{
"KeyType": "HASH",
"AttributeName": "email"
}
],
"ItemCount": 0,
"CreationDateTime": 1449823894.715
}
}

また、受信をカウントしたいメールアドレスの Item を作成しておく必要があります。下記のようなアイテムを作成しておきました。

{

"count": 2,
"email": "moto@example.com",
"name": "moto",
"unixtime": 1449829080
}


Lambda ファンクションを再作成

Python で Function を同じ名前で再作成します。

処理の内容は、


  • 受け取ったメールの from アドレスをキーとして DynamoDB の Item を Update します

  • その際、カウントは ADD Action でアトミックに追加します

  • 変更前の unixtime を取得し、現在時刻との差分から、何日ぶりかを計算します

  • IFTTT の Maker channel の API エンドポイントに JSON 形式で POST をします

のような流れです。

当然ですが DynamoDB のテーブルへの UpdateItem 権限が必要ですので、適切に Role を設定してください。

# -*- coding: utf-8 -*-

import boto3
import json
import re
import urllib2
import time

# 使い回すオブジェクトはハンドラ外で定義しましょう
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('email-count')
url = 'https://maker.ifttt.com/trigger/twitter/with/key/KEY'

def lambda_handler(event, context):
# メアドを event オブジェクトから取得
email = event['Records'][0]['ses']['mail']['source']
unixtime = int(time.time())

# update_item で、count をインクリメント、変更前のオブジェクトを取得
response = table.update_item(
Key={'email': email},
AttributeUpdates={'count': {'Value': 1,'Action': 'ADD'}, 'unixtime':{'Value': unixtime} },
Expected={'email':{'ComparisonOperator':'NOT_NULL'}},
ReturnValues='ALL_OLD'
)
print response

# 得られたデータから、IFTTT に渡すパラメータを決定
name = response['Attributes']['name']
count = int(response['Attributes']['count'])+1
days = int( (unixtime - response['Attributes']['unixtime']) / (60*60*24))
payload = json.dumps({'value1':name, 'value2':str(days), 'value3':str(count)})
print payload

# HTTPS POSTでリクエストを送る
req = urllib2.Request(url, payload, {'Content-Type': 'application/json'})
f = urllib2.urlopen(req)
response = f.read()
f.close()
return response


デモ

実際にメールを送ってみると...

自動でツイートされました!


まとめ

メールベースで何か処理をしたいというケースは結構あると思いますので、これは新しいクラウドデザインパターンになりえるのではないかなーと思います。