SESをSMTPで利用した時のメモです。Bounce Mailの管理も設定します。
送信先として利用するテスト用Emailアドレスの検証
初回利用開始時、SESはsandbox状態となっています。sandbox状態では、送信先となるEmailアドレスに対して制限をかけられており、事前に対象アドレスを検証する必要があります。その他sandbox状態での影響は、以下を参照。
Moving out of the Amazon SES sandbox
以下、検証作業を実施します。公式ドキュメント。
送信先としたいEmailアドレスを指定して実行します。
$ aws ses verify-email-identity --email-address xxx@gmail.com --region=ap-northeast-1 --profile xxx
該当Emailアドレスに、Verify処理用のメールが届くため、メール本文内のVerify処理をクリック。有効時間は24時間です。
The link in the verification message expires 24 hours after the message was sent. If 24 hours have passed since you received the verification email, repeat steps 1–5 to receive a verification email with a valid link.
検証されたことの確認。
$ aws ses list-identities --identity-type EmailAddress --region ap-northeast-1 --profile xxx { "Identities": [ "xxx@gmail.com" ] }
送信元として利用するドメインの検証
メール送信元とするドメイン情報を検証します。本処理は、非sandbox状態であっても必要となります。以下、公式ドキュメント。
Verifying a domain with Amazon SES
ドメイン検証のコマンドを実行。
$ aws ses verify-domain-identity --domain goodbyegangster.com --profile xxx { "VerificationToken": "RkfXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
aws ses verify-domain-identity
登録されたドメイン(今回の場合は goodbyegangster.com
)のVerification Statusは、 pending vertification
ステータスとして作成されます。
検証処理を実施するため、登録ドメインを管理するDNSサーバーに、以下のTXTレコードを登録します。
Name | Type | Value |
---|---|---|
_amazonses.goodbyegangster.com | TXT | RkfXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
- Name文字列
_amzonses.sample.com
より、sample.com
の部分を利用したいドメイン名に変更した値
- Value文字列
- ドメイン登録時に取得したVerificationTokenの値
Amazon SES domain verification TXT records
DNSレコードによる検証処理が完了すると、Verification Statusが verified
に変更されます。
SMTP利用の初期設定
SESでSMTPプロトコルを利用するための設定を実施します。
Obtaining your Amazon SES SMTP credentials
SESコンソールページの SMTP Settings
より、 Create My STMP Credentials
をクリックします。
SMTP通信用資格情報を作成するための、IAMユーザーを作成します。適当なIAM User名を入力して、 Create
をクリックします。
SMTP通信用の資格情報が作成されるため、この情報を保存しておきます。
コマンドラインから送信テスト
メール送信できるようなっている筈のため、送信テストをしてみます。コマンドラインからSMTP送信テストするための方法を、公式ドキュメントに記載してくれています。
Test your connection to the Amazon SES SMTP interface using the command line
上で取得したSMTP通信用資格情報の SMTP Username
と SMTP Password
の、Base64エンコードした値を取得します。
$ echo -n "AKIXXXXXXXXXXXXXXXXX" | openssl enc -base64 $ echo -n "BINXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" | openssl enc -base64
以下の通り、SMTP通話用ファイルを用意します。
input.txt
EHLO mail.goodbyegangster.com AUTH LOGIN QUtXXXXXXXXXXXXXXXXX <-Base64EncodedSMTPUserName QklXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX <-Base64EncodedSMTPPassword MAIL FROM: sample@mail.goodbyegangster.com RCPT TO: xxx@gmail.com DATA From: sample@mail.goodbyegangster.com To: xxx@gmail.com Subject: Amazon SES SMTP Test This message was sent using the Amazon SES SMTP interface. . QUIT
文字列 | 説明 |
---|---|
mail.goodbyegangster.com | 送信元ドメイン |
Base64EncodedSMTPUserName | BASE64エンコードしたSMTP Username |
Base64EncodedSMTPPassword | BASE64エンコードしたSMTP Password |
sample@mail.goodbyegangster.com | 送信元Mailアドレス |
xxx@gmail.comf | 送信先Mailアドレス |
以下を実行します。
$ openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.ap-northeast-1.amazonaws.com:587 < input.txt depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, OU = Server CA 1B, CN = Amazon verify return:1 depth=0 CN = email-smtp.ap-northeast-1.amazonaws.com verify return:1 250 Ok 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok 334 XXXXXXXXXXXX 334 XXXXXXXXXXXX 235 Authentication successful. 250 Ok 250 Ok 354 End data with <CR><LF>.<CR><LF> 250 Ok 0106017815d23993-ee75503d-90e2-43a7-8d72-dc80ffc25347-000000 451 4.4.2 Timeout waiting for data from client.
送信先Mailアドレスにメールが届いています。以下Gmailの画面より。
送信元(ENVELOPE FROM)が ap-northeast-1.amazonses.com
、署名元が amazonses.com
となっていることに注目。
DMARCの設定
DMARC(Domain-based Message Authetication, Reporting, and Conformance)とは、
DMARC (Domain-based Message Authentication, Reporting, and Conformance)とは、 電子メールにおける送信ドメイン認証技術の一つであり、 RFC 7489*1で標準化されています。
送信ドメイン認証で用いられる技術には、 SPF (Sender Policy Framework)2やDKIM (DomainKeys Identified Mail)3があります。 前者のSPFは、送信元メールサーバのIPアドレス等が正当なものかどうかを判別する手段です。 そして後者のDKIMは、メールに電子署名を付加することで、 メールの送信者および内容が改ざんされていないかどうかを検証できるようにするものです。 DMARCは、両者を利用したメールのドメイン認証を補強する技術です。
カスタムMAIL FROMドメインの設定(SPFレコードの設定)
SPFの設定をする前にカスタムMAIL FROMドメインの設定を実施します。MAIL FROMドメインとは、ENVELOPE FROMのことです。カスタムMAIL FROMドメインを設定する理由を、公式マニュアルに記載してくれています。
Using a custom MAIL FROM domain
ざっくりまとめると。
By default, messages that you send through Amazon SES use a subdomain of amazonses.com as the MAIL FROM domain. Sender Policy Framework (SPF) authentication successfully validates these messages because the default MAIL FROM domain matches the application that sent the email— in this case, Amazon SES.
デフォルトでは、MAIL FROMドメイン(ENVELOPE FROMドメイン)はamazonses.comのサブドメインを利用し、AWS側にてAmazon SESドメイン用のSPF設定を実施してくれているため、SPFの検証は成功します。
There are two ways to achieve DMARC validation: using Sender Policy Framework (SPF), and using DomainKeys Identified Mail (DKIM). The only way to comply with DMARC through SPF is to use a custom MAIL FROM domain, because SPF validation requires the domain in the From address to match the MAIL FROM domain. By using your own MAIL FROM domain, you have the flexibility to use SPF, DKIM, or both to achieve DMARC validation.
DMARC認証でのSPF検証を成功させるには、MAIL FROMドメイン(ENVELOPE FROMドメイン)とFROMアドレス(ヘッダーFROMドメイン)が一致している必要がある、とのこと。このために、カスタムMAIL FROMドメインで、MAIL FROMドメイン(ENVELOPE FROMドメイン)を変更してあげる必要あるのですね。
カスタムMAIL FROMドメイン(ENVELOPE FROMドメイン)にて、利用できるドメインの条件です。
・The MAIL FROM domain has to be a subdomain of the verified identity (email address or domain) that you send email from. For example, mail.example.com is a valid MAIL FROM domain for the domain example.com.
・The MAIL FROM domain shouldn't be a domain that you send email from. If you have to use the MAIL FROM domain in a From address, either disable email feedback forwarding and receive your bounces through Amazon SNS notifications, or ensure that your MAIL FROM domain is not the destination for feedback forwarding. To determine the destination of email forwarding feedback, see Email feedback forwarding destination.
・The MAIL FROM domain shouldn't be a domain that you use to receive email.
設定します。
$ aws ses set-identity-mail-from-domain --identity goodbyegangster.com \ --mail-from-domain mail.goodbyegangster.com \ --behavior-on-mx-failure RejectMessage \ --profile xxx
aws ses set-identity-mail-from-domain
DNSサーバーに、以下のレコードを登録します。
Name | Type | Value |
---|---|---|
subdomain.domain.com |
MX | 10 feedback-smtp.region .amazonses.com |
subdomain.domain.com |
TXT | "v=spf1 include:amazonses.com ~all" |
subdomain.domain.com
は利用するカスタムMAIL FROMドメインを、region
にはSESを利用しているリージョンを指定します。
以下はSESコンソール画面での、Verify Status確認画面です。
上にて、カスタムMAIL FROMドメインでSPFレコードの設定を実施している訳なので、カスタムMAIL FROMドメインでのSPF検証も成功するようになっています。
DKIMの設定
続いてDKIMの設定を実施します。公式マニュアルはこちら。
親ドメインに対して設定すれば、そのサブドメインに対してもDKIMが有効となるそうです。
設定します。
$ aws ses set-identity-dkim-enabled --identity goodbyegangster.com --dkim-enabled --profile xxx
aws ses set-identity-dkim-enabled
SESコンソール画面の対象ドメインページのDKIM設定画面を確認すると、DKIMのステータスがverification attempts failed
となっており、VerifyするためのCNAMEレコードが生成されています。このCNAMEレコードの先に、DKIMで利用する公開鍵があるだろうと予想しています。このCNAMEレコードをDNSサーバーに登録します。
DNSレコードを登録すると、ステータスがenabled
となります。
送信テスト
改めて送信テストを実施してみます。
送信元(ENVELOPE FROM)が mail.goodbyegangster.com
、署名元が goodbyegangster.com
となっています。
Gmailのメール・ソース画面では、SPFとDKIMの検証をPASSしているかどうか確認できます。
検索してみると、上記画面にてDMARCの検証をPASSしているかどうかも確認できるぽいのですが、僕のGmailアカウントでは表示されていませんでした(もしくはDMARC検証をしていない?)。フリーのGmailではなく、Google WorkspaceのGmailであれば、表示されるのかもしれません。ちなみに、outlook.comメールの方ではDMARC検証をしているようで、こっちに送信したメールでソースを確認してみたところ、ちゃんとDMARC検証をPASSしていました。
Bounceメール管理用のAWSリソース作成
SESでは、BouceやComplaint発生時に、SNSと連携する機能を提供してくれています。その機能を利用して、Bouce情報を管理するための仕組みを用意しておきます。以下みたいな、AWS的ピタゴラスイッチの仕組みとなります。
必要となるAWSリソース作成用のCloudFormationテンプレートを置いておきます。
https://github.com/goodbyegangster/cloudformation/tree/master/sns-and-kinesis-firehose
SESのNotification設定
goodbyegangster.com
ドメインに対して、Bounce発生時にSNSトピックに連携してくれる設定をしておきます。
$ aws ses set-identity-notification-topic \ --identity goodbyegangster.com \ --notification-type Bounce \ --sns-topic arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:ses_notifications_bounce \ --profile xxx
aws ses set-identity-notification-topic
正しく設定できている場合、S3上に AmazonSnsSubscriptionSucceeded
というメッセージが出力されています。
s3://bucket-name/bounce/YYYY/MM/DD/HH/ファイル
{ "notificationType":"AmazonSnsSubscriptionSucceeded", "message":"You have successfully subscribed your Amazon SNS topic 'arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:ses_notifications_bounce' to receive 'Bounce' notifications from Amazon SES for identity 'goodbyegangster.com'." }
テスト用Bounceの発生
意図的にBouceを発生することできるメールアドレス bounce@simulator.amazonses.com
を用意してくれており、これを利用してテストを行います。
SMTP通信用ファイルの送信先アドレスに、Bounce用メールアドレスを設定します。
input_bounce.txt
EHLO mail.goodbyegangster.com AUTH LOGIN QUtXXXXXXXXXXXXXXXXX QklXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX MAIL FROM: sample@mail.goodbyegangster.com RCPT TO: bounce@simulator.amazonses.com DATA From: sample@mail.goodbyegangster.com To: bounce@simulator.amazonses.com Subject: Amazon SES SMTP Test This message was sent using the Amazon SES SMTP interface. . QUIT
メール送信します。
$ openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.ap-northeast-1.amazonaws.com:587 < input_bounce.txt depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, OU = Server CA 1B, CN = Amazon verify return:1 depth=0 CN = email-smtp.ap-northeast-1.amazonaws.com verify return:1 250 Ok 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok 334 XXXXXXXXXXXX 334 XXXXXXXXXXXX 235 Authentication successful. 250 Ok 250 Ok 354 End data with <CR><LF>.<CR><LF> 250 Ok 01060178163781da-45359d35-aa36-43b3-8879-7a202363eccc-000000 451 4.4.2 Timeout waiting for data from client.
以下のようなBouce情報を記載したファイルが、S3上に作成されます。
s3://bucket-name/bounce/YYYY/MM/DD/HH/ファイル
{ "notificationType":"Bounce", "bounce":{ "feedbackId":"0106017819e950c6-52016b3a-e1c4-49e0-bb48-2b394b3e4ed7-000000", "bounceType":"Permanent", "bounceSubType":"General", "bouncedRecipients":[{ "emailAddress":"bounce@simulator.amazonses.com", "action":"failed", "status":"5.1.1", "diagnosticCode":"smtp; 550 5.1.1 user unknown" }], "timestamp":"2021-03-10T02:13:44.000Z", "remoteMtaIp":"xxx.xxx.xxx.xxx", "reportingMTA":"dsn; e234-3.smtp-out.ap-northeast-1.amazonses.com" }, "mail":{ "timestamp":"2021-03-10T02:13:43.258Z", "source":"sample@mail.goodbyegangster.com", "sourceArn":"arn:aws:ses:ap-northeast-1:XXXXXXXXXXXX:identity/goodbyegangster.com", "sourceIp":"xxx.xxx.xxx.xxx", "sendingAccountId":"XXXXXXXXXXXX", "messageId":"0106017819e94cda-496105c8-061f-4869-82ca-d661289d15aa-000000", "destination":["bounce@simulator.amazonses.com"] } }
Athenaで参照
S3上にあるBounce情報を、Athenaで参照できるように設定しておきます。
Bounce情報用のテーブル定義を作成します。
CREATE EXTERNAL TABLE bounce ( notificationType string, bounce struct< feedbackId:string, bounceType:string, bounceSubType:string, bouncedRecipients:array<struct< emailAddress:string, action:string, status:string, diagnosticCode:string >>, `timestamp`:string, remoteMtaIp:string, reportingMTA:string >, mail struct< `timestamp`:string, source:string, sourceArn:string, sourceIp:string, sendingAccountId:string, messageId:string, destination:array<string> > ) PARTITIONED BY ( datehour STRING ) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' WITH SERDEPROPERTIES ('ignore.malformed.json' = 'true') LOCATION 's3://bucket-name/bounce/' TBLPROPERTIES ( "projection.enabled" = "true", "projection.datehour.type" = "date", "projection.datehour.range" = "2018/01/01/00,NOW", "projection.datehour.format" = "yyyy/MM/dd/HH", "projection.datehour.interval" = "1", "projection.datehour.interval.unit" = "HOURS", "storage.location.template" = "s3://bucket-name/bounce/${datehour}" )
ネストされたJSONをパースするためのCREATE TABLE文の作り方は、以下の公式ブログの情報を参考にしています。
JSONSerDe によるマッピングを使って、入れ子の JSON から Amazon Athena のテーブルを作成する
org.openx.data.jsonserde.JsonSerDe
というHive用に用意されたSerde(formatみたいなものなのか?)を利用してパース文を書くこととなり、以下のGitHubのReadmeにも参考を記載してくれていますが、まあ、分かりにくいですね。try&errorを繰り返しているうちに、パースできるCREATE TABLE文が見つかる感じです。
JsonSerde - a read/write SerDe for JSON Data
Kinesis Firehoseにて、日時単位で階層化してファイルを送信しているので、それに応じてパーティション化もしてあげています。随時、動的にパーティション化してくれます。
Partition Projection with Amazon Athena
こんな感じで検索できます。
SELECT notificationtype, mail.source, mail.timestamp, mail.destination FROM default.bounce WHERE notificationtype = 'Bounce' AND datehour BETWEEN '2021/03/31/00' AND '2021/04/01/00'
結果。
今回設定したものはBounceだけですが、ComplaintやDeliveryの情報も、同じように設定できます。