【AWS/SNS】Lambda関数からショートメッセージ(SMS)を送る

2022/6/07

(最終更新: 2022/6/07

Lambda関数からショートメッセージ(SMS)を送る

はじめに

Node.js で作成したアプリ(Lambda 関数)から、AWS SNS を利用して SMS を送る方法を紹介します。

  • Lambda 関数作成
  • Node.js のアプリ作成(ポイント)
  • SNS トピック・サブスクリプションの作成

をおこないます。 Node.js アプリについては簡単なソースコードを記載しております。

この記事を読むとわかること

  • AWS SNS による SMS 送信を Node.js で行う方法がわかります。
  • AWS SDK の利用方法がわかります。

【参考】この記事で学んだことを使って、アプリケーションを作ってみました! →

【AWS入門】電車遅延をSMS通知してくれるアプリを作成した(TwitterAPI利用)

【AWS入門】電車遅延をSMS通知してくれるアプリを作成した(TwitterAPI利用)

AWSサービスを使ってみよう。EventBridge+Lambda+AmazonSNSを使ってアプリを作成。

https://bunsugi.com/train-delay-check-app

SNS とは?

Amazon SNS (Amazon Simple Notification Service) とは、イベント駆動の非同期処理を実現する「イベントルータ」の一種です。

何かイベントが起こったことを検知して、イベント起こったよ!と各システムに伝えることができます。イベントを発行する側のシステムは「Publisher」と呼ばれます。イベントを受け取る側は「Subscriber」と呼ばれます。

イベントルータのイメージ イベントルータイメージ

SNS の Subscriber として、AWS のサービスを利用することができます。

  • SQS
  • Lambda
  • Kinesis Data Firehose

など。

メジャーなのは SQS らしいです。SQS は、「イベントストア」と呼ばれ、SNS から受けたイベントをポーリングして受信者側からのアクションを待ちます。SNS+SQS の組み合わせを使ったアーキテクチャは、ファンアウトと呼ばれます。

参考資料

https://aws.amazon.com/jp/sns/?whats-new-cards.sort-by=item.additionalFields.postDateTime&whats-new-cards.sort-order=desc

ポイント:  SNS は、このようなシステム間(A2A:アプリケーション対アプリケーション)でのやりとりだけでなく、人との間(A2P:アプリケーション対個人)のやりとりにも利用できます。

今回使いたいのは A2P で、

SNS から個人スマホに SMS を送る機能です。

前提

AWS マネジメントコンソールにアクセスできること。

本記事で作成する機能

Node.js で作成した Lambda 関数から、「てすとめっせーじ」と書かれた SMS を送る。

アプリケーションでの利用イメージ

SNS が一番よくつかわれるのは、AWS で想定外の挙動があったときの E メール通知です。 CloudWatch と連動して利用していると思います。

私は、電車の遅延を駅に行く前に検知するために、関連するツイート数が増えたら SMS で通知してくれる機能を作りました。 この機能は別記事にまとめたいと思います。

  • EventBridge で、15 分おきにイベント発行
  • Lambda で、TwitterAPI を使った処理を実行する
  • 条件に合致した場合、SMS を送る

システム構成イメージ システム構成イメージ

次章より実際の手順を記載します。

SNS トピックの作成

トピックを作成します。

マネジメントコンソール >  Amazon SNS  > トピック から。

トピックのタイプ

トピック作成画面 トピック作成画面

トピックのタイプは2種類から選べます。

  • FIFO タイプ
    • 「順序性」と「単一性」が担保される
    • 秒間 300 件しか実行できない
    • SQS に対するイベント発行にしか使えない
  • スタンダードタイプ
    • 1 回の要求で 2 回実行されることがある。(最低 1 回)
    • 順序はベストエフォート
    • 制限はなし?
    • A2P で使える

順序はともかく、単一性は処理によっては重要になりそうです。1 回の要求で 2 回処理されることがあるとなると、課金処理とかだったら大変。

今回は、スタンダードタイプしか使えないのでスタンダードを選択。

アクセスポリシー

トピック所有者のみにしておきます。

再配信ポリシー

デフォルトのままにします。

再配信ポリシー 再配信ポリシー

CloudWatchLogs のロール

CloudWatchLogs に、ログを書き込むためのアクセス許可を SNS に与える必要があります。

ここでは新しいロールを作成してしまうことにします。

ロール作成画面 ロール作成画面

「許可」をおします。

作成結果 作成結果

サブスクリプションの作成

トピックの配信先を「サブスクリプション」として設定します。 1 トピックにつき複数サブスクリプションを設定可能です。

サブスクリプションイメージ サブスクリプションイメージ

SMS サンドボックス

注意点として、いきなり不特定多数に SMS を送るような設定はできないようです。

事前に認証した電話番号にのみ SMS を送信できるようになっています。(「AWS アカウントが SMS サンドボックス内にいる」と表現されています。)

SMS サンドボックスを終了して、事前認証した以外の電話番号に SMS を送れるようにするには、サンドボックス外へ移動する手順が必要とのことです。

今回は勉強用ですし、自分個人のスマホにしか SMS 送信しないため、サンドボックスのまま使います。

電話番号追加

サンドボックスに電話番号を追加します。

マネジメントコンソール マネジメントコンソール

送信先は、例えば電話番号が080-1234-5678であれば+81 80 1234 5678と入力します。

電話番号追加

SMS でワンタイムパスワードが送られてくるので、それをマネジメントコンソールに入力します。

サブスクリプションの追加

先ほど作成したトピック画面からサブスクリプションを作成します。

サブスクリプションの作成

Node.js アプリ作成(ソースコードあり)

初期設定

Node.js アプリを作成します。

$ mkdir snstest
$ cd snstest/
$ npm init -y
$ touch index.js
$ npm install

(任意)GitHub にリポジトリを作成して連携しておきます。

$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin git@github.com:ユーザ名/snstest.git
$ git branch -M main
$ git push -u origin main

パッケージインストール

Node.js から AWS リソースを動かすためには、AWS SDK のモジュールが必要なため、インストールします。

$ npm install @aws-sdk/client-sns

AWS SDK とは、各プログラム言語ごとに用意された、AWS の開発をサポートするツールです。AWS 用モジュールだと思っても大丈夫だと思います。

SNS 接続部品

SNS 接続部品を作ります。

utils.js
const REGION = "ap-northeast-1" //e.g. "us-east-1"
const { SNSClient, PublishCommand  } = require("@aws-sdk/client-sns")

module.exports.sns = async (msg) => {

  const client = new SNSClient({ region: REGION })
  const params = {
    Message: msg /* required */,
    TopicArn: "arn:aws:sns:ap-northeast-1:xxxxx{作成したトピックのARNを記載}",
  }
  const command = new PublishCommand(params)
  try {
    const data = await client.send(command)    // process data.
    console.log(data)
  } catch (error) {
    // error handling.
    console.log(`error: ${error}`)
  } finally {
    // finally.
  }
}

@aws-sdk/client-snsの公式ドキュメントはこちら。

ざっくりと理解しておきます。

  • SNSClientというクラスは、SMS 送信を実行してくれるものです。sendというメソッドを持っています。
  • PublishCommandは、sendに渡すコマンドを作成するクラスです。

PublishCommand のインプット定義は公式を読んでおいても損は無いです。上記コードでいうparamsオブジェクトに入れることができる設定がわかります。

メイン関数作成

メイン関数はこれだけです。

index.js
const { sns } = require("./utils"); // ロール設定忘れずに。

const index = async () => {
    const msg="てすとめっせーじ"
    await sns(msg);
};

module.exports.handler = index;

Lambda 用なのでhandlerとしてエクスポートすることを忘れないようにします。

Lambda 関数作成とソースアップロード

先ほど作成したアプリを Lambda 関数にアップロードします。 Lambda 関数の作成・アップロード方法はこちらを参照。

Lambda 関数の設定は

  • 一から作成
  • Node.js 16.x
  • 実行ロールは「基本的な Lambda アクセス権限で新しいロールを作成

とします。

Lambda関数作成

アップロード対象の資材は以下。

  • index.js
  • node_modules/
  • package-lock.json
  • package.json
  • utils.js

IAM ロールの設定

作成した Lambda 関数のマネジメントコンソールから、設定  > 実行ロール と進んで IAM ロールを確認します。

IAM のコンソールから「ポリシーをアタッチ」を選択します。

ポリシーをアタッチ

AmazonSNSFullAccess」を付けておきます。

ポリシーを許可

実行確認

Lambda 関数のコンソールから、テストを実行してみます。

スマホに SMS が届きました!

スマホ画面 スマホ画面

つまづいたところ

権限問題

Lambda 関数に適切なロール(主に SNS を実行するロール)が無いと権限エラーになります。

START RequestId: xxxxxxxxxxxxxxxxxxxxxxxVersion: $LATEST
2022-06-05T05:08:42.084Z	xxxxxxxxxxxxxxxxxxxxxxx	INFO	error: AuthorizationError: User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/snstest-role-f3wh4aq2/snstest is not authorized to perform: SNS:Publish on resource: arn:aws:sns:ap-northeast-1:XXXXXXXXXXX:topic-test2 because no identity-based policy allows the SNS:Publish action
END RequestId: xxxxxxxxxxxxxxxxxxxxxxx
REPORT RequestId: xxxxxxxxxxxxxxxxxxxxxxx	Duration: 991.31 ms	Billed Duration: 992 ms	Memory Size: 128 MB	Max Memory Used: 77 MB	Init Duration: 371.37 ms

タイムアウト問題

Lambda 側でタイムアウトエラーが発生する場合があります。 デフォルトでは Lambda タイムアウトは 3 秒になっています。

その場合は、Lambda の 設定 > 一般設定 から、タイムアウト値を適当に伸ばします。

タイムアウト値

ES6 記法使えない問題

本当は、Node.js の ES6 以降記法で記載したいのですが、上手くいきませんでした。

AWS 公式ページ:SDK Version3 のデベロッパーガイドでも、Node.js の 13 以降であれば ES6 基本は可能と書かれていますが。。

本当はこんな形で書きたい。

import { SNSClient, PublishCommand  } = from "@aws-sdk/client-sns"

export const sns = async (msg) => {
   // 省略
}

NG な理由がよくわからないので、とりあえず諦め、前に示したような記法で書いています。

使用上限を超えた問題

Lambd の実行自体は成功するのに、一向に SMS が届かなくなりました。

CloudWatch ログを見るとNo quota left for accountのログが出ています。

{
    "notification": {
        "messageId": "a81c56f5-97d3-59f1-b6bb-0de3df441de8",
        "timestamp": "2022-06-05 05:59:51.371"
    },
    "delivery": {
        "destination": "+81xxxxxxxxxx",
        "smsType": "Promotional",
        "providerResponse": "No quota left for account",
        "dwellTimeMs": 79    },
    "status": "FAILURE"
}

対処法 Step1 上限申請

こちらにしたがって上限申請しました。

問い合わせに必要な内容は以下になるようです。

  • 月あたりの上限額をいくらまで引き上げるか
  • どんなアプリから SMS を発信するか
  • 合意したユーザにだけ SMS を発信することをどのように担保しているのか
  • 1 日何通送るか
  • 何というメッセージを送るか

【参考】AWS に送付した内容

I will write the details that follow.

  • The dollar limit requested: $ 50
  • Link or name of site or app that will be sending SMS:
    arn:aws:lambda:{Lambda 関数の ARN}
    This is my personal app.
  • Details of your opt-in process and any other ways in which you ensure you are only sending to people who have requested your messages:
    This account is in the sandbox, so I can only send SMS to authenticated phone numbers.
  • A list of countries in which the recipients of your messages are located: Japan
  • The maximum number of messages you expect to send per day: 20
  • Message templates you plan to send:
    ツイート数が増えました。今日のカウント数:XX。昨日のカウント数:XX。
  • Any additional information that would help us better understand your use case:
    This account is for project members to learn AWS.I'm learning AWS. I'm trying some services.

1 日たたずに返信が来ました。

アジアパシフィック (東京) リージョンにおいて、新しい SMS の月額使用限度額$1000USD が適用されました。また、お客様のアカウントを Amazon SNS サンドボックスから移動いたしました。

頼んでいませんが、SNS サンドボックスから移動もされたようです。

対処法 Step2

SNS のマネジメントコンソール > テキストメッセージング(SMS) > テキストメッセージングの優先設定

設定 設定

上限を 1 ドルから 50 ドルに変更しました。

まとめ

Amazon SNSを利用して、Lambda関数からメールを送ることができました。

もう少し非同期処理に近いところを学んでみようと思います。

関連記事

Lambda 関数の作り方・ソースコードのアップロード方法



個別連絡はこちらへ→Twitterお問い合わせ

プロフィール

プロフィールイメージ

はち子

事業会社のシステム部門で働きはじめて5年目の会社員。システム企画/要件定義/システムアーキテクチャ等。

Twitter→@bun_sugi

過去の記事について

はてなブログに掲載の記事(主にプログラミングメモ)についてはこちらに掲載しております。(本ブログに移行中)

タグ一覧

関連記事

Copyright© 2023, エンジニアを目指す日常ブログ

お問い合わせ|プライバシーポリシー