SESメール配信時のイベントをKinesis Data Firehose経由でS3へ保存（Terraform）

# なんのために

メールをトラッキングするため。

# “トラッキング” is 何

メールが送信対象者に届いたかや、届いた後にメールが開封されたのか等を追跡すること。

# トラッキングすると何が良い

- メールの送信時トラブル（リジェクト、バウンスなど）の検知ができる
- 開封率などのデータ収集が行え、マーケティングに使える

# Solution

- Kinesis Data Firehose + Open Search
- Kinesis Data Firehose + Redshift
- Kinesis Data Firehose + S3 ← 今回はこちらを採用

# 全体像

- iam周り作る
- S3のバケットリソース作る
- sesの構成リソースを作成
- eventの宛先リソースを作成
- Kinesis Data Firehoseを作る
- 諸々つなぎこむ

手始めにiam（role）を作りましょう。ざっくりと、

- kinesisへイベントを流すためのses用のロール
- eventを受け取りS3へputしてくれるkinesis用のロール

の2つのロールを作るイメージです。

iam.tf

```go
// sesに渡すロールにアタッチするAssume Role用のポリシーのドキュメント
data "aws_iam_policy_document" "ses_asssume" {
  statement {
    sid    = ""
    effect = "Allow"

    actions = [
      "sts:AssumeRole"
    ]

    principals {
      type = "Service"
      identifiers = [
        "ses.amazonaws.com"
      ]
    }
  }
}

// sesに渡すロール
resource "aws_iam_role" "ses" {
    name = "ses"
    assume_role_policy = data.aws_iam_policy_document.ses_asssume
}

// sesに渡すロールにアタッチするポリシードキュメント
data "aws_iam_policy_document" "ses" {
  statement {
    sid = ""
    actions = [
      "firehose:PutRecord",
      "firehose:PutRecordBatch"
    ]

    resources = [aws_kinesis_firehose_delivery_stream.ses.arn]
  }
}

// sesに渡すロールにアタッチするポリシー
resource "aws_iam_policy" "ses" {
  name   = "ses"
  path   = "/"
  policy = data.aws_iam_policy_document.ses.json
}

// ses用ロールにポリシーをアタッチ
resource "aws_iam_policy_attachment" "ses" {
  name       = "ses"
  roles      = [aws_iam_role.ses.name]
  policy_arn = aws_iam_policy.ses.arn
}
```

大まかにやることとしては以下のような感じです。

- AssumeRoleのポリシードキュメント作成
- ロールの作成
- ロールにAssumeRoleをアタッチ
- サービス利用のためのポリシードキュメント作成
- ポリシー作成
- ポリシーとドキュメントを紐付け
- ロールにポリシーをアタッチ

`aws_kinesis_firehose_delivery_stream.ses_extended_s3_stream.arn`

のリソースはあとから作成するので一旦は流しましょう。

同じ流れでkinesis側のロールも作りましょう。

```go
// kinesisに渡すロールにアタッチするAssume Role用のポリシーのドキュメント
data "aws_iam_policy_document" "kinesis_data_firehose_assume" {
  statement {
    sid    = ""
    effect = "Allow"

    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["firehose.amazonaws.com"]
    }
  }
}

// kinesisに渡すロール
resource "aws_iam_role" "kinesis_data_firehose" {
  name               = "kinesis"
  assume_role_policy = data.aws_iam_policy_document.kinesis_data_firehose_assume.json
}

// kinesisに渡すロールにアタッチするポリシードキュメント
data "aws_iam_policy_document" "kinesis_data_firehose" {
  statement {
    sid    = ""
    effect = "Allow"

    actions = [
      "s3:AbortMultipartUpload",
      "s3:GetBucketLocation",
      "s3:GetObject",
      "s3:ListBucket",
      "s3:ListBucketMultipartUploads",
      "s3:PutObject"
    ]

    resources = [
      aws_s3_bucket.ses.arn,
      "${aws_s3_bucket.ses.arn}/*"
    ]
  }
}

// kinesisに渡すロールにアタッチするポリシー
resource "aws_iam_policy" "kinesis_data_firehose" {
  name   = "kinesis-data-firehose"
  path   = "/"
  policy = data.aws_iam_policy_document.kinesis_data_firehose.json
}

// kinesis用ロールにポリシーをアタッチ
resource "aws_iam_policy_attachment" "kinesis_data_firehose" {
  name       = "kinesis-data-firehose"
  roles      = [aws_iam_role.kinesis_data_firehose.name]
  policy_arn = aws_iam_policy.kinesis_data_firehose
}
```

`aws_cloudwatch_log_stream.kinesis_data_firehose.arn`のリソースはあとから作ります。

次にバケットを作ります。

s3.tf

```go
resource "aws_s3_bucket" "ses" {
  bucket = "ses-event-log"
  acl    = "private"
}
```

sesリソースの作成に参りましょう。

ses.tf

```go
// イベント関連構成セットのリソース
resource "aws_ses_configuration_set" "main" {
  name = "main"
}

resource "aws_ses_event_destination" "kinesis_data_firehose" {
  name                   = "kinesis-data-firehose"
  configuration_set_name = aws_ses_configuration_set.main.name
  enabled                = true

  matching_types = [
    "send",
    "reject",
    "delivery",
    "bounce",
    "complaint"
  ]

  kinesis_destination {
    stream_arn = aws_kinesis_firehose_delivery_stream.ses.arn
    role_arn   = aws_iam_role.kinesis_data_firehose.arn
  }
}
```

`aws_ses_configuration_set` はメール配信時の受信するイベントやそのイベントの宛先を構成するためのリソースです。

具体的な送信先は `aws_ses_event_destination`で設定しています。

この宛先を `aws_ses_configuration_set`へセットしているイメージですね。

`matching_types`は受け取るイベントのタイプです。それぞれが何を示すのかは公式ページを参照ください。

[https://docs.aws.amazon.com/ja_jp/ses/latest/dg/monitor-using-event-publishing.html](https://docs.aws.amazon.com/ja_jp/ses/latest/dg/monitor-using-event-publishing.html)

`kinesis_destination`では実際に受け取ったイベントを宛先に設定しています。

（他にもCloud WatchやSNSも宛先として選択可能なようです。）

そして最後に、主役（？）となるkinesisのリソースを作ります。

kiesisdatafirehose.tf

```go
resource "aws_kinesis_firehose_delivery_stream" "ses" {
  name        = "ses"
  destination = "extended_s3"

  extended_s3_configuration {
    role_arn        = aws_iam_role.kinesis_data_firehose.arn
    bucket_arn      = aws_s3_bucket.ses.arn
    prefix          = "ses"
    buffer_interval = 60
  }
}
```

`destination`は配信先のリソースですね。s3がどうやら非推奨になっていたっぽいので、代わりに `extended_s3`を指定しました。拡張設定オプションなるものなるものらしいのですが、細かい違いについてはよくわかっていません。。。

[https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream#:~:text=s3_configuration - (Optional) Required for non-S3 destinations. For S3 destination%2C use extended_s3_configuration instead. Configuration options for the s3 destination (or the intermediate bucket if the destination is redshift). More details are given below](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_firehose_delivery_stream#:~:text=s3_configuration%20%2D%20(Optional)%20Required%20for%20non%2DS3%20destinations.%20For%20S3%20destination%2C%20use%20extended_s3_configuration%20instead.%20Configuration%20options%20for%20the%20s3%20destination%20(or%20the%20intermediate%20bucket%20if%20the%20destination%20is%20redshift).%20More%20details%20are%20given%20below)

`extended_s3_configuration` では色々配信に関する設定が行えます。

ひとまず受信データを宛先に配信するまでのバッファリングをデフォルトの300秒から最小の60秒に変えています。（リアルタイム性を上げたいため）

[https://aws.amazon.com/jp/kinesis/data-firehose/faqs/#:~:text=Kinesis Data Firehose buffers incoming,data delivery to Amazon S3](https://aws.amazon.com/jp/kinesis/data-firehose/faqs/#:~:text=Kinesis%20Data%20Firehose%20buffers%20incoming,data%20delivery%20to%20Amazon%20S3).

あとは本記事ではカットしてますが、必要に応じて、`cloudwatch_logging_options`などを追加してログ周りの強化を図るなどすれば良さそうですね。

一旦以上で必要最低限のリソース構築はできたかなと思います。

あとはアプリケーションからのメール送信機能などにおいて実際にメールを送信した際にトラッキングを有効にするには、カスタムヘッダーに `X-SES-CONFIGURATION-SET`を含む必要があります。

以下のnameとvalueを送ってあげる感じです。

name ...  `X-SES-CONFIGURATION-SET`

value ... `main` （これは `aws_ses_configuration_set`で作成した構成リソースを指定します。）

具体的な方法についてはアプリケーションに依存する形になるかと思いますのでここでは割愛します。

ここまでの流れでリソースが構築できていればカスタムヘッダーの送信により、イベントのトラッキングが発動し、ログが取れるかなと思います。（内容に不備等がありましたら申し訳ございません。そっと教えてくださいませ。。）

# 参考

公式

[https://aws.amazon.com/jp/premiumsupport/knowledge-center/ses-email-sending-history/](https://aws.amazon.com/jp/premiumsupport/knowledge-center/ses-email-sending-history/)

クラメソさん

[https://dev.classmethod.jp/articles/setting-up-ses-event-puglishing-sent-to-kinesis-firehose-and-cloudwatch-with-terraform-v-0-8-2/](https://dev.classmethod.jp/articles/setting-up-ses-event-puglishing-sent-to-kinesis-firehose-and-cloudwatch-with-terraform-v-0-8-2/)

[https://dev.classmethod.jp/articles/amazon-ses-open-click-tracking/](https://dev.classmethod.jp/articles/amazon-ses-open-click-tracking/)