AmazonSNSでSMSのメッセージ配信失敗をほぼリアルタイムで検知したい

2020.05.02

はじめに

こんにちは。大阪オフィスの林です。

SNSからSMSでメッセージ通知を行ったときの配信状況の確認(特に配信失敗時)について、方法とやり方を考えてみたのでまとめておきたいと思います。

やりたいこと

SMSのメッセージ配信の状況を(ほぼ)リアルタイムで確認したい。(主に配信失敗時)

まずはシンプルな確認方法

今回、なるべくリアルタイム性を高くするといった点を重視するため、CloudWatchを使った確認方法は採用しないこととします。またSNSは下記4つのログ記録には対応していますが、今回対象としたいSMSは含まれていませんので、ログ監視とも違う何か別の方法で配信の状況をリアルタイム性高く確認する必要があります。

  • AWS Lambda
  • Amazon SQS
  • HTTP/S
  • プラットフォームアプリケーションのエンドポイント

~その1~ SNSのダッシュボードで確認

まず1つ目として、目視で確認出来る方法を紹介します。SNSのダッシュボードから、SMSでメッセージ配信を行った際の配信統計情報を確認することが出来ます。

しかし、この配信統計情報は、全体の成功/失敗の数はカウントして表示してくれていますが、具体的にどのトピックのものなのか? どのサブスクリプションに紐づいている宛先のものなのか?といったところが全く分かりません。

~その2~ SNSのDLQを使って確認

そこで次の方法を考えます。次の方法としてSQSにDLQ(デッドレターキュー)を飛ばすという方法を紹介します。SNS及びSQSの設定について本記事では割愛させて頂きますので、詳細については下記を参照ください。

DLQの設定をすると、SMSのメッセージ配信が失敗したタイミング(リアルタイム)でSQSのキューにメッセージが来ました。SNSのダッシュボードでの確認と比較し、それなりに細かい情報を付加してくれていますが、肝心のどの宛先に対して通知が失敗したかを確認出来ません。しいて確認できる一意の値として、サブスクリプションのARNが存在する程度です。

メッセージ本文

{
  "Type" : "Notification",
  "MessageId" : "5b1b77d6-e111-50bb-92c0-20f1f52fb188",
  "TopicArn" : "arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:xxxxx",
  "Subject" : "test",
  "Message" : "test",
  "Timestamp" : "2020-05-02T06:23:58.073Z",
  "SignatureVersion" : "1",
  "Signature" : "xxxxxxxxxxxxxxxxxxxxxxxxYJRAv4nZuEZmmMqIazyQAYw/rF9xqQdkZRRJt46sBnQDoFglHSKaagR2HXw6cNxbRV5t+Uy9yPZ8VLaXqFFEZqwenk5gqlvkfpYoSMpx2XOeM3UIbsCdWQ4kzC5yCx7XJflNQxCAVDH84rniqbIXQ6vVuKG3NG3aVqRacmRAWthM+YGKccFpaGFajaUtzSqSATChxFulvJuUWr0Nx6O3zyFrgXlAD3KcbiroO7Ck/xxxxxxxxxxxx+weOAJ47ydZfiKgSCHohEPqYWgXNqMUV79GZqK8cw6h/xxxxxxxxxxxx==",
  "SigningCertURL" : "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c94170xxxxxxxxxxxx.pem",
  "UnsubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:xxxxx:9a36ec2c-9f44-42e1-a4d3-1c3ddc409a8a"
}

SNSのダッシュボードでの確認とDLQでの確認に共通して、エンドポイント(電話番号)の情報が無く、どこに対してSMSのメッセージ配信が失敗したか分かりません。単純にリアルタイムでSMSのメッセージ配信が失敗したということだけを把握したい場合は、ここまででも良いのかもしれませんが、どうにかしてSMSの通知失敗とエンドポイント(電話番号)の把握までをカバーしたいと思い、もう少し考えてみました。

lambda連携

SQSに渡されたメッセージの本文にサブスクリプションのARNが存在するという点を上記でサラッと触れました。一意に識別できるデータがこのサブスクリプションのARNくらいしかないので、このARNをキーとしてエンドポイント(電話番号)を取得し、それをメールで通知するというところまでやっていきたいと思います。コード(Python3.7で作成)の本文は本記事の最後に載せておきますのでポイントとなる部分だけご説明させて頂きます。

事前準備としてlambdaのトリガーとしてSQSを紐付けておきます。 SQS側から見るとこんな感じで紐付きを確認できます。

lambdaに渡ってからの大きな処理の流れは下記の3つです。
1. SQSにメッセージが入るとlambdaが起動し、SQSのメッセージの中からサブスクリプションARNを取得します。

for record in event['Records']:
  payload=record["body"]
   match = re.search(r'SubscriptionArn=(.*)', payload)
   GetSubscriptionArn = match.group(1)[:-1]

2. 次に取得したサブスクリプションARNからエンドポイント(電話番号)の情報を抽出します。

sns = boto3.client('sns')
response = sns.get_subscription_attributes(
   SubscriptionArn = GetSubscriptionArn
)

3. 最後に、抽出したエンドポイント(電話番号)の情報をメール本文に入れ、別途作成しておいたSMS配信失敗通知用のSNSでメール通知します。

response = sns.publish(
   TopicArn = 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:sms-error',
   Message = response["Attributes"]["Endpoint"] + 'へのSMS通知に失敗しました。',
   Subject = 'SMS-Send-Faild'
)

これでひとまずリアルタイム性高くSMSの配信失敗を把握できるようになりました。

まとめ

今回はこのような実装に辿り着きましたが、実はもっとシンプルな方法があるんじゃないかなぁ~っと内心思っています。ですので他のもっとシンプルな方法を見つけたら更新したいと思います!

以上、大阪オフィスの林がお送りしました!

参考

コードの紹介です。Python3.7で動かしました。

from __future__ import print_function
import boto3
import re

def lambda_handler(event, context):
    for record in event['Records']:
        payload=record["body"]
        match = re.search(r'SubscriptionArn=(.*)', payload)
        GetSubscriptionArn = match.group(1)[:-1]

        #endpoint取得
        sns = boto3.client('sns')
        response = sns.get_subscription_attributes(
            SubscriptionArn = GetSubscriptionArn
        )
        
        #SNS送信
        response = sns.publish(
            TopicArn = 'arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:sms-error',
            Message = response["Attributes"]["Endpoint"] + 'へのSMS配信に失敗しました。',
            Subject = 'SMS-Send-Faild'
        )