SESのトラッキングデータをKinesis経由でS3に保存するまでをTerraformで実施

# 概要

SESでの開封・クリックのトラッキングについてはいくつか記事がありますが、それをTerraformで実現したという情報はあまり見当たらないのでまとめました。

SESはトラッキングデータをCloudWatch、Kinesis Firehose、SNSに送ることができます。
今回はKinesis Firehose経由でS3に保存します。

![Untitled Diagram (1).png](https://qiita-image-store.s3.amazonaws.com/0/216010/ec658639-9bfa-6b06-4de0-f5e223a15784.png)

# SESでの開封・クリックの取得の仕組み

開封・クリックのトラッキングには、HTMLメールを使用する必要があります。
開封のトラッキングは、HTMLメールの末尾に画像が添付されることで実現されます。
クリックのトラッキングは、メール内のリンクがSESによって自動的に書き換えられ、あるURLを経由したリダイレクトとなることで実現されます。

# variable.tfの記述

variables.tfに変数を定義します。

```
variable "name_prefix" {
  default = "ses-tracking"
}

variable "s3_bucket" {
  default = "xxx"
}
```

# main.tfの記述

## Provider

SESが利用できるリージョンを指定します。

```
# ######## #
# Provider #
# ######## #

provider "aws" {
  version = "~> 1.51"
  region  = "us-east-1"
}
```

## S3

```
# ## #
# S3 #
# ## #

resource "aws_s3_bucket" "bucket" {
  bucket        = "${var.s3_bucket}"
  acl           = "private"
  force_destroy = true
}
```

## CloudWatch Logs

Kinesis FirehoseのエラーログをCloudWatch Logsに出力するよう、ロググループとログストリームを作成します。

```
# ############### #
# CloudWatch Logs #
# ############### #

resource "aws_cloudwatch_log_group" "cloudwatch_log_group" {
  name = "${var.name_prefix}-cloudwatch-log-group"
}

resource "aws_cloudwatch_log_stream" "cloudwatch_log_stream" {
  name           = "${var.name_prefix}-cloudwatch-log-stream"
  log_group_name = "${aws_cloudwatch_log_group.cloudwatch_log_group.name}"
}
```

## Kinesis Firehose

S3とCloudWatch Logの権限を与えて、Kinesis Firehoseを作成します。
動作確認を素早く行うため、buffer_intervalを最短の60に設定します。

```
# ####### #
# Kinesis #
# ####### #

resource "aws_kinesis_firehose_delivery_stream" "firehose" {
  name        = "${var.name_prefix}-kinesis-firehose"
  destination = "s3"

  s3_configuration {
    role_arn        = "${aws_iam_role.firehose_role.arn}"
    bucket_arn      = "${aws_s3_bucket.bucket.arn}"
    buffer_size     = 5
    buffer_interval = 60
    cloudwatch_logging_options {
      enabled = true
      log_group_name = "${aws_cloudwatch_log_group.cloudwatch_log_group.name}"
      log_stream_name = "${aws_cloudwatch_log_stream.cloudwatch_log_stream.name}"
    }
  }

}

resource "aws_iam_role" "firehose_role" {
  name               = "${var.name_prefix}-firehose-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "firehose.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "firehose_role_policy" {
  name   = "${var.name_prefix}-firehose-role-policy"
  role   = "${aws_iam_role.firehose_role.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:GetBucketLocation",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:ListBucketMultipartUploads",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::${aws_s3_bucket.bucket.bucket}",
        "arn:aws:s3:::${aws_s3_bucket.bucket.bucket}/*"
      ]
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "logs:PutLogEvents"
      ],
      "Resource": [
        "${aws_cloudwatch_log_stream.cloudwatch_log_stream.arn}:*"
      ]
    }
  ]
}
EOF
}
```

## SES Configuration Set

Kinesis Firehoseの権限を与えて、SESのConfiguration SetとEvent Destinationを作成します。
matching_typesは必要なものだけ指定しましょう。
メール送信時にこのConfiguration Setを利用することで、トラッキングが行われます。

```
resource "aws_ses_configuration_set" "ses_configuration_set" {
  name = "${var.name_prefix}-ses-configuration-set"
}

resource "aws_ses_event_destination" "kinesis" {
  name                   = "${var.name_prefix}-event-destination-kinesis"
  configuration_set_name = "${aws_ses_configuration_set.ses_configuration_set.name}"
  enabled                = true

  matching_types         = ["send", "reject", "bounce", "complaint", "delivery", "open", "click", "renderingFailure"]

  kinesis_destination = {
    stream_arn = "${aws_kinesis_firehose_delivery_stream.firehose.arn}"
    role_arn   = "${aws_iam_role.ses_role.arn}"
  }

  depends_on = ["aws_iam_role_policy.ses_role_policy"]
}

resource "aws_iam_role" "ses_role" {
  name               = "${var.name_prefix}-ses-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "ses.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "ses_role_policy" {
  name   = "${var.name_prefix}-ses-role-policy"
  role   = "${aws_iam_role.ses_role.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Action": [
        "firehose:PutRecord",
        "firehose:PutRecordBatch"
      ],
      "Resource": "${aws_kinesis_firehose_delivery_stream.firehose.arn}"
    }
  ]
}
EOF
}
```

# Terraform実行

```bash
$ terraform init
$ terraform apply
```

※ aws_ses_event_destinationの作成に失敗することがあります。その場合、再度 `terraform apply` を実行してください。

# 送信テスト

AWS CLIを使った以下のシェルスクリプトで送信テストができます。
Configuration Setを指定するのがポイントです。
前述の通り、開封・クリックのトラッキングが可能なのはHTMLメールのみです。
tagsについては、あとでデータを分析する際に利用できます。

```bash
#!/bin/bash -eux

TO=xxx
FROM=xxx

SUBJECT='TEST'
TEXT='hello world'
HTML="<h1>hello world</h1><a href='https://www.amazon.co.jp'>Amazon</a>"

CONFIGRATION_SET='ses-tracking-ses-configuration-set'

aws --region us-east-1 ses send-email \
    --to $TO \
    --from $FROM \
    --subject $SUBJECT \
    --text $TEXT \
    --configuration-set-name $CONFIGRATION_SET \
    --tags Name=tag1,Value=value1 \
    --html "$HTML"
```

# S3に溜まったデータ

S3には以下のようにデータが保存されていきます。

```
{"eventType":"Send","mail":...
{"eventType":"Click","mail":...
{"eventType":"Open","mail":...
{"eventType":"Delivery","mail":...
```