テンプレートを使用してSES でメールを送信してみた

# 背景・目的
以前、LambdaからSESでメールを送信しました。今回はメールテンプレートを使用して送信してみます。

https://qiita.com/zumax/items/c94455e7abb7462e3ccd

# まとめ

下記に特徴をまとめます。

|特徴|説明|
|:--|:--|
|SESのテンプレート|下記の2つがある<BR>・保存済みテンプレート<BR>・インラインテンプレート|
|ストアドテンプレート|SES v2 APIのCreateEmailTemplateオペレーションを使用してSESで作成されたTemplateリソース<BR>テンプレートには、書き込まれたコンテンツとインラインで変数を含むメールの件名と本文が含まれる|
|インラインテンプレート|Templateリソースは使用しない<BR>変数を使用して、APIを呼び出すときにインラインで提供される|
|ストアドテンプレートの制限事項|リージョンあたり、最大20,000このテンプレートが作成可能<BR>500KBサイズまで|
|インラインテンプレートの制限事項|JSON ファイルは、テキスト部分と HTML 部分の両方を含めて、最大 1 MBまで|

# 概要
下記を基に整理します。

https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-personalized-email-api.html

> Amazon SES では、保存されたテンプレートを使用するか、インラインテンプレートを使用して、テンプレート化された E メールを送信できます。

> - 保存済みテンプレート – Amazon SES v2 API の CreateEmailTemplateオペレーションを使用して SES で作成および保存されるTemplateリソースを指します。 Amazon SES テンプレートには、書き込まれたコンテンツとインラインで変数 (プレースホルダー) を含む E メールの件名と本文が含まれています。ストアドテンプレートの名前とテンプレート内のプレースホルダー変数への動的データは、 SendEmailまたは v2 API SendBulkEmail オペレーションを呼び出すときに提供されます。
保存されたテンプレートは簡単に再利用でき、同様の種類の E メールを送信する際の時間と労力を節約できます。各 E メールをゼロから作成する代わりに、基本構造と設計を一度作成するだけで、テンプレート内の動的コンテンツを更新できます。

> - インラインテンプレート – Templateリソースは使用されませんが、変数 (プレースホルダー) を含む E メールの件名と本文は、これらのプレースホルダー変数の値とともに、 SendEmailまたは v2 API SendBulkEmail オペレーションを呼び出すときにインラインで提供されます。
インラインテンプレートは、SES アカウントでテンプレートリソースを管理する必要がなくなり、アプリケーションロジック内にテンプレートコンテンツを直接含めることができるため、統合プロセスを簡素化することで、一括 E メールを送信するプロセスを合理化します。あたりの 20,000 テンプレートの制限にはカウントされません AWS リージョン。

- SESでは、下記のテンプレートを使用してテンプレート化されたメールを送信できる
    - 保存済みテンプレート
    - インラインテンプレート

- 保存済みテンプレート
    - SES v2 APIのCreateEmailTemplateオペレーションを使用してSESで作成されたTemplateリソース
    - テンプレートには、書き込まれたコンテンツとインラインで変数を含むメールの件名と本文が含まれる
- インラインテンプレート
    - Templateリソースは使用しない
    - 変数を使用して、APIを呼び出すときにインラインで提供される

> ストアドテンプレートを使用する場合、次の制限が適用されます。

> - それぞれに最大 20,000 個の E メールテンプレートを作成できます AWS リージョン。
> - 各テンプレートは、テキストと HTML パートの両方を含めて、最大 500 KB のサイズまで可能です。

- ストアドテンプレートの制限事項
    - リージョンあたり、最大20,000このテンプレートが作成可能
    - 500KBサイズまで


> インラインテンプレートを使用する場合、次の制限が適用されます。
> - 各入力 JSON ファイルは、テキスト部分と HTML 部分の両方を含めて、最大 1 MB のサイズにすることができます。

- インラインテンプレートの制限事項
    - JSON ファイルは、テキスト部分と HTML 部分の両方を含めて、最大 1 MB

> 以下は、保存済みテンプレートとインラインテンプレートの両方に適用されます。
> - 使用できる置換変数の数に制限はありません。
> - SendBulkEmail オペレーションの呼び出しごとに、最大 50 個の送信先オブジェクトに E メールを送信できます。Destination オブジェクトには、ToAddresses、CcAddresses、および BccAddresses で定義された複数の受信者を含めることができます。v2 API への 1 回の呼び出しで問い合わせることができる送信先の数は、アカウントの最大送信レートによって制限される場合があります。詳細については、「Amazon SES 送信制限の管理」を参照してください。

- 置換変数の数に制限はない
- SendBulkEmail オペレーション
    - 呼び出しごとに最大 50 個の送信先オブジェクトに E メールを送信できる
- Destination オブジェクトには、下記で定義した複数の受信者を含めることができる
    - ToAddresses
    - CcAddresses
    - BccAddresses
- v2 API への 1 回の呼び出しで問い合わせることができる送信先の数は、アカウントの最大送信レートによって制限される場合がある


# 実践
## ストアドメールテンプレート
下記を基に試します。

https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-personalized-email-api.html

### IAMロールの修正
1. こちらで作成した[ロール](https://qiita.com/zumax/items/c94455e7abb7462e3ccd#iam%E3%83%AD%E3%83%BC%E3%83%AB)を開きます
1. ポリシーの編集をクリックし`ses:SendTemplatedEmail`を追加します。修正後のポリシーは下記のとおりです
    ```json
    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Effect": "Allow",
    			"Action": [
    				"ses:SendEmail",
    				"ses:SendRawEmail",
    				"ses:SendTemplatedEmail"
    			],
    			"Resource": "*"
    		}
    	]
    }
    ```



### メールテンプレートの作成
1. スケルトンを作成します
    ```json
    templates % aws sesv2 create-email-template --generate-cli-skeleton > mytemplate.json
    templates % cat mytemplate.json 
    {
        "TemplateName": "",
        "TemplateContent": {
            "Subject": "",
            "Text": "",
            "Html": ""
        }
    }
    templates % 
    ```
1. 動的に埋める値をセットします
    ```json
    templates % cat mytemplate.json 
    {
        "TemplateName": "MyTemplate",
        "TemplateContent": {
            "Subject": "Greetings, {{name}}!",
            "Text": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}.",
            "Html": "<h1>Hello {{name}},</h1><p>Your favorite animal is {{favoriteanimal}}.</p>"
        }
    }
    templates % 
    ```

    - Subject：メールの件名
    - Html：HTML本文
    - Text：テキスト本文（HTMLメールが表示できない受信者にはこちらが利用される）
1. CLIで登録します
    ```bash
    templates % aws sesv2 create-email-template --cli-input-json file://mytemplate.json 
    templates % 
    ```

### 登録後の確認
#### CLI
1. 登録後のテンプレート一覧を確認します
    ```json
    templates % aws sesv2 list-email-templates   
    {
        "TemplatesMetadata": [
            {
                "TemplateName": "MyTemplate",
                "CreatedTimestamp": "2025-02-24T18:03:25.049000+09:00"
            }
        ]
    }
    templates % 
    ```
1. 登録したテンプレートを確認します
    ```json
    templates % aws sesv2 get-email-template --template-name MyTemplate
    {
        "TemplateName": "MyTemplate",
        "TemplateContent": {
            "Subject": "Greetings, {{name}}!",
            "Text": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}.",
            "Html": "<h1>Hello {{name}},</h1><p>Your favorite animal is {{favoriteanimal}}.</p>"
        }
    }
    templates % 
    ```
#### 確認
1. AWSにサインインします
1. SESに移動し、ナビゲーションペインでEメールテンプレートをクリックします
1. 登録されました。しかし中身は確認できないようです。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c3dba801-665f-4598-9ab3-0994f1cd238b.png)

### Lambda関数の作成
1. Lambdaに移動します
1. 「関数の作成」をクリックします
1. 下記を入力します
    - 関数名：任意
    - ランタイム：Python3.13
    - アーキテクチャ：arm64
    - 実行ロール：上記で作成したIAMロール

1. 下記のコードをデプロイします
    ```py
    import boto3
    from botocore.exceptions import ClientError
    
    def lambda_handler(event, context):
        ses_client = boto3.client('ses', region_name='ap-northeast-1')
        template_name = 'MyTemplate'
        sender = 'sender@example.com'
        recipient = 'recipient@example.com'
        template_data = '{ "name":"Taro Yamata" ,"favoriteanimal":"alligator"}'
    
        try:
            response = ses_client.send_templated_email(
                Source=sender,
                Destination={
                    'ToAddresses': [recipient],
                },
                Template=template_name,
                TemplateData=template_data,
            )
        except ClientError as e:
            print(e.response['Error']['Message'])
        else:
            print("Email sent! Message ID:"),
            print(response['MessageId'])
    ```

### テスト
1. 「Test」をクリックします
1. 受信ボックスに届きました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/13289267-b840-43f5-b677-3b214bedcc26.png)


## インラインテンプレート
テンプレートコンテンツを送信 API リクエストに直接含めることで、統合プロセスが簡素化され、個別のテンプレートリソースを作成して維持する必要はありません。

### Lambdaレイヤーの追加
::: note info
boto3 ライブラリのバージョンが古いため、Amazon SESのインラインテンプレート機能に対応していない可能性があったため、設定しました。
:::

#### ローカルでパッケージ化
1. ローカルでディレクトリを作成します
    ```bash
    % mkdir -p my_layer/python
    % 
    ```
1. pipを使用して、pythonディレクトリに最新のboto3とbotocoreをインストールします
    ```bash
    % pip install boto3 -t my_layer/python
    % pip install botocore -t my_layer/python
    % 
    ```
1. レイヤーのパッケージ化
    ```bash
    cd my_layer
    zip -r ../my_layer.zip .
    ```

#### AWSでレイヤーの追加
1. AWSにサインインします
1. Lambdaに移動します
1. ナビゲーションペインで「レイヤー」をクリックします
1. 「レイヤーの作成」をクリックします
1. アップロードします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/ef27f79c-20b2-4423-900c-616c36a28b1f.png)

    
### Lambda関数の作成
1. Lambdaに移動します
1. 「関数の作成」をクリックします
1. 下記を入力します
    - 関数名：任意
    - ランタイム：Python3.13
    - アーキテクチャ：arm64
    - 実行ロール：上記で作成したIAMロール

1. 下記のコードをデプロイします
    ```py
    import boto3
    import json
    from botocore.exceptions import ClientError

    def lambda_handler(event, context):

        # SESクライアントの作成
        ses_client = boto3.client('sesv2', region_name='ap-northeast-1')
        
        # 送信者と受信者のメールアドレス
        sender = 'sender@example.com'
        recipient = 'recipient@example.com'
        
        # テンプレートの内容
        template_content = {
            'Subject': 'こんにちは、{{name}}さん',
            'Text': 'こんにちは、{{name}}さん。ご利用ありがとうございます。',
            'Html': '<h1>こんにちは、{{name}}さん</h1><p>ご利用ありがとうございます。</p>'
        }
        
        # テンプレートデータ
        template_data = {
            'name': 'Taro Yamata'
        }
        
        try:
            # メールの送信
            response = ses_client.send_email(
                FromEmailAddress=sender,
                Destination={
                    'ToAddresses': [recipient],
                },
                Content={
                    'Template': {
                        'TemplateContent': template_content,
                        'TemplateData': json.dumps(template_data)
                    }
                }
            )
            print("Email sent! Message ID:", response['MessageId'])
        except ClientError as e:
            print("Error sending email:", e.response['Error']['Message'])
    ```
1. レイヤーの追加をクリックします
1. 作成したレイヤーを指定して、「追加」をクリックします


### テスト
1. 「Test」をクリックします
1. 受信ボックスに届きました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/ae78a32d-e20d-4380-93aa-c036abe26dc2.png)

# 考察
今回、SESのストアドメールテンプレートとインラインテンプレートを試しました。
インラインテンプレートにより、事前のテンプレート登録が不要となり、効率的とのことですが個人的にはストアドのほうがアプリと静的なコンテンツが分離できるので便利かと思いました。

引き続き、他の機能も試してみます。

# 参考
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/send-personalized-email-api.html

https://docs.aws.amazon.com/cli/latest/reference/sesv2/create-email-template.html