AWS Lambda + API Gateway + SESでお問い合わせフォームを実装する方法【結論】
静的サイト(S3 + CloudFront構成)にお問い合わせフォームを追加するには、AWS Lambda + API Gateway + SESを組み合わせたサーバーレス構成が最もコスパが高い方法です。
サーバー不要・月額数十円以下で実装でき、AWS初心者でもAIを活用しながら構築できます。この記事ではwabi-motionの実際の構築経験をもとに、手順とつまずきやすいポイントをまとめて解説します。
AWS Lambda + API Gateway + SESとは何か
静的サイトはサーバーサイドの処理ができないため、PHPやNode.jsのようにフォームデータを直接受け取ってメール送信することができません。そこでAWSの3つのサービスを組み合わせて解決します。
- AWS Lambda:フォームデータを受け取り、メール送信処理を実行するサーバーレス関数
- API Gateway:フロントエンドからのHTTPリクエストを受け付けるエンドポイント
- Amazon SES(Simple Email Service):AWSが提供するマネージドなメール送信サービス
この3つを組み合わせることで、EC2などのサーバーを一切立てることなくお問い合わせフォームを実現できます。
全体の構成と処理の流れ
実装前に全体の流れを把握しておきましょう。
- ユーザーがフォームに入力して送信ボタンを押す
- JavaScriptがAPI GatewayのエンドポイントにPOSTリクエストを送る
- API GatewayがLambda関数を呼び出す
- LambdaがSESを使ってメールを送信する
- 送信完了のレスポンスをフロントエンドに返す
事前準備:SESでメールアドレスを認証する
SESでメールを送信するには、まず送信元メールアドレスの認証が必要です。AWSコンソールでSESを開き、「Verified identities」から「Create identity」をクリックして認証を完了させます。
登録したメールアドレス宛に確認メールが届くので、メール内のリンクをクリックして認証完了です。
なお、SESはデフォルトでサンドボックス環境になっており、認証済みのメールアドレスにしか送信できません。本番運用する場合はService QuotasからSESのサンドボックス解除申請を行います。
Lambda関数を作成してメール送信処理を実装する
AWSコンソールでLambdaを開き、「関数の作成」をクリックします。設定は以下の通りです。
- ランタイム:Node.js 20.x
- アーキテクチャ:x86_64
関数を作成したら、以下のコードを貼り付けて「Deploy」をクリックします。
import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses";
const ses = new SESClient({ region: "ap-northeast-1" });
export const handler = async (event) => {
const body = JSON.parse(event.body);
const { name, email, message } = body;
if (!name || !email || !message) {
return {
statusCode: 400,
headers: { "Access-Control-Allow-Origin": "*" },
body: JSON.stringify({ message: "必須項目が不足しています" })
};
}
const params = {
Source: "your-verified-email@example.com",
Destination: {
ToAddresses: ["your-email@example.com"]
},
Message: {
Subject: {
Data: `【お問い合わせ】${name}様より`,
Charset: "UTF-8"
},
Body: {
Text: {
Data: `名前:${name}\nメールアドレス:${email}\n\nメッセージ:\n${message}`,
Charset: "UTF-8"
}
}
}
};
try {
await ses.send(new SendEmailCommand(params));
return {
statusCode: 200,
headers: { "Access-Control-Allow-Origin": "*" },
body: JSON.stringify({ message: "送信完了しました" })
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
headers: { "Access-Control-Allow-Origin": "*" },
body: JSON.stringify({ message: "送信に失敗しました" })
};
}
};
【実体験メモ】 デフォルトのタイムアウトは3秒ですが、reCAPTCHA検証など外部APIを呼び出す場合はタイムアウトエラーが発生します。Lambda → 設定 → 一般設定からタイムアウトを10秒に延長しておくことをおすすめします。
LambdaにSES送信権限を付与する
LambdaがSESを呼び出すには、IAMロールへの権限追加が必要です。Lambda関数の「設定」→「アクセス権限」から実行ロールを開き、以下のインラインポリシーを追加します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ses:SendEmail", "ses:SendRawEmail"],
"Resource": "*"
}
]
}
【実体験メモ】 この設定を忘れると「AccessDenied: User is not authorized to perform ses:SendEmail」というエラーが発生します。エラーが出たらまずCloudWatch Logsを確認する癖をつけると解決が早いです。
API GatewayでHTTPエンドポイントを作成する
AWSコンソールでAPI Gatewayを開き、「HTTP API」を選択して作成します。REST APIより設定がシンプルでCORS対応も簡単です。
- 統合:Lambda関数を選択して先ほど作成した関数を指定する
- ルート:POST /contact
- ステージ:$default(デフォルト)
作成後にエンドポイントURLが発行されます。CORSの設定も忘れずに行います。
- Access-Control-Allow-Origin:サイトのドメイン
- Access-Control-Allow-Methods:POST, OPTIONS
- Access-Control-Allow-Headers:Content-Type, x-api-key
【実体験メモ】 CORSのAccess-Control-Allow-Headersにx-api-keyを含め忘れるとブラウザでCORSエラーが発生して送信できません。APIキーを使う場合は必ず追加してください。
フロントエンドのお問い合わせフォームを実装する
HTML側のフォームとJavaScriptを実装します。
<form id="contact-form">
<input type="text" id="name" placeholder="お名前" required />
<input type="email" id="email" placeholder="メールアドレス" required />
<textarea id="message" placeholder="お問い合わせ内容" required></textarea>
<button type="submit">送信する</button>
</form>
<p id="result-message"></p>
<script>
const form = document.getElementById("contact-form");
const resultMsg = document.getElementById("result-message");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const data = {
name: document.getElementById("name").value,
email: document.getElementById("email").value,
message: document.getElementById("message").value
};
try {
const res = await fetch("https://your-api-endpoint.execute-api.ap-northeast-1.amazonaws.com/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
const json = await res.json();
resultMsg.textContent = json.message;
} catch (err) {
resultMsg.textContent = "エラーが発生しました。時間をおいて再度お試しください。";
}
});
</script>
セキュリティ対策:Lambda + API Gatewayで押さえるべき4つのポイント
お問い合わせフォームを公開する前にセキュリティ対策を確認しておきましょう。
- reCAPTCHA v3の導入:ボットによる大量送信を防ぐために必須です。reCAPTCHA secretはLambdaの環境変数(RECAPTCHA_SECRET)として設定し、コードに直書きしないようにします
- APIキーの設定:API GatewayにAPIキーを設定することでエンドポイントへの不正アクセスを制限できます
- 入力値のバリデーション:Lambda側でも必須項目のチェックを行い、不正なデータを弾く処理を入れます
- IAM権限は最小限に:SES送信に必要なアクションのみに絞ったインラインポリシーを使います
AWS Lambda + SESのコストはいくらかかるか
この構成のコストは個人サイト用途であれば月額数十円以下に収まります。
- Lambda:月100万リクエストまで無料
- API Gateway(HTTP API):月100万リクエストまで約1ドル
- SES:Lambda経由は1,000通あたり約0.1ドル
サーバーを立てずにここまでの機能を実現できるのがサーバーレス構成の強みです。副業でWebサイト制作を請け負う場合にも、この構成を提案できるとコスト面でクライアントへの説得力が増します。
まとめ
AWS Lambda + API Gateway + SESを組み合わせることで、静的サイトにサーバーレスのお問い合わせフォームを実装できました。
実際に構築してみると、詰まるポイントはIAM権限・CORS設定・タイムアウトの3点に集中していました。どれもCloudWatch Logsとエラーメッセージを丁寧に読めば解決できます。AWS初心者でもAIを活用しながら進めることで十分に実装可能です。セキュリティ面ではreCAPTCHAの導入とIAM権限の最小化を必ず対応しておくことをおすすめします。
よくある質問
Q. AWS Lambda + SESでお問い合わせフォームを作るのは難しいですか?
AWSの基本操作ができれば実装できます。IAM権限・CORS・タイムアウトの3点さえ押さえておけばAWS初心者でも構築可能です。AIを活用しながら進めるとさらにハードルが下がります。
Q. サンドボックス解除は必須ですか?
テスト用途であれば認証済みメールアドレス宛の送信のみで問題ありません。不特定多数からのお問い合わせを受け付ける本番運用ではサンドボックス解除の申請が必要です。
Q. API GatewayはHTTP APIとREST APIのどちらがいいですか?
お問い合わせフォームの用途であればHTTP APIで十分です。設定がシンプルでコストも安いため、特別な理由がなければHTTP APIを選択することをおすすめします。