はじめに
多くのシステムでユーザへのメール送信が必要になることがあるかと思います。
AWSで構築してる場合、メール送信にはAWS SES
を使うことになるのですが、メール送信結果を知りたい・分析したいとなった場合、どのようにすればいいのかわからなかったので試してみました。
今回のゴールをAWS SESを使って送信した各メールの送信結果を、指定した期間でメールアドレス毎に確認する
とします。
アーキテクチャとしては、以下になります。
-
SES
でユーザに対してメールを送信する - メールの送信ログを
Kinesis Firehose
に一定時間 or 一定量貯める - 貯めたログをファイルに出力し、
S3
に保存 - S3に保存した複数ファイルに対して、
Athena
を利用してクエリ実行し、送信結果を確認
何も設定しない場合は何が確認出来るのか
そもそも、今回の対応をしない場合は何が確認出来て、何が確認出来ない
のでしょうか?
他のAWSサービスにもあるように、SESにもダッシュボードがあるので確認してみましょう。
ダッシュボードを見てみる
左メニューのアカウントダッシュボード
をクリックします。
すると、以下について確認できることがわかります。
-
送信制限
の値 送信されたメール数
- 1日に送れる
メール数の残
- 拒否・バウンス・苦情になった
割合
正直これでも十分と言えば十分なのですが、誰に送信できて、誰にどんな理由で送れなかったなどがわかりません。
また、期間も過去1日・7日・14日
しか選べないようです。
このままだと分析したり、送信失敗したユーザへ再送処理したいってなった時に難しそうですね
ログ分析できるようにしてみる
ということで、それらが出来るようにしてみましょう。
S3バケットを作成する
まず、ログファイルを保存するS3バケットを作成する必要があるので、バケットを作成
をクリックします。
色々設定値はありますが、特別な設定は今回必要ないので、デフォルト値を設定し、バケットを作成
をクリックします。
Kinesis Firehoseを作成する
次に、SESからの送信ログを一時的に貯める、Kinesis Firehoseを作成します。
-
ソース
にDirect PUTを選択します -
送信先
にS3を選択します
今回は、レコード変換はしないので、デフォルト値のままにしましたが、任意の形式やパラメータ名に変換してからS3に保存したい場合は、Lambdaでのレコード変換を検討して下さい
- 送信先の
S3バケット
に先ほど作成したS3バケットを指定し、それ以外はデフォルト値のままにします
- デフォルトだとファイルは、
年/月/日/時間/
のパスに保存されます
保存先のパスを変えたい場合はS3バケットプレフィックス
を設定してください - ファイル名はデフォルトでは
ses-send-logs-to-s3-1-2024-09-27-18-05-53-e9897e16-52b0-499a-97c5-4ec5419025cd
のような日時などが付いたランダムな文字列になります
ファイル名の命名規則を指定したい場合は動的パーティショニング
を設定してください
-
バッファイサイズ
とバッファ間隔
はデフォルトのままにします。どちらかの設定値分、ログが溜まるとS3に吐き出される仕様になります。
なるべくリアルタイムにしたい場合は数値を小さくし、リアルタイム性はいらないのでファイル数を減らしたい場合は数値を大きくしてください -
圧縮
は今回有効化しませんが、Athenaでは圧縮したファイルもそのまま検索できるようなので、圧縮したほうが良いと思います
後は、デフォルト値のままにして、Firehoseストリームを作成
をクリックします
設定セットを作成する
SESでメールの追跡やログ出力を行うためには設定セット
を作成し、ID(検証済みドメイン or 検証済みメールアドレス)
にアタッチする必要があるので、設定セットを作っていきます
-
セットの作成
をクリックし、任意の名前を入力します - (今回やることでは関係ありませんが)
評判メトリクス
を有効化し、CloudWatchで追跡できるようにしておきましょう -
セットの作成
をクリックします
ロールの作成
次に、SESがFirehoseへログを送信
するための権限を付与するために、IAMロールを作成する必要があるので、ロールを作成
をクリックします。
カスタム信頼ポリシー
を選択し、以下の信頼ポリシーを設定します
{
"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
をアタッチします。
送信先を設定する
Kinesis Firehoseにログを送信するために、先ほど作成した設定セットに送信先を追加します。
送信先の追加
をクリックします
-
イベントタイプ
で送信~サブスクリプションまで全て選択します - (今回は選択しませんでしたが)開封数とクリック数も分析したい場合は、これらもチェックを付けます
全てのログを保存したほうが何かと便利ではありますが、その分ログの量が増えて各AWSサービスのコスト増になります。
どんな分析をしたいか(ex:失敗だけを分析できればいい)、どのくらいのメール送信量になるか、など踏まえて検討してください
-
送信先タイプ
にKinesis Firehoseを選択し、配信ストリーム
に先ほど作成したKinesis Firehoseを選択します -
IAMロール
に先ほど作成したIAMロールを選択します
送信先の追加
をクリックします
設定セットをIDにアタッチする
さぁ、あと少しで終わりです。
設定セットを作成したので、IDにアタッチしましょう。
※今回は検証済みドメインにアタッチします。
デフォルト設定セットの割り当て
にチェックを付け、先ほど作成した設定セットを選択し、保存します。
これで準備完了です
メールを送信してみる
では、実際にログを送信してみましょう!
今回は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行以上あるファイルになっています。
出力されるパラメータは、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バケットを指定することもできますが、混乱の元なので無難に別のバケットが良いと思います。
テーブルの作成
マスタテーブルを作成するので、CREATE TABLE
をクリックして、以下のSQLを実行するとテーブルが作られます。
RDBのCREATE文と似てますね
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バケット名}/'
ビューの作成
次に、作成した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を実行します。
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に置かれたファイルは、テーブルのデータとして反映されるのかという疑問が出てくるかもしれませんが、テーブルの再作成など行わなくても反映されますのでご心配なく
もっと簡単にログ分析したい方へ
ここまで色々AWSリソースを作ってログ分析を出来るようにしましたが...
- 色々AWSサービス使うの嫌だな~
- クエリで分析とか大変だな~
- もっと簡単にメール送信結果が見れないかな~
と思う方もいるのではないでしょうか
と思い、調べてみたらVirtual Deliverability Manager
というちょうどいい機能がありました。
簡単に言えば、アカウントダッシュボードをより詳細に見れるようにしたものという感じです。
有効化してみる
デフォルトでは、この機能は無効化されているので有効化する必要があります。
メール1,000通毎に0.07USDが別途かかりまます。
大量のメールを送る場合なら検討の余地がありますが、そこまで送らない想定の場合は費用もそこまでかからないので、有効化しておくのが良いと思います!
Virtual Deliverability Managerの使用を開始する
をクリックします。
後は、デフォルト(推奨)の方にチェックを付けておけば、とりあえずOKです!
ダッシュボードを確認してみる
では、有効化したのでどんな内容が見れるか確認してみましょう
左メニューのダッシュボード
をクリックすると、アカウント
タブが開かれています。
このタブでは、当AWSアカウントで送信された以下の内容が確認できます。
- 送信数
- 各イベント(送信やバウンスなど)の数と割合
- 前の期間との比較
さらに、アカウントダッシュボードとは違い、日付範囲も指定できます。
ISP
タブでは、インターネットサービスプロバイダー毎の
送信数と各イベントタイプの割合が確認できます
が表示されます。
タブでは、SESのID毎の
送信数と各イベントタイプの割合が確認できます
対象のIDをクリックすると、より詳細に確認することが出来ます
設定セット
タブでは、設定セット毎の
送信数と各イベントタイプの割合が確認できます
対象の設定セットをクリックすると、より詳細に確認することが出来ます
メッセージ
タブでは、メッセージ毎の
送信先メールアドレスやイベントタイプ、ISPなどが確認できます
対象のメッセージの詳細を表示
をクリックすると、より詳細な内容が表示されます。
👇の画像はバウンスメッセージの詳細ですが、バウンスした理由などが確認できます
おわりに
今回はSESを利用して送信したメールを分析するための方法を説明しました。
SESでメール送信したはいいが、誰に正常に送れて、誰に送れなかったか確認したいって人のお役に立てれば幸いです