S3とLambda、SESを用いてお問合せフォームを作成した話

前回はS3とRoute 53、CloudFrontで静的なHPの作成を行いました。
今回は前回のものによくあるお問い合わせフォームを組み合わせ、お問い合わせフォームからメールが送られるようにします。
２回に分けて記事を書かせていただきます。
前回時点でS3より左側の設定までは完了している部分です。
今回はS3から右側の設定を行い、次回S3と今回の部分をつなげます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/42f486e3-0bd8-4fab-cd2a-903e27b33f94.png)

※2024/6/25：Lambdaのコード修正しました。

## Amazon Simple Email Service
SESはクラウドベースのメールサービスです。
無料枠もありますが今回はS3からのメール送信となりますので有料になります。1,000通あたり$0.10になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/17218b4a-cb8b-22f8-ef80-a08b51896cb9.png)

### メールアドレスの登録
まずはIDを作成より、受信元となるメールアドレスの登録をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/b50f80a6-00bd-b497-95fc-5398d7427bd7.png)

今回はEメールアドレスで作成します。
Eメールアドレスを選択後、アドレス入力欄に受信元のアドレスを入力してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/29bbaa5b-9df4-b9e9-205f-88fc34cde6b3.png)

登録後、確認のメールが届きます。
内容に従い、メールアドレスを有効化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/9cf5d673-e11c-4adc-b531-6b278712faa5.png)

画像のようにステータスが検証済みになっていれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/6cc30ae6-2fe6-bc18-0e54-950246b7bfaa.png)

## Lambda
Lambdaはイベントごとにあらかじめ登録したプログラムを実行するサービスです。

### 関数の作成
まずは関数の作成より関数を作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/ac032ace-07c0-6f7d-a1e4-37e94b894a73.png)

今回は一から作成のままで進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/5138af5e-626f-dd45-6c36-007d40e8ada5.png)

関数名の入力とランタイムの選択をします。
関数名はお好きなものを入力で問題ないです。
ランタイムはPythonを選択します。
選択後、関数の作成を押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/93b7a2f2-02a6-30ee-aa42-cf010148e24e.png)

下記の画面に遷移するかと思います。
このまま、コードの登録まで行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/b3b3b1c6-d4a2-deb4-c179-1d35f8c7d4c0.png)

続いてコードを入力します。
今回はChat GPTで作成したものを多少アレンジしています。
入力後、デプロイを押下して反映させます。
※本来、メールアドレスなどは環境変数に書くべきですが今回はテストなのでじかがきしています

※2024/06/25更新：ログ出力のために修正しています。

``` Python:emailSender.py
import json
import boto3
import os

def lambda_handler(event, context):
    try:
        print(f"Event: {event}")
        try:
            form_data = event
        except json.JSONDecodeError as e:
            print(f"JSON decode error: {str(e)}")
            raise ValueError("Invalid JSON format")

        print(f"Form data: {form_data}")

        name = form_data.get('name')
        email = form_data.get('email')
        message = form_data.get('message')

        if not name or not email or not message:
            raise ValueError("Missing one or more required fields")

        ses_client = boto3.client('ses', region_name='us-east-1')

        email_address = os.environ.get('SEND_EMAIL')

        if email_address is None:
            raise ValueError("Environment variable SEND_EMAIL is missing")

        email_address = email_address.strip()

        body = f"""
        Name:           {name}
        Email Address: {email}
        Message:       {message}
        """

        response = ses_client.send_email(
            Source=email_address,
            Destination={
                'ToAddresses': [email_address]
            },
            Message={
                'Body': {
                    'Text': {
                        'Charset': 'UTF-8',
                        'Data': body
                    }
                },
                'Subject': {
                    'Charset': 'UTF-8',
                    'Data': 'Contact Form Submission'
                }
            }
        )

        print(f"SES Response: {response}")

        return {
            'statusCode': 200,
            'body': json.dumps('Email sent successfully.')
        }
    except ValueError as ve:
        print(f"Value Error: {str(ve)}")
        return {
            'statusCode': 400,
            'body': json.dumps(f'Error: {str(ve)}')
        }
    except Exception as e:
        print(f"Error: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f'An error occurred: {str(e)}')
        }

```

### アクセス権限の設定

続いてアクセス権限の設定を行います。
設定タブよりアクセス権限を開きます。
実行ロールの下にこの関数のロールが表示されているので、このロールにメールを送信するための権限を付与していきます。
ロールを押下し、画面を遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/1a7b7199-16fe-a6a5-c822-9c3ac1053295.png)

許可ポリシーよりインラインポリーを作成を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/1b49a915-741a-236c-4fbe-dc732a839f06.png)

JSON形式でポリシーを追加します。
以下のJSONを記入します。
ポリシーの中身はこの記事を参考にしています

https://qiita.com/kskinaba/items/c692cadbe57121546413

```JSON
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"logs:CreateLogGroup",
				"logs:CreateLogStream",
				"logs:PutLogEvents"
			],
			"Resource": "arn:aws:logs:*:*:*"
		},
		{
			"Effect": "Allow",
			"Action": [
				"ses:Send*"
			],
			"Resource": "*"
		}
	]
}
```

次へを押下し、ポリシーの作成を押下し完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/49d77e23-cc12-4baf-c402-441cc0f651d9.png)

ここまで作成できたので一度Lamdaからメールが送信できるのか確認します。
下記のJSONをテスト用のメッセージとします。
```JSON
{
  "name": "テスト",
  "email": "test@test.com",
  "message": "テストメッセージです。"
}
```
無事に届いていれば完了です。



## API Gateway
API Gatewayの設定を行います。実際のお問い合わせフォームからのPOST内容をLambdaに渡す役割をしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/a5831ab6-a7b7-d78b-16dd-b645ba7b1aa1.png)

### APIの作成

今回はREST APIで作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/aa0a9233-8b94-7943-bff8-76563e112054.png)

好きなAPI名を入力し新しいAPIを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/230610a3-9fad-b97b-7796-d8a9d4fff4c4.png)

続いてメソッドを作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/03aa7d96-71f0-43d9-97a5-7ddb82925f30.png)

今回はフォームからのPOSTをLambda関数に流すので、メソッドタイプをPOST、統合タイプをLambda関数を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/1f08d77a-9c81-15b5-9baf-0a2112ef91ac.png)

Lambda関数は先ほど作成した関数を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/36361a9f-e774-f091-7ceb-79de7f9375f8.png)

ここまで完了したので再度テストを行います。
テストのタブに移動し、リクエスト本文に先ほどと同じJSONの内容を書き込みます。
入力後、テストを押下します。
無事にメールが届いていれば完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/eefb4c30-6667-2cf0-f16e-6ca8d42a0d4c.png)

続いてCORSの有効化を行い、APIを叩ける状態にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/b64c7ed8-a56f-471b-8733-e5a244e61230.png)

POSTにチェックを入れ保存を押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/bcba40e6-519c-7fc5-6e25-cab567391f5e.png)

最後にデプロイを行います。
任意のステージ名を入力し、デプロイします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273072/3d326de8-2654-dc3e-4653-5d57a5e95001.png)


# おわり

今回はAPI Gatewayから先の設定を行いました。
実は何回か躓いきましたが何とか形になりました。
次回はJSのコードを書き、今回のものと組み合わせ、お問い合わせフォームを作成をします。

https://qiita.com/sakura11054/items/678a3b45807999f63b75