2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

あの人へSESで送ったメールってどうなったんだろう?どうやれば確認出来るのか ~100本ノックしてみたい中堅エンジニア 【AWS】~ 10/100

Last updated at Posted at 2024-10-11

はじめに

image.png

多くのシステムでユーザへのメール送信が必要になることがあるかと思います。

AWSで構築してる場合、メール送信にはAWS SESを使うことになるのですが、メール送信結果を知りたい・分析したいとなった場合、どのようにすればいいのかわからなかったので試してみました。

今回のゴールをAWS SESを使って送信した各メールの送信結果を、指定した期間でメールアドレス毎に確認するとします。

アーキテクチャとしては、以下になります。

  • SESでユーザに対してメールを送信する
  • メールの送信ログをKinesis Firehoseに一定時間 or 一定量貯める
  • 貯めたログをファイルに出力し、S3に保存
  • S3に保存した複数ファイルに対して、Athenaを利用してクエリ実行し、送信結果を確認

SESのログ分析.drawio.png

何も設定しない場合は何が確認出来るのか

そもそも、今回の対応をしない場合は何が確認出来て、何が確認出来ないのでしょうか? :thinking:

他のAWSサービスにもあるように、SESにもダッシュボードがあるので確認してみましょう。

ダッシュボードを見てみる

左メニューのアカウントダッシュボードをクリックします。
すると、以下について確認できることがわかります。

  • 送信制限の値
  • 送信されたメール数
  • 1日に送れるメール数の残
  • 拒否・バウンス・苦情になった割合

0-1.png

正直これでも十分と言えば十分なのですが、誰に送信できて、誰にどんな理由で送れなかったなどがわかりません。
また、期間も過去1日・7日・14日しか選べないようです。

このままだと分析したり、送信失敗したユーザへ再送処理したいってなった時に難しそうですね :cry:

ログ分析できるようにしてみる

ということで、それらが出来るようにしてみましょう。

S3バケットを作成する

まず、ログファイルを保存するS3バケットを作成する必要があるので、バケットを作成をクリックします。

色々設定値はありますが、特別な設定は今回必要ないので、デフォルト値を設定し、バケットを作成をクリックします。

0.png

1.png

2.png

Kinesis Firehoseを作成する

次に、SESからの送信ログを一時的に貯める、Kinesis Firehoseを作成します。

Firehoseストリームを作成をクリックします。
3.png

  • ソースにDirect PUTを選択します
  • 送信先にS3を選択します

4.png

今回は、レコード変換はしないので、デフォルト値のままにしましたが、任意の形式やパラメータ名に変換してからS3に保存したい場合は、Lambdaでのレコード変換を検討して下さい

  • 送信先のS3バケットに先ほど作成したS3バケットを指定し、それ以外はデフォルト値のままにします

5.png

  • デフォルトだとファイルは、年/月/日/時間/のパスに保存されます
    保存先のパスを変えたい場合はS3バケットプレフィックスを設定してください
  • ファイル名はデフォルトではses-send-logs-to-s3-1-2024-09-27-18-05-53-e9897e16-52b0-499a-97c5-4ec5419025cdのような日時などが付いたランダムな文字列になります
    ファイル名の命名規則を指定したい場合は動的パーティショニングを設定してください
  • バッファイサイズバッファ間隔はデフォルトのままにします。どちらかの設定値分、ログが溜まるとS3に吐き出される仕様になります。
    なるべくリアルタイムにしたい場合は数値を小さくし、リアルタイム性はいらないのでファイル数を減らしたい場合は数値を大きくしてください
  • 圧縮は今回有効化しませんが、Athenaでは圧縮したファイルもそのまま検索できるようなので、圧縮したほうが良いと思います

6.png

後は、デフォルト値のままにして、Firehoseストリームを作成をクリックします
7.png

設定セットを作成する

SESでメールの追跡やログ出力を行うためには設定セットを作成し、ID(検証済みドメイン or 検証済みメールアドレス)にアタッチする必要があるので、設定セットを作っていきます

  • セットの作成をクリックし、任意の名前を入力します
  • (今回やることでは関係ありませんが)評判メトリクスを有効化し、CloudWatchで追跡できるようにしておきましょう
  • セットの作成をクリックします

8.png

9.png

ロールの作成

次に、SESがFirehoseへログを送信するための権限を付与するために、IAMロールを作成する必要があるので、ロールを作成をクリックします。

12.png

カスタム信頼ポリシーを選択し、以下の信頼ポリシーを設定します

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ses.amazonaws.com"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:ses:ap-northeast-1:*****:configuration-set/${先ほど作成した設定セットの名前}"
                }
            }
        }
    ]
}

次に、Kinesis Firehoseへアクセスするためのポリシーをアタッチします。
本当は必要な権限に絞ったほうが良いのですが、今回はゆるくAmazonKinesisFirehoseFullAccessをアタッチします。
13.png

14.png

送信先を設定する

Kinesis Firehoseにログを送信するために、先ほど作成した設定セットに送信先を追加します。
送信先の追加をクリックします

10.png

  • イベントタイプで送信~サブスクリプションまで全て選択します
  • (今回は選択しませんでしたが)開封数とクリック数も分析したい場合は、これらもチェックを付けます

11.png

全てのログを保存したほうが何かと便利ではありますが、その分ログの量が増えて各AWSサービスのコスト増になります。
どんな分析をしたいか(ex:失敗だけを分析できればいい)、どのくらいのメール送信量になるか、など踏まえて検討してください

  • 送信先タイプにKinesis Firehoseを選択し、配信ストリームに先ほど作成したKinesis Firehoseを選択します
  • IAMロールに先ほど作成したIAMロールを選択します

16.png

送信先の追加をクリックします

17.png

設定セットをIDにアタッチする

さぁ、あと少しで終わりです。
設定セットを作成したので、IDにアタッチしましょう。
※今回は検証済みドメインにアタッチします。

設定セットの管理をクリックします
18.png

デフォルト設定セットの割り当てにチェックを付け、先ほど作成した設定セットを選択し、保存します。

これで準備完了です :smile:

19.png

メールを送信してみる

では、実際にログを送信してみましょう!
今回はAWS CLIでメール送信してみます

aws ses send-email \
  --from ${検証済みドメインのメールアドレス} \
  --to ${任意のメールアドレス} \
  --subject "test" \
  --text "xxx" \
  --region ap-northeast-1

{
    "MessageId": "01060192312f8080-b889e24c-63f8-4fe1-8855-09ac6f11c866-000000"
}

5分くらい待つと、作成したS3バケットにファイルが出来て、中身を見ると以下のようなjsonが1行以上あるファイルになっています。

20.png

出力されるパラメータは、eventTypeによって変わります。eventTypeの値はイベント送信先で指定できる配信やハードバウンスなどの10種類のいずれかが出力されます。

詳しくは公式のこちらをご参照ください。

出力されるjsonサンプル
{
	"eventType": "Send",
	"mail": {
		"timestamp": "2024-09-27T09:05:52.140Z",
		"source": "管理者 <test@example.com>",
		"sendingAccountId": "**********",
		"messageId": "0106019232babe0c-84733e58-9c8b-44ee-bd1f-cf623d7cad05-000000",
		"destination": [
			"user1@example.com"
		],
		"headersTruncated": false,
		"headers": [
			{
				"name": "From",
				"value": "管理者 <test@example.com>"
			},
			{
				"name": "To",
				"value": "user1@example.com"
			},
			{
				"name": "MIME-Version",
				"value": "1.0"
			}
		],
		"commonHeaders": {
			"from": [
				"\"管理者\" <test@example.com>"
			],
			"to": [
				"user1@example.com"
			],
			"messageId": "0106019232babe0c-84733e58-9c8b-44ee-bd1f-cf623d7cad05-000000"
		},
		"tags": {
			"ses:source-tls-version": [
				"TLSv1.3"
			],
			"ses:operation": [
				"SendEmail"
			],
			"ses:configuration-set": [
				"send-logs"
			],
			"ses:source-ip": [
				"**.**.**.**"
			],
			"ses:from-domain": [
				"example.com"
			],
			"ses:caller-identity": [
				"***"
			]
		}
	},
	"send": {}
}

Athenaで検索してみる

では、S3に送信ログファイルが置かれたのでAthenaで検索してみましょう。
公式で紹介されているこちらに沿って、まずテーブルとビューを作成します。

クエリの結果の保存先を設定する

Athenaでクエリを実行すると実行結果がS3に保存されます。
なので、Athenaを初めて利用する際は保存するS3を設定する必要がありますので、S3バケットを1つ作ります。

メール送信ログのS3バケットを指定することもできますが、混乱の元なので無難に別のバケットが良いと思います。

管理をクリックします
25.png

  • 出力先に事前に作成したS3バケットを選択します
  • バケットオーナーに現在のAWSアカウントを指定します
  • バケットオーナーへのフルコントロールと、暗号化の両方にチェックして保存します
    26.png

テーブルの作成

マスタテーブルを作成するので、CREATE TABLEをクリックして、以下のSQLを実行するとテーブルが作られます。
RDBのCREATE文と似てますね :smile:

sesmasterテーブルの作成
CREATE EXTERNAL TABLE sesmaster (
eventType string,
complaint struct < arrivaldate: string,
complainedrecipients: array < struct < emailaddress: string >>,
complaintfeedbacktype: string,
feedbackid: string,
`timestamp`: string,
useragent: string >,
bounce struct < bouncedrecipients: array < struct < action: string,
diagnosticcode: string,
emailaddress: string,
status: string >>,
bouncesubtype: string,
bouncetype: string,
feedbackid: string,
reportingmta: string,
`timestamp`: string >,
mail struct < timestamp: string,
source: string,
sourcearn: string,
sendingaccountid: string,
messageid: string,
destination: string,
headerstruncated: boolean,
headers: array < struct < name: string,
value: string >>,
commonheaders: struct < `from`: array < string >,
to: array < string >,
messageid: string,
subject: string >,
tags: struct < ses_source_tls_version: string,
ses_operation: string,
ses_configurationset: string,
ses_source_ip: string,
ses_outgoing_ip: string,
ses_from_domain: string,
ses_caller_identity: string >>,
send string,
delivery struct < processingtimemillis: int,
recipients: array < string >,
reportingmta: string,
smtpresponse: string,
`timestamp`: string >,
deliveryDelay struct < delayType: string,
delayedRecipients: array < string >,
expirationTime: string,
reportingMTA: string,
timestamp: string >,
open struct < ipaddress: string,
`timestamp`: string,
userAgent: string >,
reject struct < reason: string >,
click struct < ipAddress: string,
`timestamp`: string,
userAgent: string,
link: string >
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
"mapping.ses_caller_identity" = "ses:caller-identity",
"mapping.ses_configurationset" = "ses:configuration-set",
"mapping.ses_from_domain" = "ses:from-domain",
"mapping.ses_operation" = "ses:opeation",
"mapping.ses_outgoing_ip" = "ses:outgoing-ip",
"mapping.ses_source_ip" = "ses:source-ip",
"mapping.ses_source_tls_version" = "ses:source-tls-version"
)
LOCATION 's3://${作成したS3バケット名}/'

21.png

ビューの作成

次に、作成したsesmasterテーブルをもとにビューを作成します。

vwSESMasterビューの作成
CREATE OR REPLACE VIEW vwSESMaster AS
SELECT
eventtype as eventtype
, mail.messageId as mailmessageid
, mail.destination as maildestination
, mail.timestamp as mailtimestamp
, mail.source as mailsource
, mail.sendingAccountId as mailsendingAccountId
, mail.commonHeaders.subject as mailsubject
, mail.tags.ses_configurationset as mailses_configurationset
, mail.tags.ses_source_ip as mailses_source_ip
, mail.tags.ses_from_domain as mailses_from_domain
, mail.tags.ses_outgoing_ip as mailses_outgoing_ip
, delivery.processingtimemillis as deliveryprocessingtimemillis
, delivery.reportingmta as deliveryreportingmta
, delivery.smtpresponse as deliverysmtpresponse
, delivery.timestamp as deliverytimestamp
, delivery.recipients[1] as deliveryrecipient
, deliveryDelay.delayType as deliverydelaydelayType
, deliveryDelay.delayedRecipients as deliverydelaydelayedRecipients
, deliveryDelay.expirationTime as deliverydelayexpirationTime
, deliveryDelay.reportingMTA as deliverydelayreportingMTA
, deliveryDelay.timestamp as deliverydelaytimestamp
, open.ipaddress as openipaddress
, open.timestamp as opentimestamp
, open.userAgent as openuseragent
, bounce.bounceType as bouncebounceType
, bounce.bouncesubtype as bouncebouncesubtype
, bounce.feedbackid as bouncefeedbackid
, bounce.timestamp as bouncetimestamp
, bounce.reportingMTA as bouncereportingmta
, click.ipAddress as clickipaddress
, click.timestamp as clicktimestamp
, click.userAgent as clickuseragent
, click.link as clicklink
, complaint.timestamp as complainttimestamp
, complaint.userAgent as complaintuseragent
, complaint.complaintFeedbackType as complaintcomplaintfeedbacktype
, complaint.arrivalDate as complaintarrivaldate
, reject.reason as rejectreason
FROM
sesmaster

他にもバウンス用ビューなどいろんなビューを作ることをAWSでは紹介していますが、今回は1つだけ作成にしておきます。

送信ログの検索

では、最後にいくつかログ検索をSQLで試してみましょう。
RDB触ったことある人ならば、そこまで違和感なく実行できると思います!

指定した期間内に発生したバウンスメールの送信先やバウンス理由などを確認したい場合は、以下のSQLを実行します。

指定した期間内のバウンスメールの取得
SELECT *
FROM
  "default"."vwbouncedmails"
WHERE
  mailtimestamp > cast('2024-10-07T08:00:00.000Z' as varchar) and 
  mailtimestamp < cast('2024-10-07T09:00:00.000Z' as varchar) ;

指定した期間以降で送信したメールをイベントタイプ毎に集計したい場合は、以下のSQLを実行します。

指定した期間以降のメールのeventType毎の集計
SELECT *
FROM
  "default"."vwbouncedmails"
WHERE
  mailtimestamp > cast('2024-10-07T08:00:00.000Z' as varchar) and 
  mailtimestamp < cast('2024-10-07T09:00:00.000Z' as varchar) ;

ちなみに...テーブル作成した後にS3に置かれたファイルは、テーブルのデータとして反映されるのかという疑問が出てくるかもしれませんが、テーブルの再作成など行わなくても反映されますのでご心配なく:smirk:

もっと簡単にログ分析したい方へ

ここまで色々AWSリソースを作ってログ分析を出来るようにしましたが...

  • 色々AWSサービス使うの嫌だな~
  • クエリで分析とか大変だな~
  • もっと簡単にメール送信結果が見れないかな~
    と思う方もいるのではないでしょうか :thinking:

と思い、調べてみたらVirtual Deliverability Managerというちょうどいい機能がありました。
簡単に言えば、アカウントダッシュボードをより詳細に見れるようにしたものという感じです。

有効化してみる

デフォルトでは、この機能は無効化されているので有効化する必要があります。

メール1,000通毎に0.07USDが別途かかりまます。
大量のメールを送る場合なら検討の余地がありますが、そこまで送らない想定の場合は費用もそこまでかからないので、有効化しておくのが良いと思います!

Virtual Deliverability Managerの使用を開始するをクリックします。

37.png

後は、デフォルト(推奨)の方にチェックを付けておけば、とりあえずOKです!

38.png

39.png

40.png

ダッシュボードを確認してみる

では、有効化したのでどんな内容が見れるか確認してみましょう :smile:

左メニューのダッシュボードをクリックすると、アカウントタブが開かれています。

このタブでは、当AWSアカウントで送信された以下の内容が確認できます。

  • 送信数
  • 各イベント(送信やバウンスなど)の数と割合
  • 前の期間との比較

さらに、アカウントダッシュボードとは違い、日付範囲も指定できます。
29.png

ISPタブでは、インターネットサービスプロバイダー毎の送信数と各イベントタイプの割合が確認できます
30.png

が表示されます。タブでは、SESのID毎の送信数と各イベントタイプの割合が確認できます
31.png

対象のIDをクリックすると、より詳細に確認することが出来ます
32.png

設定セットタブでは、設定セット毎の送信数と各イベントタイプの割合が確認できます
33.png

対象の設定セットをクリックすると、より詳細に確認することが出来ます
34.png

メッセージタブでは、メッセージ毎の送信先メールアドレスやイベントタイプ、ISPなどが確認できます
35.png

対象のメッセージの詳細を表示をクリックすると、より詳細な内容が表示されます。
👇の画像はバウンスメッセージの詳細ですが、バウンスした理由などが確認できます
36.png

おわりに

今回はSESを利用して送信したメールを分析するための方法を説明しました。
SESでメール送信したはいいが、誰に正常に送れて、誰に送れなかったか確認したいって人のお役に立てれば幸いです :bow:

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?