この記事は今年もやるよ!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で動いてるんですね)。
セットアップ
大まかな流れは、
- IFTTT に Recipe を作成
- Lambda ファンクションを仮作成(受信したメールが確認出来る様に)
- SES の Rule 作成
- (上記の流れで) Rouet53 に SES認証用のレコードと MX レコードを設定
- Gmail から メール転送を設定
- DynamoDB Table を作成
- Lambda ファンクションを本番コードで更新
といった順番になります。
IFTTT に Recipe を作成
- レシピ作成で、this に Maker 、That を Twitter で作成します
- Maker の Event Name には、"twitter" と指定しました
- Twitter の Action には、"Post a tweet" を選択し、Tweet text には、
{{Value1}}からメール着信、{{Value2}}日ぶり {{Value3}}回目
と入れました
作成が終わったら、Maker Channel の設定画面から、テスト実行を行う事が出来ます。
実際に Twitter に下記のようにツイートされました。
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 をクリック。
次にメールを受信する対象のドメインまたはアドレスを指定します。今回は jaws.ninja ドメイン宛のメール全てを受け付ける設定とします。
ドメインの認証を行う必要がある場合には、下記のように SES から Route53 の Hosted Zone に必要なレコードを追加してくれます。便利ですねー。
次に、実行する Action として、先ほど作成した Lambda Function を指定します。
あとはデフォルトのまま進めて、作成完了です。
Gmailの転送設定
転送先アドレスの追加
まず転送が出来るように、転送先アドレスを追加します。
- Gmail の設定の「メール転送とPOP/IMAP」タブから、「転送先アドレスを追加」を押します。
- アドレスは適当に、lambda@jaws.ninja などとします
- 転送確認のためのメールが、SES を通じて lambda に渡されるので、CloudWatch logs から受け取った内容を確認します
subject のところに、確認用コードが書いてあるので、Gmail の方に入力します。
これで転送先アドレスの追加は完了です。
フィルタの設定
特定のアドレスから来たメールが、先ほど追加したアドレスに転送されるように設定します。
まずは抽出条件を指定し、
次に処理の内容(先ほど追加したアドレスへの転送)を指定し、フィルタを作成します。
以上で 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
デモ
実際にメールを送ってみると...
moto からメール着信、0 日ぶり、3 回目
— moto (@j3tm0t0) 2015, 12月 11
自動でツイートされました!
まとめ
メールベースで何か処理をしたいというケースは結構あると思いますので、これは新しいクラウドデザインパターンになりえるのではないかなーと思います。