あの人へSESで送ったメールってどうなったんだろう？どうやれば確認出来るのか ～AWS100本ノック～ 10/100

# はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/33483ca1-3be3-35ef-3258-ed31f9f3bf6f.png)


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

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

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

アーキテクチャとしては、以下になります。
- `SES`でユーザに対してメールを送信する
- メールの送信ログを`Kinesis Firehose`に一定時間 or 一定量貯める
- 貯めたログをファイルに出力し、`S3`に保存
- S3に保存した複数ファイルに対して、`Athena`を利用してクエリ実行し、送信結果を確認
 
![SESのログ分析.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/eb37021e-1870-c0fa-8c13-3253c2baf923.png)

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

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

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

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

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

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

![0-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/3a7fb509-a4d0-fa19-b672-90a7da1471e5.png)

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

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

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

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

## S3バケットを作成する

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

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

![0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/03209998-670e-7bbe-599a-e41d574130e8.png)

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/ef7bb315-a82f-935c-6e5e-25c0e1b9ee12.png)

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/60115d1d-3837-93d8-6921-13a224dff0e0.png)

## Kinesis Firehoseを作成する

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

`Firehoseストリームを作成`をクリックします。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/05cc40e5-f158-9735-2b4f-65737365b5ce.png)

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

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/aa66a2fe-bc7e-e121-6edc-b3982a2eea18.png)


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

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


![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/f4f59bac-d481-d579-14c4-4a1e143bd2ec.png)

:::note info
- デフォルトだとファイルは、`年/月/日/時間/`のパスに保存されます
保存先のパスを変えたい場合は`S3バケットプレフィックス`を設定してください
- ファイル名はデフォルトでは`ses-send-logs-to-s3-1-2024-09-27-18-05-53-e9897e16-52b0-499a-97c5-4ec5419025cd`のような日時などが付いたランダムな文字列になります
ファイル名の命名規則を指定したい場合は`動的パーティショニング`を設定してください
:::

- `バッファイサイズ`と`バッファ間隔`はデフォルトのままにします。どちらかの設定値分、ログが溜まるとS3に吐き出される仕様になります。
なるべくリアルタイムにしたい場合は数値を小さくし、リアルタイム性はいらないのでファイル数を減らしたい場合は数値を大きくしてください
- `圧縮`は今回有効化しませんが、Athenaでは圧縮したファイルもそのまま検索できるようなので、圧縮したほうが良いと思います

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/4ec7dbc5-1d5f-8486-3ea3-40b373b9e497.png)


後は、デフォルト値のままにして、`Firehoseストリームを作成`をクリックします
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/f1dab21f-a56d-d74b-795e-c8e19c6b79a1.png)


## 設定セットを作成する

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

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

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0c3d43d9-969c-f2ec-41af-740070fcc184.png)

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/86dff18b-7bf1-77a5-af11-2b292efdcd89.png)

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

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/ce2deb4b-fdbd-11a6-320b-c846f2e73492.png)

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

```json
{
    "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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/83ba3481-8f42-7b75-b466-94f72c70e34e.png)

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/5d597875-1979-5d06-5264-5b45111216af.png)

## 送信先を設定する

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

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/0529b627-ac0a-2b83-d4f6-112f729c5f76.png)

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

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/d5a0c3c7-16d3-b1d0-e57d-53ef7c961dd1.png)

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

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

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/c22513e1-58b6-db6e-2ed3-e416af7db4e6.png)


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

![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/783d3bb5-44ed-dd8f-cdc0-a0d340f6a182.png)


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

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

`設定セットの管理`をクリックします
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/3bb33f9b-2eef-7511-e295-e8a80e6fa2eb.png)

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

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

![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/261f59f5-531d-93be-d67d-ea1ee9243cf7.png)


# メールを送信してみる

では、実際にログを送信してみましょう！
今回はAWS CLIでメール送信してみます
```sh
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/e094462c-33b2-8266-e331-38bce54cfed2.png)

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

詳しくは公式の[こちら](https://docs.aws.amazon.com/ja_jp/ses/latest/dg/event-publishing-retrieving-sns-contents.html)をご参照ください。

<details><summary>出力されるjsonサンプル</summary>

```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": {}
}
```
</details>



# Athenaで検索してみる

では、S3に送信ログファイルが置かれたのでAthenaで検索してみましょう。
公式で紹介されている[こちら](https://aws.amazon.com/jp/blogs/messaging-and-targeting/analyzing-amazon-ses-event-data-with-aws-analytics-services/)に沿って、まずテーブルとビューを作成します。


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

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

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

`管理`をクリックします
![25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/d0e6dc52-d204-8d5c-91ff-768ab8dbc24c.png)

- `出力先`に事前に作成したS3バケットを選択します
- `バケットオーナー`に現在のAWSアカウントを指定します
- `バケットオーナーへのフルコントロール`と、`暗号化`の両方にチェックして保存します
![26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/951c45a6-7ef2-c54e-100f-394255de2db1.png)


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

<details><summary>sesmasterテーブルの作成</summary>

```sql
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バケット名}/'
```
</details>

![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/bfe60e02-628c-6629-7583-2050d201847d.png)


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

<details><summary>vwSESMasterビューの作成</summary>

```sql
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
```
</details>

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

## 送信ログの検索

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

指定した期間内に発生したバウンスメールの送信先やバウンス理由などを確認したい場合は、以下のSQLを実行します。
```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を実行します。
```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) ;
```

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

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

ここまで色々AWSリソースを作ってログ分析を出来るようにしましたが...
- 色々AWSサービス使うの嫌だな～
- クエリで分析とか大変だな～
- もっと簡単にメール送信結果が見れないかな～
と思う方もいるのではないでしょうか :thinking: 

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


## 有効化してみる

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

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

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

![37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/d1f37edd-562c-f2a7-2f02-dc0901c5675a.png)

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

![38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/b8cbd45d-b056-7a9b-a523-5ae7c8326278.png)

![39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/84ee09e4-0b56-c3ba-ff21-0930d65b0265.png)

![40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/9d8e34d8-797c-eb2b-53ae-dbe12626a460.png)


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

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

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

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

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

さらに、アカウントダッシュボードとは違い、日付範囲も指定できます。
![29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/6dea5d74-12a8-cd25-28b6-f281fba6585e.png)

`ISP`タブでは、`インターネットサービスプロバイダー毎の`送信数と各イベントタイプの割合が確認できます
![30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/22cc6b59-daaf-9840-7e83-02595c868f5d.png)

`が表示されます。`タブでは、`SESのID毎の`送信数と各イベントタイプの割合が確認できます
![31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/afd4fcdc-13a4-4d75-42a4-315d64bad428.png)

対象のIDをクリックすると、より詳細に確認することが出来ます
![32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/e39c23d1-4d6e-497b-6061-6e38f8443ce6.png)


`設定セット`タブでは、`設定セット毎の`送信数と各イベントタイプの割合が確認できます
![33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/68197de8-0ca9-32d7-afef-7eb4f5b651df.png)

対象の設定セットをクリックすると、より詳細に確認することが出来ます
![34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/e9f94435-e329-9fac-28dd-5affc9dbb7c7.png)


`メッセージ`タブでは、`メッセージ毎の`送信先メールアドレスやイベントタイプ、ISPなどが確認できます
![35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/016f44d1-aef5-d207-8041-f142ba5ea9d3.png)

対象のメッセージの`詳細を表示`をクリックすると、より詳細な内容が表示されます。
👇の画像はバウンスメッセージの詳細ですが、バウンスした理由などが確認できます
![36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87924/f02e2dea-4b11-ac60-7d09-8d4f737a4232.png)



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