AWS SES・Lambda・API Gatewayで作る!サイトからメールを受け取るお問い合わせフォームの実装方法
最近、あるクライアントのためにシンプルなランディングページを構築しました。このクライアントは、自分のメールアドレスを公開することなく、ウェブサイト経由でメールを受け取りたいと考えていました。
正直に言うと、私は以前この機能を自分で実装したことがありませんでした。いつもは以下のように、mailtoをhref属性に指定したアンカータグ付きのシンプルな「Contact Us」ボタンを使っていました。
<button>
<a href="mailto:myemail@example.com">Contact Me</a>
</button>
しかし、このアプローチには2つの大きな不便さがあります。
- メッセージを送りたいユーザーと、それを受け取るサイトオーナーの双方が、互いにメールアドレスを共有する必要があります。気にしない人もいますが、プライバシーを重視する方には理想的とは言えません。
- サイト訪問者がリンクをクリックすると、デバイスのデフォルトのメールアプリが強制的に開かれるため、ストレスになることがあります。公共のパソコンを使っている場合はどうでしょうか?ログインしていない場合は?そもそもメールアプリを使いたくない場合は?
技術的には、宛先のメールアドレスをコピーして、ブラウザなどからメッセージを送ることも可能です。しかし、これらはすべて余計な手順であり、ユーザーがメッセージ送信を諦めてしまう原因になり、ビジネス側は貴重なフィードバックやチャンスを失いかねません。
そこで今回は、ユーザーがフォームにメッセージを入力して送信ボタンを押すだけで、サイトから離れることなくオーナーにメールが届く仕組みを採用することにしました。
Googleで簡単に検索すると、ウェブサイトに埋め込めるサードパーティ製のツールやウィジェットがいくつも見つかりますが、そのほとんどはブランドロゴが表示され、完全なカスタマイズには有料サブスクリプションが必要です。
WordPressのようなCMSを使っていれば標準プラグインで対応できますが、そうでない場合、継続的なコストは避けたいところです。
そこで私は、この機能を自前でコーディングし、完全なコントロールを持てるようにすることにしました。
本ガイドでは、HTMLとAWSサービスを使ってこの機能を実装した手順を再現しながら解説していきます。
HTMLフォームの作成
まずは非常にシンプルに、CSSなしの基本的なHTMLフォームを作成し、目的の機能だけをテストできるようにします。
<h2>Contact Us</h2>
<form>
<label for="name">Name:</label>
<input name="name" type="text"/><br/><br/>
<label for="email">Email:</label>
<input name="email" type="email"/><br/><br/>
<label for="name">Message:</label>
<textarea name="message"></textarea><br/><br/>
<input type="submit"/>
<div>
<p id="result-text"></p>
</div>
</form>

次に、JavaScriptでフォーム送信時の処理を実装します。
const form = document.querySelector('form')
form.addEventListener('submit', event => {
// フォーム送信によるページのリロードを防止
event.preventDefault()
const { name, email, message } = event.target
console.log('Name: ', name.value)
console.log('email: ', email.value)
console.log('Message: ', message.value)
})
これで、ユーザー入力を受け取るフォームと、結果をコンソールに表示するだけのJavaScriptコードができました。
ひとまずここまでにして、フォームデータを受け取り、そのデータでメールを送信するバックエンドサービスの構築に取り掛かりましょう。
バックエンドの全体像
ここからはAWSに入り、どのサービスをどのように使うのかを見ていきます。
タイトルにもある通り、AWS LambdaとSimple Email Service(SES)を使用します。SESは、呼び出されるとメールメッセージを送信できるサーバーレスのメッセージングサービスです。AWS Lambdaは、イベントに応答して実行されるサーバーサイドのコードを記述できるサービスです。
さらにAPI Gatewayも使用します。これにより、HTTP経由でLambda関数を呼び出せるようになります。

今回の場合、フォームが送信されると、以下のワークフローが実行されます。
- ブラウザ(JavaScript)が、フォームデータをリクエストボディに含めたPOSTリクエストを、AWS API Gatewayが指定するエンドポイントURLに送信します。
- API Gatewayがこのリクエストを検証した後、イベントパラメータを受け取るLambda関数をトリガーします。API Gatewayはフォームデータをイベントパラメータのbodyプロパティに格納します。
- Lambda関数がイベントボディからデータを抽出し、そのデータを使って送信したいメールの本文と宛先を組み立てます。そして関数はAWS SDKを使って、メールデータとともにSESを呼び出します。
- SESがsendMailリクエストを受け取ると、メールデータを実際のテキストメールに変換し、AWS独自のメールサーバー経由で受信者に送信します。
メールが送信されると、ブラウザはステータスコード200と成功メッセージを含むレスポンスを受け取ります。AWSクラウド上のどこかのステップで失敗した場合は、ステータスコード500のレスポンスが返されます。
ステップ1:SESのセットアップ方法
実は、これらのステップは逆順に設定していきます。最初は比較的簡単なSESから始めましょう。
まずAWSコンソールでSESサービスに移動し、サイドメニューの「Email Addresses」をクリックして、「Verify a New Email Address」ボタンをクリックします。

開いたダイアログに、SESがメール送信時に送信者(sender)として使用するメールアドレスを入力します。

すると、入力したメールアドレス宛に確認用リンクが記載されたメールが届きます。これは、AWSがそのメールアドレスの所有者が送信者アドレスとしての使用に同意していることを確認するためのものです。
メールを認証するまで、SESのダッシュボードでは認証ステータスが「保留中(pending)」のまま表示されます。

メールアドレスの所有者がAWSから届いたメールを開き、中の認証リンクをクリックすると、認証ステータスが「verified」に変わります(ページを更新すると変更が反映されます)。

以上でSESの設定は完了です。必要に応じて、認証済みメールアドレスを一覧から選択し、「Send a Test Email」ボタンをクリックしてサービスをテストすることもできます。受信者のメールアドレス、件名、本文を入力して送信できます。
送信されたメールはAWSサーバーによって署名され、認証済みアドレスが送信者として表示されるはずです。以下のような感じです。

ステップ2:Lambdaのセットアップ方法
さあ、ここからが一番楽しい部分です。フォームデータを受け取り、SESを呼び出す関数を作成します。
Lambda関数の素晴らしい点は、バックエンドのコードを24時間365日稼働させるサーバーの管理や保守を心配する必要がないことです。サーバーレスだからです。
ただし、サーバーが一切関わっていないわけではありません。AWSが裏側でその管理を担ってくれるので、私たちはコードを書くことだけに集中できます。さらに、課金は関数が呼び出された回数と実行にかかった時間に対してのみ発生し、驚くほど安価です!
IAMロールの作成と設定
Lambda関数を書き始める前に、関数にアタッチし、SESサービスを呼び出す権限(AWSではポリシーと呼ばれます)を付与するためのIAMロールを作成する必要があります。

AWSコンソールからIAMサービスに移動し、サイドメニューの「Policies」をクリックして、「Create Policy」ボタンをクリックします。

ポリシー作成ページでJSONタブに切り替え、以下の権限を貼り付けて「Next」をクリックします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
]
}
3番目の画面でポリシーに名前を付け、「Create Policy」ボタンをクリックします。

次に、LambdaにアタッチするIAMロールを作成し、先ほど作成した権限ポリシーと紐付けます。

IAMのサイドメニューから「Roles」をクリックし、「Create role」ボタンをクリックします。

ロール作成画面で、選択されているタイプが「AWS service」であることを確認し、「Lambda」を選択して「Next:Permissions」ボタンをクリックします。

次の画面で、先ほど作成したポリシーを名前で検索して選択し、「Next」をクリックします。

確認画面で覚えやすいロール名を付けて、「Create role」をクリックします。
これで新しいLambda関数を作成できます。Lambdaサービスのダッシュボードに移動し、「Create Function」ボタンをクリックしてください。

関数作成画面で、関数に名前を付け、「Author from scratch」オプションを選択し、ランタイムとしてNode.jsを選択します。
「Change default execution role」の下で「Use an existing role」オプションを選択し、「Existing role」ドロップダウンから前のステップで作成したロール名を選択します。
最後に「Create function」ボタンをクリックして関数を作成します。
コードの記述とテスト

エディタでindex.jsファイル(Lambda呼び出し時に実行されるファイル)を開き、内容を以下のコードに置き換えます。
const aws = require("aws-sdk");
const ses = new aws.SES({ region: "us-east-1" });
exports.handler = async function (event) {
console.log('EVENT: ', event)
const params = {
Destination: {
ToAddresses: ["your@email.com"],
},
Message: {
Body: {
Text: {
Data: `Hello from Lambda!`
},
},
Subject: { Data: `Message from AWS Lambda` },
},
Source: "your@email.com",
};
return ses.sendEmail(params).promise()
};
2行目でAWS SDKを使用してSESインスタンスを作成していることに注目してください。us-east-1リージョンを選んだのは、私がメールアドレスを登録・認証したのがそのリージョンだったからです。必ずご自身のメールアドレスと、登録したAWSリージョンに置き換えてください。
この関数をテストするには、「Deploy」ボタンをクリックします。次に「Test」ボタン→「Configure test event」をクリックすると、新しいテストイベントを作成できるテスト設定ダイアログが開きます。
テストイベントのボディエディタに、最終的にブラウザリクエストから届く内容を模した以下のJSONを入力し、「Create」をクリックします。
{
"body": {
"senderName": "Namo",
"senderEmail": "namo@trains.com",
"message": "I love trains!"
}
}
「Test」ボタンをクリックすると、作成したテストが実行されます。エディタに新しいタブが開き、関数実行時に生成されたログが表示されるはずです。以下のような感じです。

ログアウトしたイベントオブジェクトが「Function logs」の下に表示され、テストイベントで使用したbodyデータが含まれていることがわかります。
このテストでは、私の受信箱にもメールが届いているはずです。確認してみましょう。

はい、予想通りです。しかもテスト実行後ほぼ即座に届きました。
次に、テストデータからより意味のあるメッセージを取得できるよう、関数コードを修正しましょう。
const aws = require("aws-sdk");
const ses = new aws.SES({ region: "us-east-1" });
exports.handler = async function (event) {
console.log('EVENT: ', event)
// イベントボディからプロパティを抽出
const { senderEmail, senderName, message } = JSON.parse(event.body)
const params = {
Destination: {
ToAddresses: ["the.benhawy@gmail.com"],
},
// 文字列内にデータを埋め込んで送信
Message: {
Body: {
Text: {
Data: `You just got a message from ${senderName} - ${senderEmail}:
${message}`
},
},
Subject: { Data: `Message from ${senderName}` },
},
Source: "the.benhawy@gmail.com",
};
return ses.sendEmail(params).promise();
};
重要なポイントとして、API Gatewayが関数を呼び出す際、イベントボディには文字列が渡されます。そのため、JSON.parseを使ってevent.bodyをJSONに変換し、送信者のメールアドレス、名前、メッセージを抽出しています。その後、これらの変数を文字列補間によってメール本文と件名に組み込んでいます。
ただし、この状態でテストを実行するとエラーが返ります。これは、テストがJSONオブジェクトをevent.bodyに渡しており、JSONに対してJSON.parseを実行するとJavaScriptでエラーが発生するためです。
残念ながらテストエディタでは文字列をイベントに渡せないため、この部分は後ほど別の場所からテストすることになります。
ステップ3:API Gatewayのセットアップ方法
次に、最後に使うAWSサービスとしてAPI Gatewayを設定します。これにより、ブラウザから作成済みのLambda関数へHTTPリクエストを送れるようになります。

Lambda関数のページを離れずに、「Function overview」セクションを展開し、「Add trigger」をクリックします。

次に、ドロップダウンからAPI Gatewayを選択し、APIタイプとして「HTTP API」、セキュリティ方式として「Open」を選択し、CORSチェックボックスにチェックを入れます。そして「Add」をクリックします。

関数の「Configuration」タブにリダイレクトされ、作成したばかりのAPI Gatewayトリガーが表示されるはずです。そこでAPI endpointを控えておいてください。これが、ブラウザからフォームデータとともに呼び出すURLになります。
HTMLに戻って統合テスト
いよいよ、フォームが本当にメールを送信できるかテストしましょう。
フォーム送信時にリクエストを送信するよう、JavaScriptを修正します。
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
// フォーム送信によるページのリロードを防止
event.preventDefault();
const { name, email, message } = event.target;
// 前のステップで控えたAPIエンドポイントURLを使用
const endpoint =
"<https://5ntvcwwmec.execute-api.us-east-1.amazonaws.com/default/sendContactEmail>";
// HTTP経由で文字列として送信できるようJSON.stringifyを使用
const body = JSON.stringify({
senderName: name.value,
senderEmail: email.value,
message: message.value
});
const requestOptions = {
method: "POST",
body
};
fetch(endpoint, requestOptions)
.then((response) => {
if (!response.ok) throw new Error("Error in fetch");
return response.json();
})
.then((response) => {
document.getElementById("result-text").innerText =
"Email sent successfully!";
})
.catch((error) => {
document.getElementById("result-text").innerText =
"An unkown error occured.";
});
});
さあ、決断の時です。フォームに入力して送信ボタンをクリックしてみてください。成功メッセージが表示されれば、メールが送信されたということです。

送信先のメールアドレスの所有者は私自身なので、受信箱をチェックしてみると、フォームで入力した内容が含まれたメールが自分宛に届いていました!

ここまで順を追って進めてきた方は、今やどんなウェブサイトにも組み込める、ちゃんと動作する「Contact Us」フォームを手に入れたことになります。しかも、実際に使われたときにのみ課金されます。
あなたがどう感じるかはわかりませんが、私はこれがとてもクールで、ほとんど魔法のようだと思っています!クラウドコンピューティング/サービスをワークフローに取り入れる、実用的で素晴らしい例でもあります。
もちろん、フロントエンドでReactやVueなどのフレームワークを使ったり、LambdaをPythonやGoなど別のプログラミング言語で書いたりと、このフローは自由にカスタマイズできます。
最後に…
最後までお読みいただきありがとうございました!私は独学で開発者になった経験をもとに、JavaScript、クラウド開発、個人的な学習・キャリアに関する記事を書いています。Twitter(@adham_benhawy)でも同じテーマについて発信していますので、ぜひフォローしてください!
参考リソース
- https://aws.amazon.com/premiumsupport/knowledge-center/lambda-send-email-ses/
- https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html
- https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html?icmpid=docs_lambda_console
-
EmailJSでVue.jsアプリケーションからメールを送信する方法【初心者向けガイド】
数日前、シンプルなVueプロジェクトに取り組んでいた際、作成したばかりのコンタクトフォームからメールを送信する必要がありました。誰かがフォームに入力するたびに、自動的に通知メールを受け取りたかったのです。 いろいろ調べていく中でEmailJSに出会いました。公式ドキュメントが充実していて、とても使いやすかったため、この記事としてまとめることにしました。この記事がどなたかの参考になれば幸いです :) はじめよう! この記事では、EmailJSを使ってVue.jsアプリケーションからメールを送信する手順を解説します。 先に進む前に、お使いの環境にVue CLIがインストールされていることを前提と
-
Macの標準メールアプリで暗号化メールを送信する方法【S/MIME証明書の取得から設定まで】
通常、メールのやり取りはプロバイダーがセキュリティを管理しているため、比較的安全なものとされています。しかし、メールの暗号化は利用しているメールサービスによっては対応していない場合もあります。より強固にメールを守りたいなら、自分のメールアプリで暗号化機能を使うのがおすすめです。 Macユーザーであれば、標準搭載されている「メール」アプリだけで暗号化メールを送信できます。この記事では、その設定方法と使い方を詳しく解説します。 macOSに内蔵されたメールアプリは、送信前にメール内容を暗号化することが可能です。ただし、この暗号化を行うには、事前にMacへメール用のデジタル証明書をインストールして