AWS Lambda + API Gateway で作るサーバーレス問い合わせ機能

静的ページ用の問い合わせ機能を、**AWS Lambda + API Gateway (+ Amazon SES)** でサーバーレスに実装する手順を整理します。

GitHubのソースコードは[こちら](https://github.com/kei178/aws-lambda-ses-contact-form)

# 手順

設計の全体像は、クライアントから API Gateway の POST メソッドを呼び出し、それをトリガーに Lambda 関数を発火させて、SES の送信用メールアドレスで管理者にメール通知をおこなう感じです。

## 1. Amazon SES

まずは問い合わせ内容を通知するためのメールアドレスの設定です。

### メールアドレスの追加・認証
[Amazon SESの管理画面](https://ap-southeast-2.console.aws.amazon.com/ses/home?region=ap-southeast-2#verified-senders-email:)で、送信用のメールアドレスを追加し、届いたメールから認証を完了させます。

### ドメイン用 DKIM の設定

設定したアドレスからSESでメールを送信する時に、送信元アドレスとして `xxx@xxx.com via amazon.com` のように `via amazon.com` と表記されることがあります。

この `via amazon.com` を消すために DKIM の設定をします。

[Amazon SESの管理画面](https://ap-southeast-2.console.aws.amazon.com/ses/home?region=ap-southeast-2#verified-senders-email:)から送信用アドレスの詳細ページにいき、「DKIM」のCNAMEの値を入手します。

<img width="1012" alt="スクリーンショット 2020-04-23 22.33.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138789/a5d117f3-a10f-c02c-8941-a48768b710c3.png">

このCNAMEの3つをメールアドレスのドメインのDNSレコードに追加します。

## 2. AWS Lambda

続いて、問い合わせ通知の処理を AWS Lambda で実装します。

### IAMロール

まずは [IAMの管理画面](https://console.aws.amazon.com/iam/home?#/roles)で今回の Lambda に適用するロールを作成します。

ロールは以下のJSONで規定します。

```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":"*"
     }
  ]
}
```

### Lambda 関数の作成

[Lambda の管理画面](https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/functions)で新しい関数を作成します。アクセス権限には、先ほど作成したロールを割り当てます。

### index.js の更新

`index.js` を次のように更新します。`region:` や `Source` はご自身の情報に修正してください。

```js:index.js
'use strict'
const SDK = require('aws-sdk')

exports.handler = (event, context, callback) => {
    const ses = new SDK.SES({ region: 'ap-southeast-2' })
    const email = {
        // From
        Source: "YOUR_EMAIL_ADDRESS",
        // To
        Destination: { ToAddresses: [event.form.to] },
        Message: {
            Subject: { Data: event.form.subject },
            Body: {
                Text: { Data: event.form.body }
            },
        },
    };
    ses.sendEmail(email, callback);
};
```

## 3. API Gateway

最後に API Gateway で、問い合わせを受け付けるエンドポイントを作ります。

### API の新規作成

上で作成した Lambda 関数のページの「トリガーを追加」で API Gateway の REST API を新規作成します。

#### リソースの追加

作成後、同 REST API のページに自動的に移動します。

「アクション > リソースの追加」で、`/send` など、任意のリソースを追加します。

#### POST メソッドの追加

「アクション > メソッドの追加」で、追加したリソースに `POST` メソッドを追加します。

#### POST メソッドの設定

##### リクエストマッピングテンプレート

`POST` メソッドの詳細ページ「統合リクエスト > マッピングテンプレート」で `application/json` のマッピングテンプレートを追加します。テンプレートの記載内容は次の通りです。

```json:application/json
{
    "form": $input.json('$')
}
```

##### レスポンスヘッダー

`POST` メソッドの詳細ページ「メソッドレスポンス > 200 のレスポンスヘッダー」で  `Access-Control-Allow-Origin` というヘッダーを追加します。

その後、「統合レスポンス > ヘッダーのマッピング」で次のように値をセットします。

|レスポンスヘッダー            |マッピングの値       |
|:--------------------------|:------------------|
|Access-Control-Allow-Origin|'*'                |

上記設定がないと、僕の環境下では、クライアントから呼び出した時に

```
No 'Access-Control-Allow-Origin' header is present on the requested resource
```

のエラーが出ました。この `Access-Control-Allow-Origin` の設定で解決できました。

#### API のデプロイ

「アクション > API のデプロイ」で、任意のステージ（`v1` など）に REST API をデプロイします。

ステージの「URL の呼び出し」にエンドポイントの記載があります。

```
POST https://xxxxxx.execute-api.YOUR_REGION.amazonaws.com/v1/YOUR_API_NAME/send
```

このエンドポイントで作成した POST メソッドを呼び出せます。

## 4. クライアントでの呼び出し

静的ページから POST リクエストで API Gateway のエンドポイントを呼ぶことで、Lambda 関数が発火します。

jQuery で利用する場合、次のように呼び出します。

```js

function submitContact(url, to, subject, body) {
  const jsonData = JSON.stringify({
    'to':      to,
    'subject': subject,
    'body':    body
  })

  $.ajax({
    type: "POST",
    url:   url,
    async: true,
    contentType: 'application/json',
    dataType: 'json',
    data: jsonData,
    error: function(data) { 
      console.log(data)
    },
    success: function(data) {
      console.log(data)
    }
  })
}
```

# References

- [API Gateway + Lambda + SES で安価な問い合わせフォームを構築しよう(後編) | Qiita](https://qiita.com/north-code/items/042aa8c7650eb42db3b7)
- [Amazon SES の Easy DKIM | AWS](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-authentication-dkim-easy.html)
- [API Gateway CORS: no 'Access-Control-Allow-Origin' header | Stack Overflow](https://stackoverflow.com/questions/35190615/api-gateway-cors-no-access-control-allow-origin-header)