AWS SESを使って独自ドメインのメールアドレスの受信と転送設定を行う
はじめに
(システムのテストなどで)メールアドレスが大量に必要になった時に、AWS SESは1つの解になるかも知れません。
ドメインの取得
- freenomなどで取得しても良いですし、Route53で直接取得しても良いです。
- 既に、Route53を使って、ドメインは取得(設定)済みとして進めます。
- 今回は、example.comと言うドメインを取得したことにします。
S3のバケットの取得
SESの設定からもバケットは作成することができますが、今回は予め作成しておきます。
- S3のコンソールから、「バケットを作成」を押して、好きな名前をつけます。
- SESのリージョンとS3のリージョンを一致させる必要はありません。
- SESは、受信機能の制限によりオレゴンを選択していますが、S3は、東京リージョンを選択しました。
- 作成したS3のバケットには、以下のバケットポリシーを設定しておきます。
- 自分のアカウントのSESからのputObject操作を受けつけると言う意味です。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowSESPuts-1660060428208", "Effect": "Allow", "Principal": { "Service": "ses.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::[ここにバケット名]/*", "Condition": { "StringEquals": { "AWS:SourceAccount": "[ここにアカウントの番号]" }, "StringLike": { "AWS:SourceArn": "arn:aws:ses:*" } } } ] }
AWS SESの設定
(注意) メールを受信できるリージョンは、限られています。残念ながら22年8月12日現在では、東京リージョンは対象外でした。
詳細は、以下の「E メールの受信」の章をご確認ください。
docs.aws.amazon.com
リージョン選択
今回は、オレゴンで設定していきます。
- 右上のリージョン選択で、米国西部(オレゴン)を選びます。
- 左の「Configuration」メニューに「Email receiving」のメニューがあることを確認しておきます。
受信ルールの設定
次に、受信ルールを設定します。
- 左の「Configuration」メニューから「Email receiving」を選択します。
- リージョンが非対応の場合は、表示されていません。
- 「Create rule set」ボタンを押します。
- 「Create rule」 ボタンを押します。
- Rule nameは、なんでも良いので、自分にとってわかりやすい名前をつけて、「Next」ボタンを押します。
- 「Add new recipient condition」ボタンを押して、利用したいメールアドレスを入力します。
- ドメイン部分は、example.comを使うので、例えば、「hogehoge@example.com」などとします。
- 次ページでは、「Add new action」から「Deliver to S3 bucket」を選択します。
- S3 bucketに上で、バケットポリシーを設定したバケットを選択します。(バケットポリシーが間違っているとここで警告を受けます。)
- prefixは、お好みで。
- 次ページで、最終確認をして、画面下部の「Create rule」ボタンを押してルールを作成します。
ルールを作った後は、activateします。
- 先ほど作成したルールを選択して、「Set as active」ボタンを押します。
MXレコードの設定
- Route53のMXレコードに、以下のリンクで指定されているメールサーバを指定します。
(受信の場合、CUSTOM MAIL FROMは特に設定する必要はありません。送信の場合でも、MAIL FROMが気にならないのであれば、設定する必要はありません。)
転送設定
毎回、直接s3のファイルを読みに行くのは面倒なので、転送設定をしておきます。
Lambda関数の作成
- SESの受信設定を行なったのと同じリージョンで、Lambda関数を作成します。
Lambda関数の設定ロール
最低限、以下の権限を付与したロールを作成します。
- S3のオブジェクトの読み込み (今回、SESで受信したメールをS3へ書き込むので、読み出す権限が必要になる)
- SESを用いた送信 (メールを転送するためです)
具体的には、以下の様に設定します。
S3のポリシー
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::[バケット名]/[プレフィックス]/*" } ] }
SESのポリシー
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ses:SendRawEmail", "ses:SendEmail" ], "Resource": "arn:aws:ses:us-west-2:[アカウントID]:identity/*" } ] }
※ses:SendRawEmailの方は、使わなければはずしてください(以下のlambda関数では使っていません。)
Lambda関数の内容
ざっくり以下の内容です。
「バケット名」「プレフィックス」「転送先のメールアドレス」となっている部分は、ご自身の内容に置き換えてください。
gmailへ転送してうまくいくことは確認しました。
lambda関数にライブラリを追加しなくて済む様に、デフォルトで入っているemailライブラリを使っています。
import json import boto3 from botocore.exceptions import ClientError from email import policy from email.parser import BytesParser def parse_eml(eml_data): try : msg = BytesParser(policy=policy.default).parsebytes(eml_data) charset = msg.get_body(preferencelist=("plain","html"))["content-type"].params["charset"] # parse text body text_content,text_charset = None,None text_body = msg.get_body(preferencelist=("plain")) if text_body is not None: text_content = text_body.get_content() text_charset = text_body["content-type"].params["charset"] # parse html body html_content,html_charset = None,None html_body = msg.get_body(preferencelist=("html")) if html_body is not None: html_content = html_body.get_content() html_charset = html_body["content-type"].params["charset"] return { "to" : msg["to"], "subject" : msg["subject"], "charset" : charset, "text_body" : {"Data":text_content,"Charset":text_charset} if text_body is not None else None, "html_body" : {"Data":html_content,"Charset":html_charset} if html_body is not None else None } # TODO : handle error except Exception as e: return None def build_body(parsed_data): body = { "Text" : parsed_data["text_body"], "Html" : parsed_data["html_body"] } if parsed_data["text_body"] is None: del body["Text"] if parsed_data["html_body"] is None: del body["Html"] return body def lambda_handler(event, context): # S3 params BUCKET_NAME = "バケット名" PREFIX = "プレフィックス" # SES params RECIPIENT = "転送先のメールアドレス" ERROR_CODE = { 'statusCode' : 500, 'body' : json.dumps('Internal Server Error') } try : s3_client = boto3.client("s3") ses_client = boto3.client("ses") for item in event["Records"]: source = item["ses"]["mail"]["source"] message_id = item["ses"]["mail"]["messageId"] s3_key = f"{PREFIX}/{message_id}" s3_response = s3_client.get_object( Bucket = BUCKET_NAME, Key = s3_key ) eml_data = s3_response["Body"].read() parsed_data = parse_eml(eml_data) ses_client.send_email( Source = parsed_data["to"], Destination = { "ToAddresses":[RECIPIENT] }, Message={ 'Subject': { 'Data': f'Fw: {parsed_data["subject"]}', 'Charset': parsed_data["charset"] }, 'Body': build_body(parsed_data) } ) break # TODO : Handle Error except KeyError: return ERROR_CODE except ClientError: return ERROR_CODE else: return { 'statusCode': 200, 'body': json.dumps('OK') }