API Gateway → Lambda → SNS でリクエストをメール通知する
やりたいこと
簡単に言えば「URL を踏んだらメールに通知が飛ぶ」だけのことである。
- API Gateway で待ち受けている Lambda でリクエストが来るのを待つ
- リクエストを着信したとき、着信した内容を SNS 経由で特定のメルアドにメールする
前提
- ロジックの実装には Python 3.12.2 を用いる。
- Region は
ap-northeast-1
なので、リンクもそれに準拠している。
別の Region を使う場合は随時読み替える。
大まかな流れ
- Lambda 関数を作成する
- 関数を準備したあと、以下の順に設定を行う
- API Gateway(着信担当)のエンドポイントを作成する
- エンドポイントへの着信を Lambda に横流しするようにする
- SNS(通知担当)のトピックとサブスクリプションを作成する
- トピックに届けられた内容をサブスクリプションに登録されたメルアドに通知するようにする
- Lambda 関数に API Gateway から転送された着信内容を SNS に横流しするような実装を行う
Lambda 関数の準備
関数の作成
やること:処理のコア部分をとりあえず作る
AWS Lambda の一覧に行って、「関数の作成」を押下する。関数名とランタイムの指定を求められるので、各々入力・選択する。関数名は何でもよく、ランタイムは Python 3.12 にする。
作成できると以下のような感じになる。
Amazon API Gateway の準備
Amazon API Gateway は Amazon 曰く「Amazon API Gateway を利用すれば、デベロッパーは規模にかかわらず簡単に API の作成、公開、保守、モニタリング、保護を行えます」ということらしい。これを日本語に翻訳すると「俺が API の受信を肩代わりして、着信はそっちに転送してやんよ」という意味である。
Lambda は具体的なロジック以外のことを考えなくて良い(サーバレス)のだが、逆に言うとロジック以外の部分は他人任せということである。Lambda は肝心の着信部分が他人任せなので、他人任せの部分も別途準備しないといけない。その他人任せの部分を自分で実装せずに、ともすれば更に他人任せに出来るのが API Gateway である。
今回やることの限りで言えば、API Gateway は単に門番役である。
エンドポイントを作成する
やること:Lambda 関数のエンドポイントを作成する
-
以下の画像左部の「トリガーを追加」ボタンを押下する
-
「トリガーの設定」パネルが表示されるので、以下の画像のように設定する
- 今回は新しくエンドポイントを作成する設定にした
- Security(認証)は特に設けないため Open とする
-
「追加」ボタンを押下する
-
エンドポイントが関数に結び付けられた形で準備される。
- ちなみに名前は勝手に準備される(「関数名-API」みたいな感じ)
この時点で Lambda 関数のメイン画面に飛ばされる。画面上部「関数の概要」パネルに新しく追加された「API Gateway」を押下すると、以下のようなパネルが下に表示される。ここにある API Endpoint にアクセスすると、この時点でエンドポイントから何かしらのレスポンスが返ってくるはずである。
後で使うので、この API Gateway のエンドポイントをどこかに控えておく。
Amazon SNS の準備
Amazomn SNS は Amazon 曰く「A2A および A2P メッセージング用のフルマネージド Pub/Sub サービス」であるとのことである。これを日本語に翻訳すると「どこか(Publisher)からSNSに飛んできた内容をカテゴリ分け(Topic)して、Topic をサブスクしてる宛先(Subscriber)に通知してやる」サービスである。
SNS は Pub/Sub モデルなメッセージング(通知)を行ってくれる。ここで通知の発信者(Pub)と受信者(Sub)をメッシュ状に繋げることを考えると相互に繋がる線の数がヤバいことになるのは目に見えている。そこで Pub と Sub の間に仲立ちを置いて、仲立ちが通知の中継をすることで通知網の複雑性を削減するというのがこのモデルがやりたいことである。
今回やることの限りで言えば、SNS は単にメール通知をしてくれる便利サービスである。
SNS のトピックを作成する
やること:通知のカテゴリ的なもの(トピック)を作る
- Amazon SNS のトピック一覧ページに行く
- 「トピックの作成」ボタンを押下する
- 「トピックの作成」パネルが表示されるので、以下の画像のように設定する
- 名前は何でも良い
- 表示名は尚の事何でも良い(オプション)。ただ、後のメール通知の件名がこの表示名になるっぽいので、訳のわからぬ表示名はやめておく。
- 「トピックの作成」ボタンを押下する
生成されたトピックの ARN は控えておく。
トピックをサブスクする
やること:トピックに飛んできた内容の通知先を仕込む
トピックを作成すると以下のような画面に遷移される。
- 「サブスクリプションの作成」ボタンを押下する
- 「サブスクリプションの作成」パネルが表示されるので、以下の画像のように設定する
- トピック ARN はさっき作ったトピックの ARN を指定する
- プロトコルは今回は「Eメール」とする
- エンドポイントはメールの宛先である。自分のメルアドとかにする。
- 「サブスクリプションの作成」ボタンを押下する。
この後サブスクリプションの概要ページが表示されるが、秒で閉じても構わない。
ただし、サブスクした宛先メルアドの確認が必要である。先述の (2) で指定したメルアドにほんとにサブスクするかの確認メールが届くので、メールを開いてこれを承認する。
Lambda と SNS を接続する
やること:Lambda が SNS に通知を横流しできるようにする
- 以下の画像右部の「送信先を追加」ボタンを押下する
- 「送信先を追加」パネルが表示されるので、以下の画像のように設定する
- ソース、条件は今回は何も気にしなくて良い
- 送信先タイプは「SNS トピック」にする
- 送信先は先ほど作ったトピックの ARN を指定する。
- 「保存」ボタンを押下する。
この時点で Lambda のフローは次のようになっている。
Lambda 関数の実装
控えていた SNS の ARN だけ置き換える。
-
event["body"]
にリクエストボディ(JSON とか)が入るので、それをパースする -
sns.publish()
の Message 引数がメールに渡る文章である
import json
import boto3
def lambda_handler(event, context):
body: dict[str, str] = json.loads(event["body"])
message = body.get("message", "unknown")
sns = boto3.client("sns")
sns.publish(
TopicArn="<SNS ARN>",
Message=f"You've got '{message}' from the request",
)
return {
"statusCode": 200,
"body": json.dumps(body),
"headers": {"Content-Type": "application/json"},
}
デバッグ用に、レスポンスは空ではなく送信したリクエストボディを返すようにしている。
実装の検証
API Gateway の作成時に控えていたエンドポイントに以下の要領でアクセスしてみる。
curl \
-X GET \
-H "Content-Type: application/json" \
-d '{ "message" : "random message" }' \
https://...
このリクエストを送信すると、レスポンスとして以下が返ってくると同時に SNS でサブスクしたメルアドにリクエストボディの内容を含んだメッセージが送信される。
ubuntu:~/projects/lambda$ curl \
-X GET \
-H "Content-Type: application/json" \
-d '{ "message" : "random message" }' \
https://...
{"message": "random message"}
メール:
これで API Gateway のエンドポイントに何らかのリクエストを投げると、Lambda → SNS を経由してメールで通知が飛んでくる流れを実装できたことになる。
Discussion