Amazon SNS で SMS を送信出来ないときに確認するところ

## はじめに

DX 技術本部の yu-yama@sra です。

Amazon SNS を使用して VPC エンドポイント越しに SMS を送信しようとしたところ、
ややハマりしたので備忘メモを残します。

## 構成図
![SMS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534812/75bdaef2-a1e4-c18d-fad4-f687183b0e83.png)

## TL;DR

-   SMS を送信する AWS リソースに設定している **IAM ロールのポリシーに aws:SourceIp を使用していないか**確認しましょう
-   SMS メッセージの送信に**支払うことのできる上限額 (USD)を超えていないか**確認しましょう

## ハマりその1 - 権限

AWS リソースに IAM ロールをアタッチして VPC エンドポイント越しに SMS の送信を試みていたが、権限エラーが発生しており、修正前ポリシーの設定は以下のようになっていた。

```json:修正前ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:ListPhoneNumbersOptedOut",
                "sns:Publish",
                "sns:SetSMSAttributes",
                "sns:GetSMSAttributes",
                "sns:OptInPhoneNumber",
                "sns:CheckIfPhoneNumberIsOptedOut"
            ],
            "Resource": "*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "{AWSリソースの存在するサブネットのIPアドレスレンジ}"
                }
            }
        }
    ]
}
```

調べたところ、

`リクエスト実行元が Amazon VPC エンドポイントを使用するホストである場合、aws:SourceIp キーは使用できません。`
引用：[AWS グローバル条件コンテキストキー \- AWS Identity and Access Management](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_condition-keys.html)

とのことで aws:SourceIp キーが使えないらしいので、以下のように修正

```json:修正後ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:ListPhoneNumbersOptedOut",
                "sns:Publish",
                "sns:SetSMSAttributes",
                "sns:GetSMSAttributes",
                "sns:OptInPhoneNumber",
                "sns:CheckIfPhoneNumberIsOptedOut"
            ],
            "Resource": "*"
        }
    ]
}
```

すると、権限エラーが返却されなくなった

## ハマりその2 - メッセージ送信上限額

上記権限問題修正後、メッセージ送信で `200 status` が返却されているのに SMS が届かない事象に遭遇。

```java:修正前Javaコード
Map<String, MessageAttributeValue> smsAttributes =
        new HashMap<String, MessageAttributeValue>();
smsAttributes.put("AWS.SNS.SMS.SenderID", new MessageAttributeValue()
        .withStringValue("mySenderID") //The sender ID shown on the device.
        .withDataType("String"));
smsAttributes.put("AWS.SNS.SMS.MaxPrice", new MessageAttributeValue()
        .withStringValue("0.50") //Sets the max price to 0.50 USD.
        .withDataType("Number"));
smsAttributes.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue()
        .withStringValue("Promotional") //Sets the type to promotional.
        .withDataType("String"));
```

AWSの提供している[AWS SDK for Java で、メッセージ属性値(上限価格を 0.50 USD、SMS 型をプロモーション)を設定しSMS メッセージを送信するサンプルコード](https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk)まんまなのだが届かない。

[ログを出力](https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sms_stats_cloudwatch.html)し確認したところ、

```json:送信時刻に出力されていたログ
{
    "notification": {
        "messageId": "12345-6789-0123-456789012345",
        "timestamp": "2020-12-25 00:00:00.000"
    },
    "delivery": {
        "numberOfMessageParts": 10,
        "destination": "+819012345678",
        "priceInUSD": 0.7451,
        "smsType": "Promotional",
        "providerResponse": "This delivery would exceed max price",
        "dwellTimeMs": 317,
        "dwellTimeMsUntilDeviceAck": 862
    },
    "status": "FAILURE"
}
```

`"This delivery would exceed max price"`と最大価格を超えている旨のメッセージが出力されていた...
現在0.5なので1に修正

```java:修正後Javaコード
Map<String, MessageAttributeValue> smsAttributes =
        new HashMap<String, MessageAttributeValue>();
smsAttributes.put("AWS.SNS.SMS.SenderID", new MessageAttributeValue()
        .withStringValue("mySenderID") //The sender ID shown on the device.
        .withDataType("String"));
smsAttributes.put("AWS.SNS.SMS.MaxPrice", new MessageAttributeValue()
        .withStringValue("1.00") //Sets the max price to 1.00 USD.
        .withDataType("Number"));
smsAttributes.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue()
        .withStringValue("Promotional") //Sets the type to promotional.
        .withDataType("String"));
````

すると、めでたく無事に送信されました...

# まとめ

- サンプルコードを無思考で使用するのはやめましょう
    - サンプルコードはあくまでサンプルなので確実に動くことを保証するものではありません。使用する際は何を行っているか理解した上で使用しましょう。

- ログをちゃんと読みましょう
    - まずはログを読むこと。

ごくごく基本的なことが出来ていないためのハマりでした。**基本は大切。**