AWS Lambdaで静的サイトにメール送信フォームを作る簡単な方法

AWS Lambdaで静的サイトにメール送信フォームを作る簡単な方法
本稿では個人的に作ったLambda用メール送信スクリプトを使ってメール送信フォームを作る方法をご紹介する。手っ取り早く知りたい方はGitHubに公開したソースコードをご覧頂きたい。
今はgitさえ使えればGitHub PagesやNetlifyで無料で静的なサイトを公開できる。便利な世の中だ。静的サイトとはサーバサイドを含まない単純なサイトのこと。自分のホームページやMarkdownノートアプリのサイトも全部Netlifyで配信している。速いし安定していてすごく気に入っている。

静的サイトはサーバサイドを含まないので、自前で動的にページを生成したりフォームを設置することが出来ない。でもちょこっとだけそういう事がしたいというユースケースがあると思う。例えばホームページに問い合わせフォームを設置するとか。問題は、わざわざそのためだけにNetlifyの有料機能を使ったりサーバを設置するのは大げさだし面倒という点。いつくるか分からない問い合わせのためだけに、ずっとサーバをスタンバイさせるのは無駄。
サーバレスアーキテクチャはこういう「ちょい足し用途」にとても適している。必要な時に必要なリソースだけ割り当ててサーバを動かせる。例えばAWS Lambdaは動作時間と割当リソースのサイズで料金が決まるので、待っている間は料金がかからない。

今回、AWS Lambdaを使って自分のホームページに問い合わせフォームを設置してみた。ソースコードをGitHubに公開したので、これを元にすればすぐにあなたもメール送信APIが作れる。AWS Lambdaを使ってみたいという人には良いチュートリアルになると思う。

Lambdaは処理ごとに “function” を作って呼び出して使う。functionそれ自体はAWS上で動く単なるスクリプト。node.jsやJava、C#、Pythonが動かせる。SES(Simple Email Service)はAWSが提供するメール送信サービス。後述するけど簡単にメールが送れるので、構える必要はない。Lambda functionからこのSESを通じてメールを送信する。ただし、Lambda functionはデフォルトでは外部に公開されないので、自身のサイトから呼び出すにはHTTPエンドポイントを付けてやる必要がある。そしてAPI Gatewayがそれを担う。以上のアーキテクチャの概略を上図に示した。
このLambda functionはAWS標準で提供されている方法だととても煩雑で管理しづらい。その問題を解決するためにapexというツールを使う。apexの概要は以下の記事が参考になるだろう。
apexをインストールする:
<span id="6d56" class="ri pi io re b gz rj rk m rl rm">curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh</span>
次に、AWSのアクセスキーが環境にセットアップ済みであることを確認する。apexは以下の環境変数を使用する。
- AWS_ACCESS_KEY_ID — AWSアカウントアクセスキー
- AWS_SECRET_ACCESS_KEY — AWSアカウントシークレットキー
- AWS_REGION — AWSリージョン
もしまだされていなければ、こちらを参考に設定する。次に、apexプロジェクトの初期化を行う。今回使用するスクリプト群をgitでcloneして、その中で初期化する:
<span id="6bab" class="ri pi io re b gz rj rk m rl rm">git clone <a class="ag hb" href="mailto:git@github.com" rel="noopener ugc nofollow" target="_blank">git@github.com</a>:craftzdog/send-email-lambda.git<br></br>cd send-email-lambda<br></br>apex init<br></br>> Project name: send-email</span>
プロジェクト名は send-emailとする。するとapexがプロジェクト名に倣ってIAM roleなどを自動で作成してくれて、project.json というファイルが生成される。このファイルを以下のように編集する。
<span id="2f86" class="ri pi io re b gz rj rk m rl rm">{<br></br> "name": "send-email",<br></br> "description": "Simple email transmitter",<br></br> "memory": 128,<br></br> "timeout": 5,<br></br> "environment": {},<br></br> "runtime": "nodejs6.10",<br></br> "role": "<YOUR_IAM_ROLE>"<br></br>}</span>
これでLambdaを使う準備が出来た。
Lambdaを使う準備ができたら、次にメールを送るための準備をする。まずはAWS SESで使いたいメールアドレスを認証して登録する。以下のように、SESの管理画面で “Verify a New Email Address” というボタンを押下する。

AWSから認証メールが飛んでくるので、メール内に書かれているリンクを開けば登録が完了。これでSESからそのアドレスを使ってメールが送れるようになった。
次に、Lambda functionからそのアドレスで送るように設定する。 functions/submit/function.json というファイルを開いて以下のように編集する。
<span id="df6c" class="ri pi io re b gz rj rk m rl rm">{<br></br> "environment": {<br></br> "SES_REGION": "us-west-2",<br></br> "FROM_NAME": "Craftzdog Contact Form",<br></br> "FROM_EMAIL": "<YOUR_AUTOMATED_EMAIL_SENDER>",<br></br> "TO_EMAIL": "<EMAIL_TO_RECEIVE>"<br></br> }<br></br>}</span>
- SES_REGION: SESを使うリージョン。Oregonならus-west-2。
- FROM_NAME: 送り元の名前。 “俺の問い合わせフォーム” とか。
- contact@example.com
- TO_EMAIL: 問い合わせを受け取りたいメールアドレス
Lambdaを使って誰かに好き放題されないように、標準では最低限の権限しかapexのIAMロールに与えられていない。SESを使いたいので、その権限を与えるポリシーを追加する。
IAMの管理画面に行って、apexが作成したロールを見つける。以下の画像ではロールの名前が contact-form_lambda_function となっているけど、あなたは send-email_lambda_function となっているはず。

“Create Role Policy”というボタンを押して、以下ようなポリシーを作成する。名前は send-email_submit にする。
<span id="8931" class="ri pi io re b gz rj rk m rl rm">{<br></br> "Version": "2012-10-17",<br></br> "Statement": [<br></br> {<br></br> "Sid": "Stmt1504526549000",<br></br> "Effect": "Allow",<br></br> "Action": [<br></br> "ses:SendEmail"<br></br> ],<br></br> "Resource": [<br></br> "*"<br></br> ]<br></br> }<br></br> ]<br></br>}</span>
これで送信する準備が整った。早速送ってみよう。以下のコマンドで直接Lambda functionを呼び出せる。
<span id="f994" class="ri pi io re b gz rj rk m rl rm">echo -n '{ "subject": "hello", "body": "world" }' | apex invoke submit</span>
件名が “hello” 本文が “world” のメールがあなたの受信ボックスに届いたら成功。おめでとう!もし来なければ、以下のコマンドでログを確認する。
<span id="da91" class="ri pi io re b gz rj rk m rl rm">apex logs -f</span>
エラーメッセージを読んで、正しくロールにポリシーが適用されているか、AWSのリージョンを間違えていないか、送信元メールアドレスは正しくSESに認証されているかなどを確認する。
さて、Lambda functionが用意できたがこれをサイトから呼び出せるようにしたい。そのためにAPI GatewayでHTTPエンドポイントを作成する。日本語だとこのあたりが参考になる。
/submit に対する POST メソッドのリクエストを受け取って、 Lambda functionを呼び出すように設定する。
“Create API” でAPIを新規作成する。名前は “my-awesome-send-email-api”とか。

Resoueces セクションにて、 “Actions” -> “Create Resource” を選択して /submit リソースを作成する。この時、 “Enable API Gateway CORS” にチェックを入れる。
作成した /submit エンドポイントに対して、 POST メソッドを受け付けるようにする。 “Actions” -> “Create Method” でメソッドを作成する。

上図のようにLambda functionを呼び出すように設定する。

準備ができたら、APIをインターネットに公開する。 “Actions” -> “Deploy API” を選択して APIをデプロイする。デプロイの名前は “production” とか。

これで以下のように、APIを外から呼び出せるようになった。

画面上部に表示されている Invoke URL というやつがAPIのエントリーポイント。curlで以下のように呼び出せる。
<span id="bf80" class="ri pi io re b gz rj rk m rl rm">curl --request POST \<br></br> --url <a class="ag hb" href="https://qen9yylar9.execute-api.us-west-1.amazonaws.com/production/submit" rel="noopener ugc nofollow" target="_blank">https://******.execute-api.us-west-1.amazonaws.com/production/submit</a> \<br></br> --header 'content-type: application/json' \<br></br> --data '{<br></br> "subject": "Hello",<br></br> "body": "Hoge"<br></br>}'</span>
メールが届いたら成功。おめでとう!
いたずらで大量にメールが送られたりしないように、呼び出し回数を制限しておくことをお勧めする。上記の画面で、 “Enable throttling” にチェックを入れて、 “Rate” の欄に 1とか2 と入力しておけばOK。
これはお好きな方法でいいと思うけど、HTML5 標準の fetch 関数を使う方法を紹介する。以下のような関数を作ればいい:
<span id="f1a8" class="ri pi io re b gz rj rk m rl rm">export default function sendEmail (subject, body) {<br></br> return fetch('<a class="ag hb" href="https://qen9yylar9.execute-api.us-west-1.amazonaws.com/production/submit'" rel="noopener ugc nofollow" target="_blank">https://******.execute-api.us-west-1.amazonaws.com/production/submit'</a>, {<br></br> method: 'POST',<br></br> headers: {<br></br> 'Accept': 'application/json',<br></br> 'Content-Type': 'application/json'<br></br> },<br></br> body: JSON.stringify({ subject, body })<br></br> })<br></br>}</span>
これでサイトからメールが送れるようになった。やったね!あとは入力された情報から件名や本文をJSで生成して、この関数を叩けばOK。
今回紹介したようなメールフォームは、例えばお店や会社のシンプルなホームページとかに付け足すのに最適だと思う。賢く作ればサーバ代がほぼゼロで運用できる上に多少凝ったことも出来る。S3やDynamoDBと組み合わせればコメント欄も付けられる。DynamoDBもLambdaと同じで完全に従量課金制なので、ちょい足し利用にはもってこい。もしまた機会があればその方法も紹介したいと思う。参考になったらClapお願いします!
Netlifyはいつからかは分かりませんが、現在はフォームハンドリング機能を無料で提供しているようです。なんと、NetlifyのファウンダーのMathias Biilmannに英語版ブログのコメント欄で教えてもらいました:
Great guide to using lambda, these kind of cloud functions are a really great way to glue different services together without relying on a bloated, monolithic, dynamic backend.Small note, though, form handling on Netlify is completely free currently, so no need for a paid plan to enable it. You can basically get this whole setup + comment spam filtering, just by adding a netlify attribute to your HTML form :)
— Mathias Biilmann (強調は筆者)
メールフォームを設置するだけならもはやAWS Lambdaすら不要ですね。Google Formsの代替としてとても強力な選択肢と言えます。
