はじめに
Amazon ConnectからAWS Lambdaを呼出し、SMS送信する方法を紹介します。
発信元の電話番号がSMS送信可能な番号であれば送信し、その番号がSMS送信できない場合は、手動でSMS送信先の電話番号を入力してもらう仕組みを実装しました。
前提条件
- Amazon SNS で SMS送信できること。必要に応じてサンドボックス解除の申請や使用限度額の引き上げを行ってください。
- Amazon Connectインスタンスを作成済みであること
Lambda 関数作成
SMS送信用のLambda 関数を作成します。
SMS送信を許可するIAMポリシーを作成し、Lambda関数のIAMロールに適用します。
ポリシーは以下の通りです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"NotResource": "arn:aws:sns:*:*:*"
}
]
}
NotResource
を指定することで、トピックに対する実行権限を付与することなく、携帯電話番号へのSMS送信が可能になります。参考記事
コードは以下の通りです。
import boto3
import json
def lambda_handler(event, context):
print('event:' + json.dumps(event, ensure_ascii=False))
phone_number = get_phone_number(event)
message = create_message()
try:
send_sms(phone_number, message)
return {}
except Exception as e:
print(f'SMSの送信に失敗しました。エラー: {str(e)}')
def get_phone_number(event):
if 'input_phone_number' in event['Details']['ContactData']['Attributes']:
input_phone_number = event['Details']['ContactData']['Attributes']['input_phone_number']
return '+81' + input_phone_number[1:] # 090xxxxxxxx -> +8190xxxxxxxx
else:
return event['Details']['ContactData']['CustomerEndpoint']['Address']
def create_message():
body = [
'これはAWS LambdaからのSMSテストメッセージです。',
'このメッセージは3行に分かれています。',
'これが最後の行です。'
]
return '\n'.join(body)
def send_sms(phone_number, message):
sns = boto3.client('sns')
sender_id = 'classmethod' # 送信者ID
response = sns.publish(
PhoneNumber=phone_number,
Message=message,
MessageAttributes={
'AWS.SNS.SMS.SenderID': {
'DataType': 'String',
'StringValue': sender_id
}
}
)
print(f'SMSが正常に送信されました。メッセージID: {response["MessageId"]}')
実装ポイント
get_phone_number
Amazon ConnectのフローからLambda関数に渡される電話番号の形式は、2パターンあることに注意ください。
Lambdaに渡されるevent内容は、以下の2パターンです。
- 発信元の電話番号にSMSを送信できる場合、発信元の電話番号にSMS送信します
- 発信元の電話番号がSMS送信できない場合、手動でSMS送信先の電話番号を入力し、SMS送信します。
発信元番号は「+81xxx」形式ですが、ユーザー入力の番号は「0xxx」形式となります。
ユーザー入力の番号は「0xxx」形式は「+81xxx」に変換処理を行うことで、どちらの番号形式にも対応できるようにしています。
発信元の電話番号にSMSを送信できる場合、以下のeventが渡されます。Attributes
は空で、CustomerEndpoint
内の発信元電話番号を利用します。
{
"Details": {
"ContactData": {
"Attributes": {},
"CustomerEndpoint": {
"Address": "+8190xxxxxxxx",
"Type": "TELEPHONE_NUMBER"
},
~省略~
発信元の電話番号がSMS送信できない場合、手動でSMS送信先の電話番号を入力し、以下のeventが渡されます。Attributes
内の手動で入力した電話番号を利用します。
{
"Details": {
"ContactData": {
"Attributes": {
"input_phone_number": "090xxxxxxxx"
},
"CustomerEndpoint": {
"Address": "+8150xxxxxxxx",
"Type": "TELEPHONE_NUMBER"
},
~省略~
return
Lambdaの戻り値を使い分けることで、Connectフローの遷移先を制御できます。
SMS送信成功時は空のオブジェクトを返して「成功」に遷移し、エラー時は何も返さずに「エラー」に遷移するようにしています。
これにより、実行結果に応じた適切なフロー制御が可能になります。
SMS送信の制限
SMSの送信者を識別する送信者IDは、3文字以上11文字以内という制限があります。
[送信者 ID] に、少なくとも 1 つの文字を含み、スペースは含まない、3~11 文字の英数字のカスタム ID を入力します。 引用ドキュメント
送信されるメッセージは140バイトで分割されて送信されますので注意してください。
ただし、メッセージが140バイトを超える、例えば200バイトの場合、2つに分割されて受信されるか、分割されずに1つのメッセージとして受信されるかは、携帯電話会社や端末によって異なります。
各 SMS メッセージは最大 140 バイトまで含めることができ、文字限度はエンコーディングスキームによって異なります。例えば、SMS メッセージには以下を含めることができます。 160 GSM 文字 140 ASCII 文字 70 UCS-2 文字
Connectフロー
Connectフローは以下の通りです。
フローコード (クリックすると展開します)
{
"Version": "2019-10-30",
"StartAction": "00e56ecd-f363-4a8b-b70b-e17616278f40",
"Metadata": {
"entryPointPosition": { "x": 128.8, "y": 96.8 },
"ActionMetadata": {
"4ff267b8-6fa5-4256-ac19-6d0703fd81fc": {
"position": { "x": 131.2, "y": 248.8 },
"conditionMetadata": [
{
"id": "e89535f6-c067-42a3-bcbf-2155ddf734e4",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "+8170"
},
{
"id": "4b157c54-9d09-4695-a686-3d7ecb50fbe3",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "+8180"
},
{
"id": "7fb14539-4610-4133-bcfa-301d39218c79",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "+8190"
}
]
},
"00e56ecd-f363-4a8b-b70b-e17616278f40": {
"position": { "x": 232.8, "y": 76 }
},
"564ac430-c916-4279-8907-60f96080b1a3": {
"position": { "x": 450.4, "y": 76.8 },
"children": ["cbbace15-32aa-4949-b88b-90cc5bf21a1e"],
"overrideConsoleVoice": true,
"fragments": {
"SetContactData": "cbbace15-32aa-4949-b88b-90cc5bf21a1e"
},
"overrideLanguageAttribute": true
},
"cbbace15-32aa-4949-b88b-90cc5bf21a1e": {
"position": { "x": 450.4, "y": 76.8 },
"dynamicParams": []
},
"90c242e6-360d-4be9-8cfa-645b33ac6a84": {
"position": { "x": 565.6, "y": 482.4 },
"parameters": {
"Attributes": { "input_phone_number": { "useDynamic": true } }
},
"dynamicParams": ["input_phone_number"]
},
"d1730443-5dea-4b16-a33a-fb8e25c4b607": {
"position": { "x": 1471.2, "y": 289.6 }
},
"9b196cfd-e7c7-4c60-9897-87390d7df8cc": {
"position": { "x": 1028.8, "y": 796.8 }
},
"2906039d-70d3-416a-ba18-0b60b178e6ec": {
"position": { "x": 1706.4, "y": 848.8 }
},
"d5e69705-2a25-4beb-9e0e-a19c1218ae62": {
"position": { "x": 808, "y": 480.8 },
"conditionMetadata": [
{
"id": "8009a7a5-5f3e-40ab-be62-962223a6ca8a",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "070"
},
{
"id": "e674c1fb-5e94-4c95-92d9-4f81c73686c9",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "080"
},
{
"id": "71408375-01ee-469f-bf63-e35e6f33643c",
"operator": {
"name": "Starts with",
"value": "StartsWith",
"shortDisplay": "starts with"
},
"value": "090"
}
]
},
"e5ae829a-953e-4490-a846-c49518fa0bb3": {
"position": { "x": 1257.6, "y": 288.8 },
"parameters": {
"LambdaFunctionARN": { "displayName": "cm-hirai-send_sms" }
},
"dynamicMetadata": {}
},
"52578cfa-32ab-4dc5-ae78-0829748b80e7": {
"position": { "x": 341.6, "y": 486.4 },
"conditionMetadata": [],
"countryCodePrefix": "+1"
},
"15562c61-adf4-4aeb-a6a6-b5a0282aae7f": {
"position": { "x": 1475.2, "y": 708.8 }
},
"2c2c44f1-1d41-487a-9e42-00700771103c": {
"position": { "x": 1029.6, "y": 480 },
"conditionMetadata": [
{ "id": "43f09e33-0220-4cf7-b689-d983ce737b29", "value": "1" },
{ "id": "c0dcbab6-e99b-4981-b44f-f84b77eb984f", "value": "2" }
]
}
},
"Annotations": [],
"name": "cm-hirai-send-sms",
"description": "",
"type": "contactFlow",
"status": "published",
"hash": {}
},
"Actions": [
{
"Parameters": { "ComparisonValue": "$.CustomerEndpoint.Address" },
"Identifier": "4ff267b8-6fa5-4256-ac19-6d0703fd81fc",
"Type": "Compare",
"Transitions": {
"NextAction": "52578cfa-32ab-4dc5-ae78-0829748b80e7",
"Conditions": [
{
"NextAction": "e5ae829a-953e-4490-a846-c49518fa0bb3",
"Condition": { "Operator": "TextStartsWith", "Operands": ["+8170"] }
},
{
"NextAction": "e5ae829a-953e-4490-a846-c49518fa0bb3",
"Condition": { "Operator": "TextStartsWith", "Operands": ["+8180"] }
},
{
"NextAction": "e5ae829a-953e-4490-a846-c49518fa0bb3",
"Condition": { "Operator": "TextStartsWith", "Operands": ["+8190"] }
}
],
"Errors": [
{
"NextAction": "52578cfa-32ab-4dc5-ae78-0829748b80e7",
"ErrorType": "NoMatchingCondition"
}
]
}
},
{
"Parameters": { "FlowLoggingBehavior": "Enabled" },
"Identifier": "00e56ecd-f363-4a8b-b70b-e17616278f40",
"Type": "UpdateFlowLoggingBehavior",
"Transitions": { "NextAction": "564ac430-c916-4279-8907-60f96080b1a3" }
},
{
"Parameters": {
"TextToSpeechEngine": "Neural",
"TextToSpeechStyle": "None",
"TextToSpeechVoice": "Kazuha"
},
"Identifier": "564ac430-c916-4279-8907-60f96080b1a3",
"Type": "UpdateContactTextToSpeechVoice",
"Transitions": { "NextAction": "cbbace15-32aa-4949-b88b-90cc5bf21a1e" }
},
{
"Parameters": { "LanguageCode": "ja-JP" },
"Identifier": "cbbace15-32aa-4949-b88b-90cc5bf21a1e",
"Type": "UpdateContactData",
"Transitions": {
"NextAction": "4ff267b8-6fa5-4256-ac19-6d0703fd81fc",
"Errors": [
{
"NextAction": "4ff267b8-6fa5-4256-ac19-6d0703fd81fc",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Attributes": { "input_phone_number": "$.StoredCustomerInput" },
"TargetContact": "Current"
},
"Identifier": "90c242e6-360d-4be9-8cfa-645b33ac6a84",
"Type": "UpdateContactAttributes",
"Transitions": {
"NextAction": "d5e69705-2a25-4beb-9e0e-a19c1218ae62",
"Errors": [
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "ショートメッセージを送信しました。電話を切ります。"
},
"Identifier": "d1730443-5dea-4b16-a33a-fb8e25c4b607",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"Errors": [
{
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"Text": "ご入力頂いた電話番号宛てに、ショートメッセージをお送りすることができません。電話を切ります。"
},
"Identifier": "9b196cfd-e7c7-4c60-9897-87390d7df8cc",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"Errors": [
{
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {},
"Identifier": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"Type": "DisconnectParticipant",
"Transitions": {}
},
{
"Parameters": { "ComparisonValue": "$.Attributes.input_phone_number" },
"Identifier": "d5e69705-2a25-4beb-9e0e-a19c1218ae62",
"Type": "Compare",
"Transitions": {
"NextAction": "9b196cfd-e7c7-4c60-9897-87390d7df8cc",
"Conditions": [
{
"NextAction": "2c2c44f1-1d41-487a-9e42-00700771103c",
"Condition": { "Operator": "TextStartsWith", "Operands": ["070"] }
},
{
"NextAction": "2c2c44f1-1d41-487a-9e42-00700771103c",
"Condition": { "Operator": "TextStartsWith", "Operands": ["080"] }
},
{
"NextAction": "2c2c44f1-1d41-487a-9e42-00700771103c",
"Condition": { "Operator": "TextStartsWith", "Operands": ["090"] }
}
],
"Errors": [
{
"NextAction": "9b196cfd-e7c7-4c60-9897-87390d7df8cc",
"ErrorType": "NoMatchingCondition"
}
]
}
},
{
"Parameters": {
"LambdaFunctionARN": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:cm-hirai-send_sms",
"InvocationTimeLimitSeconds": "3",
"ResponseValidation": { "ResponseType": "STRING_MAP" }
},
"Identifier": "e5ae829a-953e-4490-a846-c49518fa0bb3",
"Type": "InvokeLambdaFunction",
"Transitions": {
"NextAction": "d1730443-5dea-4b16-a33a-fb8e25c4b607",
"Errors": [
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"StoreInput": "True",
"InputTimeLimitSeconds": "5",
"Text": "お客様がただいまお使いの電話番号宛てに、ショートメッセージをお送りすることができません。メッセージの送信先となる別の電話番号をご入力下さい。",
"DTMFConfiguration": { "DisableCancelKey": "False" },
"InputValidation": { "CustomValidation": { "MaximumLength": "20" } }
},
"Identifier": "52578cfa-32ab-4dc5-ae78-0829748b80e7",
"Type": "GetParticipantInput",
"Transitions": {
"NextAction": "90c242e6-360d-4be9-8cfa-645b33ac6a84",
"Errors": [
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": { "Text": "エラーになりました。電話を切ります。" },
"Identifier": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"Type": "MessageParticipant",
"Transitions": {
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"Errors": [
{
"NextAction": "2906039d-70d3-416a-ba18-0b60b178e6ec",
"ErrorType": "NoMatchingError"
}
]
}
},
{
"Parameters": {
"StoreInput": "False",
"InputTimeLimitSeconds": "30",
"SSML": "<speak>\n送信先電話番号は\n<say-as interpret-as=\"telephone\">$.Attributes.input_phone_number</say-as>\nでよろしいでしょうか。\n正しい場合は1を、修正する場合は2を押してください。\n</speak>"
},
"Identifier": "2c2c44f1-1d41-487a-9e42-00700771103c",
"Type": "GetParticipantInput",
"Transitions": {
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"Conditions": [
{
"NextAction": "e5ae829a-953e-4490-a846-c49518fa0bb3",
"Condition": { "Operator": "Equals", "Operands": ["1"] }
},
{
"NextAction": "52578cfa-32ab-4dc5-ae78-0829748b80e7",
"Condition": { "Operator": "Equals", "Operands": ["2"] }
}
],
"Errors": [
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "InputTimeLimitExceeded"
},
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "NoMatchingCondition"
},
{
"NextAction": "15562c61-adf4-4aeb-a6a6-b5a0282aae7f",
"ErrorType": "NoMatchingError"
}
]
}
}
]
}
電話をかけると、以下の3つのパターンが確認できます。
- 発信元番号がSMS送信可能な「090」「080」「070」で始まる番号の場合、電話をかけると、すぐにSMSが送信され、通話が切断されます。
- 発信元番号がSMS送信可能な番号でない場合、プッシュ式でSMS送信可能な番号を入力すると、入力された番号が復唱されます。その後、「1」を入力するとSMSが送信され、「2」を押すと再度プッシュ式で電話番号を入力する必要があります。
- 発信元番号がSMS送信可能な番号でない場合、プッシュ式でSMS送信できない番号を入力すると、その番号にはSMSを送信できないとアナウンスし、通話を切断します。
[コンタクト属性を確認する]ブロック
発信元番号は「+81xxx」形式ですが、ユーザー入力の番号は「0xxx」形式です。
そのため、[コンタクト属性を確認する]ブロックが2つありますが、各々チェックするコンタクト属性の値が異なります。
[顧客の入力を取得する]ブロック
Amazon Connectの音声には、Amazon Pollyが利用されています。
Amazon Pollyは、深層学習を使用したテキスト読み上げサービスであり、SSML (Speech Synthesis Markup Language)タグを使用することで、音声の様々な側面をカスタマイズできます。
[顧客の入力を取得する]ブロックで電話番号を含めたアナウンスでは、以下の通り<say-as>
タグを使用し、interpret-as="telephone"
と指定することで、電話番号として解釈し、各桁を個別に読み上げます。
<speak>
送信先電話番号は
<say-as interpret-as="telephone">$.Attributes.input_phone_number</say-as>
でよろしいでしょうか。
正しい場合は1を、修正する場合は2を押してください。
</speak>
最後に
Amazon ConnectとLambdaを連携することで、SMS送信を柔軟にカスタマイズできることがわかりました。
発信元番号のチェックや、ユーザー入力の電話番号への送信など、ユースケースに合わせて色々な実装が可能です。
ポイントは以下の通りです。
- 発信元番号とユーザー入力では、Lambdaに渡される電話番号の形式が異なる
- Lambdaから返す値によって、Connectフローの遷移先を制御できる
- SMS送信には文字数などの制限があるので注意が必要
- Amazon PollyのSSMLタグを使うと、電話番号を適切に読み上げられる
本記事が参考になれば幸いです。
参考
https://docs.aws.amazon.com/ja_jp/polly/latest/dg/supportedtags.html
https://docs.aws.amazon.com/ja_jp/sns/latest/dg/sms_publish-to-phone.html