AWS Amplify, Lambda, SESで実現するなんちゃってメール配信

今回は、以下のAWSリソース
　
- Amplify
- Lambda
- SES

を使用して、なんちゃってメール配信を実現してみようと思います。

## 仕様

この記事では、以下の構成を実現します。

1. HTMLフォームで、ユーザーからメールアドレスの入力を受け付ける
1. Lambdaの関数URLにリクエストを送信する
1. LambdaからSESをコール
1. ユーザーが入力したメールアドレスに対して、メールを送信する

シンプルにメールを配信していきましょう。
ついでにカスタムドメインも取得して、独自ドメインでAmplifyをホスティングしちゃいます。

それではやっていきます。

## 実装

以下の手順で進めていきます。

1. AmplifyにHTMLファイルをアップロードする
1. route53で独自ドメインを取得
1. Amplifyにカスタムドメインを設定
1. Lambda関数を作成して、HTTPエンドポイントを設定
1. route53で取得したカスタムドメインを、SESで検証
1. LambdaにSESへのアクセス権限を付与
1. 動作確認

### 1. AmplifyにHTMLファイルをアップロード

まずは、Amplifyから用意していきましょう。
どこでも良いので、以下のHTMLファイルを用意してGithubにプッシュします。

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AWSでメール配信</title>

</head>
<body>
    <form action="https://xxxxx.lambda-url.ap-northeast-1.on.aws/" method="post">
      <input type="email" name="email" id="email" placeholder="メールアドレスを入力">
      <button type="submit">送信する</button>
    </form>
</body>
</html>
```

フォームのアクションには、Lambdaの関数URLを設定します。
現時点ではまだLmanda関数を作成していないので、Lambda関数作成後に設定してください。

Amplifyのコンソール上から新しくアプリケーションを作成します。

https://aws.amazon.com/jp/amplify/

GithubとAmplifyを接続すると、任意のブランチを選択できるようになっているはずなので、Amplifyでホスティングしたいブランチを選択。
設定が完了したら、Amplifyが発行してくれる `https://xxx.yyyy.amplifyapp.com` みたいな感じのドメインでサイトがホスティングされているはずです。

このあたりは、「Amplify ホスティング」のような感じでググるとクラスメソッドさんとかの記事がたくさん出てくるので、僕はそのあたりを見つつやりました。

こちらの記事など参考になりそうです。

https://dev.classmethod.jp/articles/amplify-console-simple-static-site/

### 2. route53で独自ドメインを取得

Amplify自体はデフォルトURLのままでも良いんですが、メール配信の際に独自ドメインを使いたいので、route53のコンソール上でドメインを取得しておきます。

https://aws.amazon.com/jp/route53/

TLDは何でもOKですが、謎の格安ドメインみたいなものを選択するとwhois情報とかを秘匿できないみたいなので、やはり `.com` が無難です。$13 で取得可能。

あと、独自ドメインはroute53以外のお名前とかのレジストラで取得しても良いんですが、DNS関連の設定変更作業が発生するので、特にこだわりがなければroute53で取得する方がシンプルで簡単だと思います。

### 3. Amplifyにカスタムドメインを設定

先ほど紹介したクラスメソッドさんの記事で、Amplifyにカスタムドメインを適用する方法が記載されています。

https://dev.classmethod.jp/articles/amplify-console-simple-static-site/#:~:text=%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81-,%E6%AC%A1%E3%81%AB%E3%82%84%E3%82%8A%E3%81%9F%E3%81%8F%E3%81%AA%E3%82%8A%E3%81%9D%E3%81%86%E3%81%AA%E3%81%93%E3%81%A8,-%E3%83%9B%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E5%BE%8C%E3%81%AB%E3%82%84

- SSLの作成
- SSL証明
- ドメインのアクティベーション

の3ステップがありますが、route53でドメインを取得している場合は特に詰まる事もなく数分で完了するはず。

![スクリーンショット 2023-03-11 14.00.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684929/fcfb86fb-3dd3-1d4b-137c-b95759be5c57.png)

route53以外でドメインを取得していたりするとSSLの設定やドメインのアクティベートで作業を行う必要が出てきてすんなりいかないケースも存在するため要注意です。

### 4. Lambda関数を作成して、HTTPエンドポイントを設定

それでは、Lambdaで関数を作成していきましょう。

https://aws.amazon.com/jp/lambda/

コンソール上から「関数の作成」を選択して、関数名や実行環境はお好みで設定。
実行ロールは「基本的な Lambda アクセス権限で新しいロールを作成」でOK。
詳細設定で、「関数URLを有効化」を設定しておきます。

作成が完了したら、先ほどのHTMLファイルのフォームアクションにラムダの関数URLを設定して、
Lambdaに「FunctionURLAllowPublicAccess」のアクセス権限を付与します。
（アクセス権限を付与しないと、AmplifyからLambdaにリクエストを送れない）

これで、AmplifyからLambda関数を実行できるようになりました。
では、メール送信の関数を作成します。

```main.py
import json
import base64
from urllib.parse import unquote

import boto3
from email.header import Header

ses = boto3.client('ses', region_name='ap-northeast-1')

def lambda_handler(event, context):
    decoded_email = base64.b64decode(event['body']).decode('utf-8')
    email = unquote(decoded_email).replace('email=', '')

    display_name = '{0}<{1}>'.format(
        Header('T-unity', 'utf-8').encode(),
        'xxx@{custom_domain_name}.com'
    )

    ses.send_email(
        Source=display_name,
        Destination={
            'ToAddresses': [email]
        },
        Message={
            'Subject': {
                'Data': 'テスト'
            },
            'Body': {
                'Text': {
                    'Data': 'メールを送信'
                }
            }
        }
    )
```

メール送信の部分に関しては、クラスメソッドさんの以下記事のプログラムをまるっと使用させていただいてます。

https://dev.classmethod.jp/articles/amazon-ses-use-my-domain-from-lambda/

メールアドレスの加工部分をちょろっと自作したくらい。
HTML標準なのかAWS側の仕様なのか分かりませんが、クライアントから平文のまま送信したメールアドレスがbase64でエンコードされた状態で渡ってきたため、デコード作業が必要でした。

とりあえず今回は深掘りせず。

<b>※関数URLのみでLambdaをパブリックに公開するのは辞めましょう。</b>

https://zenn.dev/shunsuke_suzuki/scraps/0320cc385d317a

### 5. route53で取得したカスタムドメインを、SESで検証

次にSES周りの設定をやっていきます。

https://aws.amazon.com/jp/ses/

といっても、ほぼやる事ないです。
コンソールにログインしたら、左側のサイドバーから「検証済みID」という項目を選択して、先ほど取得したカスタムドメインを設定していきます。

検索したらこちらもクラスメソッドさんの記事がありました。

https://dev.classmethod.jp/articles/amazon-ses-try-sending-email-in-sandbox-environment/

セットアップが完了するとテストメールを送信できるようになります。
セットアップ完了時点ではまだサンドボックスの状態なので、SESで検証ができたアドレスにしかメールを送信できません。

なので、さくっと受信確認ができるように、適当なgmailアドレス等を追加してIDの検証を済ませておくと良いです。
SESコンソールから送信したテストメールが届きました。

![S__44843010.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684929/9ced34ef-1147-d831-e424-3b39f7d6f708.jpeg)

これでメール配信の準備ができました。
なお、プロダクションでメール配信を実行する際には、AWS側に問い合わせをして承認してもらう必要があり、何日かかかる場合もあるみたいなので急ぎ実装が必要な場合は注意が必要そうです。

### 6. LambdaにSESへのアクセス権限を付与

現時点ではLambdaからSESにアクセスする権限がないため、Lambda関数を実行しても500エラーが返されるはずです。
なので、LambdaのIAMに権限を追加してあげます。

先ほど作成したLambda関数のコンソール上で「実行ロール」が表示されているので、そちらをクリックしてIAMのコンソールに移動。

Lambdaにアタッチされているポリシーを編集します。
特に何も設定を変更していなければクラウドウォッチ関連のポリシーのみが設定されているはずなので、SendEmail,SendRawEmailを許可。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "{ARN}:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "{ARN}:log-group:/aws/lambda/{function_name}:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
    ]
}
```

これでLambdaにSESのアクセス権限が付与されました。
ちなみに、アクセス権限がない状態だとCloudwatchにこんな感じのエラーログが出力されます。

```
[ERROR] MessageRejected: An error occurred (MessageRejected) when calling the SendEmail operation: Email address is not verified. The following identities failed the check in region AP-NORTHEAST-1: xxxxx@gmail.com
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 39, in lambda_handler
    ses.send_email(
  File "/var/runtime/botocore/client.py", line 391, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/var/runtime/botocore/client.py", line 719, in _make_api_call
    raise error_class(parsed_response, operation_name)
```

### 7. 動作確認

実際に、フォームからメールアドレスを入力して確認してみましょう。

![スクリーンショット 2023-03-12 2.13.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684929/9d5f177f-8650-ff37-2adc-436722788866.png)

届きました。

![S__44843012.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/684929/d0e5307e-6277-77db-969e-cf8b7bf1a2d6.jpeg)

この記事を書いている段階では、まだサンドボックスからプロダクションへの移行が承認されておらず、SESに登録したGmailアドレスでしか受信を確認できませんでしたが、承認されれば問題ないと思います。

## おわり

という事で、Amplify / Lambda / SES を使用した簡易的なメール配信でした。