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

TAKUYA
週休7日で働きたい
13 min readSep 6, 2017

--

Lambdaに改造されて働かされるいぬさん

本稿では個人的に作ったLambda用メール送信スクリプトを使ってメール送信フォームを作る方法をご紹介する。手っ取り早く知りたい方はGitHubに公開したソースコードをご覧頂きたい。

静的サイトにサーバサイドのロジックをちょい足ししたい

今はgitさえ使えればGitHub PagesやNetlifyで無料で静的なサイトを公開できる。便利な世の中だ。静的サイトとはサーバサイドを含まない単純なサイトのこと。自分のホームページMarkdownノートアプリのサイトも全部Netlifyで配信している。速いし安定していてすごく気に入っている。

Netlifyで運用しているMarkdownノートアプリのサイト https://www.inkdrop.info/

静的サイトはサーバサイドを含まないので、自前で動的にページを生成したりフォームを設置することが出来ない。でもちょこっとだけそういう事がしたいというユースケースがあると思う。例えばホームページに問い合わせフォームを設置するとか。問題は、わざわざそのためだけにNetlifyの有料機能を使ったりサーバを設置するのは大げさだし面倒という点。いつくるか分からない問い合わせのためだけに、ずっとサーバをスタンバイさせるのは無駄。

サーバレスアーキテクチャはこういう「ちょい足し用途」にとても適している。必要な時に必要なリソースだけ割り当ててサーバを動かせる。例えばAWS Lambdaは動作時間と割当リソースのサイズで料金が決まるので、待っている間は料金がかからない。

今回作った問い合わせフォーム

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

Lambdaを用いたメール送信APIのアーキテクチャ

APIのアーキテクチャ

Lambdaは処理ごとに “function” を作って呼び出して使う。functionそれ自体はAWS上で動く単なるスクリプト。node.jsやJava、C#、Pythonが動かせる。SES(Simple Email Service)はAWSが提供するメール送信サービス。後述するけど簡単にメールが送れるので、構える必要はない。Lambda functionからこのSESを通じてメールを送信する。ただし、Lambda functionはデフォルトでは外部に公開されないので、自身のサイトから呼び出すにはHTTPエンドポイントを付けてやる必要がある。そしてAPI Gatewayがそれを担う。以上のアーキテクチャの概略を上図に示した。

apexを使ってLambda functionの管理を楽に行う

このLambda functionはAWS標準で提供されている方法だととても煩雑で管理しづらい。その問題を解決するためにapexというツールを使う。apexの概要は以下の記事が参考になるだろう。

apexをインストールする:

curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh

次に、AWSのアクセスキーが環境にセットアップ済みであることを確認する。apexは以下の環境変数を使用する。

  • AWS_ACCESS_KEY_ID — AWSアカウントアクセスキー
  • AWS_SECRET_ACCESS_KEY — AWSアカウントシークレットキー
  • AWS_REGION — AWSリージョン

もしまだされていなければ、こちらを参考に設定する。次に、apexプロジェクトの初期化を行う。今回使用するスクリプト群をgitでcloneして、その中で初期化する:

git clone git@github.com:craftzdog/send-email-lambda.git
cd send-email-lambda
apex init
> Project name: send-email

プロジェクト名は send-emailとする。するとapexがプロジェクト名に倣ってIAM roleなどを自動で作成してくれて、project.json というファイルが生成される。このファイルを以下のように編集する。

{
"name": "send-email",
"description": "Simple email transmitter",
"memory": 128,
"timeout": 5,
"environment": {},
"runtime": "nodejs6.10",
"role": "<YOUR_IAM_ROLE>"
}

これでLambdaを使う準備が出来た。

Lambda functionからメールを送信する

Lambdaを使う準備ができたら、次にメールを送るための準備をする。まずはAWS SESで使いたいメールアドレスを認証して登録する。以下のように、SESの管理画面で “Verify a New Email Address” というボタンを押下する。

使いたいメールアドレスの登録

AWSから認証メールが飛んでくるので、メール内に書かれているリンクを開けば登録が完了。これでSESからそのアドレスを使ってメールが送れるようになった。

Lambda functionにSESを設定

次に、Lambda functionからそのアドレスで送るように設定する。 functions/submit/function.json というファイルを開いて以下のように編集する。

{
"environment": {
"SES_REGION": "us-west-2",
"FROM_NAME": "Craftzdog Contact Form",
"FROM_EMAIL": "<YOUR_AUTOMATED_EMAIL_SENDER>",
"TO_EMAIL": "<EMAIL_TO_RECEIVE>"
}
}
  • SES_REGION: SESを使うリージョン。Oregonならus-west-2。
  • FROM_NAME: 送り元の名前。 “俺の問い合わせフォーム” とか。
  • FROM_EMAIL: 先ほどSESで登録したメールアドレス。 例: contact@example.com
  • TO_EMAIL: 問い合わせを受け取りたいメールアドレス

SESを使えるようにIAMロールにポリシーを追加

Lambdaを使って誰かに好き放題されないように、標準では最低限の権限しかapexのIAMロールに与えられていない。SESを使いたいので、その権限を与えるポリシーを追加する。

IAMの管理画面に行って、apexが作成したロールを見つける。以下の画像ではロールの名前が contact-form_lambda_function となっているけど、あなたは send-email_lambda_function となっているはず。

“Create Role Policy”というボタンを押して、以下ようなポリシーを作成する。名前は send-email_submit にする。

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1504526549000",
"Effect": "Allow",
"Action": [
"ses:SendEmail"
],
"Resource": [
"*"
]
}
]
}

試しにメールを送ってみる

これで送信する準備が整った。早速送ってみよう。以下のコマンドで直接Lambda functionを呼び出せる。

echo -n '{ "subject": "hello", "body": "world" }' | apex invoke submit

件名が “hello” 本文が “world” のメールがあなたの受信ボックスに届いたら成功。おめでとう!もし来なければ、以下のコマンドでログを確認する。

apex logs -f

エラーメッセージを読んで、正しくロールにポリシーが適用されているか、AWSのリージョンを間違えていないか、送信元メールアドレスは正しくSESに認証されているかなどを確認する。

API GatewayでサイトからAjaxで呼び出せるようにする

さて、Lambda functionが用意できたがこれをサイトから呼び出せるようにしたい。そのためにAPI GatewayでHTTPエンドポイントを作成する。日本語だとこのあたりが参考になる。

/submit に対する POST メソッドのリクエストを受け取って、 Lambda functionを呼び出すように設定する。

1. APIの作成

“Create API” でAPIを新規作成する。名前は “my-awesome-send-email-api”とか。

2. /submit エンドポイントの作成

Resoueces セクションにて、 “Actions” -> “Create Resource” を選択して /submit リソースを作成する。この時、 “Enable API Gateway CORS” にチェックを入れる。

3. POSTメソッドを作成

作成した /submit エンドポイントに対して、 POST メソッドを受け付けるようにする。 “Actions” -> “Create Method” でメソッドを作成する。

メソッドの設定

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

APIの作成結果

4. APIのデプロイ

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

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

デプロイ結果

画面上部に表示されている Invoke URL というやつがAPIのエントリーポイント。curlで以下のように呼び出せる。

curl --request POST \
--url https://******.execute-api.us-west-1.amazonaws.com/production/submit \
--header 'content-type: application/json' \
--data '{
"subject": "Hello",
"body": "Hoge"
}'

メールが届いたら成功。おめでとう!

呼び出し回数を制限する

いたずらで大量にメールが送られたりしないように、呼び出し回数を制限しておくことをお勧めする。上記の画面で、 “Enable throttling” にチェックを入れて、 “Rate” の欄に 1とか2 と入力しておけばOK。

サイトからAPIを呼び出してメールを送信する

これはお好きな方法でいいと思うけど、HTML5 標準の fetch 関数を使う方法を紹介する。以下のような関数を作ればいい:

export default function sendEmail (subject, body) {
return fetch('https://******.execute-api.us-west-1.amazonaws.com/production/submit', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ subject, body })
})
}

これでサイトからメールが送れるようになった。やったね!あとは入力された情報から件名や本文をJSで生成して、この関数を叩けばOK。

Lambdaは慣れておくと今後便利

今回紹介したようなメールフォームは、例えばお店や会社のシンプルなホームページとかに付け足すのに最適だと思う。賢く作ればサーバ代がほぼゼロで運用できる上に多少凝ったことも出来る。S3やDynamoDBと組み合わせればコメント欄も付けられる。DynamoDBもLambdaと同じで完全に従量課金制なので、ちょい足し利用にはもってこい。もしまた機会があればその方法も紹介したいと思う。参考になったらClapお願いします!

2017/11/27 追記 — Netlifyでフォーム機能が無料で使えるようになってた

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の代替としてとても強力な選択肢と言えます。

作ってます: https://www.inkdrop.info/

--

--