受信用のメールアドレスが欲しかったのでサーバレスで構築した方法

# はじめに

メールの送信履歴をたまたま見たら、以外にメールを送信していないなと気づいた。
確かにメールを普段使っているかというと使っていない。
普段のコミュニケーションを何でとるかといったら、**Slack**。
エビデンスを残す必要があるようなものは、**Backlog**や**Redmine**などでチケット単位でコミュニケーションを取ることが大半。
では、どんな時にメールを使っているかというと、ほぼ、**サービスへの登録の本人確認**。
でも、このサービスへの登録が本当に重要。
ただ、サービスへの無料体験などに申し込み、体験期間が過ぎたけど、まだやりたいなといった場合、既に登録されているメールでは、再度申し込むことが出来ないことは多々。

そんな時どうしますか？

gmailであればメールアドレスのユーザ部分の任意の箇所にドット（ `.` ）を追加することが可能で、そのドットのタイミングを変えて利用している人も多いと思います。
ただ、それだと限界もあります。
また新しいメールを取得するにしても、今は電話番号で本人確認があったりとちょっと面倒。

例えば、ドメインを取得すれば好きなだけユーザを作成することが出来ますが、メールサーバをAWSで運用するとなると、EC2インスタンスを立ち上げる必要があり、そこまでコストをかけて、、、という思いもあります。

# 対象読者

- サービス登録用やテスト用など、メールアドレスがほしい。
- 特にメールを送信する訳ではなく、受信出来ればいい。
- メールサーバを運用するつもりはない。

という方の参考になれば。

# どうしたか

- ドメインは取得した。Route53で運用。**.com** であれば年$12
- メールはSESで受信。SESのルールで自ドメイン宛のアドレスをS3へ保存すると同時に、SNSに通知。
- SNSのサブスクリプションでLambdaを起動。起動したLambdaでは、通知からS3に保存したメール本文を取得し、SESで指定したメールアドレスに転送。

![ses-forwarder-v2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/c9d513aa-7d03-c3f9-12a9-7485fe6aea33.png)

# サービス設定

## S3

**S3 bucket policy**

バケットポリシーでは、LambdaからS3のオブジェクトを取得できる権限（PutObject）を許可します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ses.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::ses-forwarder-stack-bucket-bks5djn2hcl1/*",
            "Condition": {
                "StringEquals": {
                    "aws:Referer": "123456789012"
                }
            }
        }
    ]
}
```

## SNS

**Subscription**

トピックは適当に作成してください。サブスクリプションでは、後述するLambdaを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/d6e282d0-c896-ed65-ec48-8a04cecf4cec.png)

## SES

**Identity Management** > **Domains**

Route53等でドメインのホストゾーンが登録されていることを前提としています。
こちらでメールのドメインを登録すると、登録する過程でMXレコードや、DKIM用のレコードを作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/ba482be6-1d06-b6cc-b50b-94a16a48f054.png)

**Identity Management** > **Email Addresses**

SESから送信先として許可するメールアドレスを登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/f9e00bee-4d65-90fc-5511-626bff2b330d.png)

**Email Receiving** > **Rule Sets**

受信したメールをどう処理するかの設定です。
S3へ配置し、SNSへ通知をするように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/6f28c383-614f-11d2-be1d-b305a6c15940.png)

## Lambda

**Function code**

```python
from email import message_from_file
import json
import boto3
import re
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

DISABLED_MESSAGE_IDS = ['AMAZON_SES_SETUP_NOTIFICATION']

def lambda_handler(event, context):

    logger.info(json.dumps(event))
    logger.debug(event.get('Records')[0].get('Sns').get('Message'))
    message = json.loads(event.get('Records')[0].get('Sns').get('Message'))
    message_id = message.get('mail').get('messageId')
    if message_id in DISABLED_MESSAGE_IDS:
        logger.info('Skip Invalid messageID. (%s)' % message_id)
        return
    notification_type = message.get('notificationType')
    destinations = message.get('mail').get('destination')
    if not destinations:
      destinations = message.get('receipt').get('recipients')
    bucket_name = message.get('receipt').get('action').get('bucketName')
    object_key = message.get('receipt').get('action').get('objectKey')

    mail_from = destinations[0] if not os.environ['MAIL_FROM'] else os.environ['MAIL_FROM']
    forward_to = os.environ['RCPT_TO'].split(",")

    # get s3 object
    s3 = boto3.client('s3')
    response = s3.get_object(
        Bucket = bucket_name,
        Key    = object_key
    )

    # change mail headers
    raw_mail = response['Body']
    msg = message_from_file(raw_mail)

    del msg['DKIM-Signature']
    del msg['To']
    del msg['From']
    del msg['Return-Path']

    msg['To'] = ", ".join(forward_to)
    msg['From'] = mail_from
    msg['Return-Path'] = mail_from

    # send mail
    ses = boto3.client('ses')
    response = ses.send_raw_email(
        Source = mail_from,
        Destinations = forward_to,
        RawMessage = {
            'Data': msg.as_string()
        }
    )
    logger.info(json.dumps(response))
    return response
```

**Execution role**

AWSLambdaBasicExecutionRoleのマネージドポリシー以外に、以下のような`s3:GetObject`、`ses:SendRawEmail`の実行ポリシーをサービスロールに付与

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}
```

**Environment variables**

- MAIL_FROM : 転送する際の送信元メールアドレス
- RCPT_TO : 転送する際の宛先メールアドレス

# さいごに

これでSESのルールで指定したドメイン宛に適当なユーザ名で送信したメールが、Lambdaの環境変数 **RCPT_TO** に指定した宛先に転送します。
転送する際の送信元は、Lambdaの環境変数 **MAIL_FROM** で指定したアドレスになりますが、未設定であれば、元の宛先が送信元になります。

これで、適当なメールアドレスのためにユーザを作成する必要がなく、例えば、アカウントのユーザ登録をしようとした場合に、そのまま適当なユーザ名のメールアドレスを利用することが可能です。

CloudFormationの[テンプレート](https://github.com/ot-nemoto/ses-forwarder)を起こしています。手順の通りデプロイして貰えれば、すぐに使えるとは思います。
参考までに。