この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回はSESで受信したメールをS3に保存し、Lambdaでメールを振り分けてS3バケットの対象フォルダに移動(コピー)するというところまでの処理を先日サポートされた Python3.6でやってみました。
Announcement: AWS Lambda now supports Python 3.6
やりたいこと
SESで受信したEC2のリタイアメントやメンテナンスのメールをS3のフォルダに振り分ける。 その他のメールは削除。
- EC2のリタイアメント通知の件名 [Retirement Notification] Amazon EC2 Instance scheduled for retirement.
- EC2のメンテナンス通知の件名 Amazon EC2 Maintenance - Maintenance [AWS Account: 012345678910]
処理の流れ
- SESで受信したメールをS3バケットに保存する
- S3をトリガーにLambda Functionが起動する
- Lambdaによってメールを対象フォルダに振り分ける
S3バケットの用意
SESでメールを受信した際にメールを保存するS3バケットを用意します。 振り分けようのフォルダも作成しました。
S3 Bucket
SESでメール受信設定
SESでのメール受信の方法は以下ブログを参考に設定します。
Rule SetsはS3 bucketの「ses/」にメールが入るようにしました。
Lambdaの設定
トリガーはS3(put) → Lambda
処理の内容
- EC2のリタイアメント通知は「ec2_retirement」フォルダへコピーしコピー元のメールは削除
- EC2のメンテナンス通知は「ec2_maintenance」フォルダへコピーしコピー元のメールは削除
- その他のメールは削除
コード
# -*- coding: utf-8 -*-
from __future__ import print_function
import enum
from enum import Enum
import boto3
import json
import urllib.parse
import re
import email
from email.parser import FeedParser
from email.header import decode_header
import time
# S3 高レベルAPI
s3 = boto3.resource('s3')
# リトライ回数
RETRY_COUNT = 3
# 振り分けルールが増えた場合、追記していく
# 関数 check_email_subject 、execute_handler にも追加
class EMAIL_TYPE(Enum):
RETIREMENT = 1
MAINTENANCE = 2
# メールの件名を取得
def get_email_subject(email_object):
(subject, subject_charaset) = decode_header(email_object['Subject'])[0]
if subject_charaset == None:
email_subject = subject
else:
email_subject = subject.decode(subject_charaset)
print("Email Subject: %s" % email_subject)
return email_subject
# メールオブジェクトをコピー(移動)
def copy_folder(s3_new_folder, s3_object_name, s3_file_key, bucket, key):
# コピー先のファイル名
new_file = '%s/%s' % (s3_new_folder, s3_object_name)
print(new_file)
for i in range(1, 1+RETRY_COUNT):
obj_copy = s3.Object(bucket, new_file).copy_from(CopySource={'Bucket': bucket, 'Key': s3_file_key})
if obj_copy['ResponseMetadata']['HTTPStatusCode'] == 200:
print("copy %s to %s complete" % (s3_object_name, s3_new_folder))
break
else:
print(obj_copy['ResponseMetadata']['HTTPStatusCode'])
time.sleep(i)
else:
raise Exception('object copy failed')
# メールオブジェクトを削除
def delete_original_object(s3_object, s3_file_key):
for i in range(1, 1+RETRY_COUNT):
s3_obj_delete = s3_object.delete()
print(s3_obj_delete)
if s3_obj_delete['ResponseMetadata']['HTTPStatusCode'] == 204:
print("delete %s complete" % s3_file_key)
break
else:
print(s3_obj_delete['ResponseMetadata']['HTTPStatusCode'])
time.sleep(i)
else:
raise Exception('object delete failed')
def check_email_subject(email_subject):
# リタイアメント
chk_ec2_retirement = r'\[Retirement Notification\] Amazon EC2 Instance scheduled for retirement.'
# メンテナンス(リブート)
chk_ec2_maintenance = r'Amazon EC2 Maintenance - Maintenance \[AWS Account: \d{12}\]'
# サブジェクトの判定
# 振り分けルールが増えた場合、追記していく
if re.search(chk_ec2_retirement, email_subject):
return EMAIL_TYPE.RETIREMENT
elif re.search(chk_ec2_maintenance, email_subject):
return EMAIL_TYPE.MAINTENANCE
def execute_handler(bucket, key):
# オブジェクトを取得する
s3_object = s3.Object(bucket, key)
# オブジェクトのkey取得
s3_file_key = s3_object.key
# オブジェクトのkeyからファイル名を取得
s3_object_name = s3_file_key.split("/")[-1]
# オブジェクトの内容を取得
s3_object_response = s3_object.get()
# オブジェクトのBodyを取得
email_body = s3_object_response['Body'].read().decode('utf-8')
email_object = email.message_from_string(email_body)
# メールの情報を取得する
# サブジェクト
email_subject = get_email_subject(email_object)
# メールのサブジェクト判別
email_type = check_email_subject(email_subject)
# サブジェクト分別 対象メールが増えたら elif を追加する
# リタイアメント
if email_type == EMAIL_TYPE.RETIREMENT:
copy_folder('ec2_retirement', s3_object_name, s3_file_key, bucket, key)
# リブート(メンテナンス)
elif email_type == EMAIL_TYPE.MAINTENANCE:
copy_folder('ec2_maintenance', s3_object_name, s3_file_key, bucket, key)
else:
print('Non-target Email')
#ファイルの削除
delete_original_object(s3_object, s3_file_key)
def lambda_handler(event, context):
# バケット名取得
bucket = event['Records'][0]['s3']['bucket']['name']
# オブジェクトのkey取得
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'])
print(key)
# メイン
execute_handler(bucket, key)
テスト
以下、件名のメールを送信
- [Retirement Notification] Amazon EC2 Instance scheduled for retirement.
- Amazon EC2 Maintenance - Maintenance [AWS Account: 012345678910]
- テスト
CloudWatch Logsで確認
期待通りの挙動になっていることを確認
S3で振り分けができたか確認
- ec2_retirementフォルダ
- ec2_maintenanceフォルダ
- sesフォルダ
受信したメールが対象のフォルダに振り分けられていて、元のメールは削除されていることが確認できます。 今回は受信したメールを削除しましたが、S3のライフサイクルで一定期間は保存してもいいと思います。
まとめ
今回はPythonの勉強も兼ねて、SESで受信したメールをLambdaで選別してS3のフォルダに分けるところまでやってみました。 S3のフォルダに対象のメールが入ったらS3 EventsでSNS通知したり、 Lambda 関数を呼び出したりできるのでメール毎にアクションを決めることができると思います。