AWS Step FunctionsでCSVファイルの架電リストをもとにAmazon Connectでオートコールを行い、架電結果を取得してみた
はじめに
本記事では、AWS Step Functionsを使用してCSVファイルの架電リストをもとにAmazon Connectでオートコールを行い、架電結果を取得する方法について紹介します。
業務において、架電対象リストに基づいて自動発信し、通話の成否(電話が繋がったかどうか)に応じて処理を分岐させたいケースは多くあります。このような処理は、AWS Step Functionsを活用することで実現可能です。
本記事で紹介する構成は以下の通りです。
なお、④の架電結果に応じた処理の分岐については、今回は検証のため実装しません。
また、Step Functions は外部 API を呼び出せるため、架電リストは S3 バケットに保存された CSV ファイルだけでなく、CRM システムから取得することも可能です。
架電結果に応じて、以下のような処理を実装することが可能です。
- 顧客に電話が繋がらなかった場合、SMSを送信して案内を行う
- 架電リストのファイルを削除し、電話が繋がらなかった顧客をリストアップして、新たな架電リストとしてS3にアップロードする
- 架電結果(繋がった/繋がらなかった)に関わらず、CRMに結果を保存する
- 繋がった場合、顧客が選択したIVRの内容や、エージェントと会話した内容もCRMに保存する
本システムの活用が想定されるユースケースとして、以下のようなシナリオが考えられます。
- 予約のリマインドコール
- 病院や美容院、レストランなどの予約システムと連携し、予約日前日に自動でリマインドコールを行う。顧客が電話に出なかった場合はSMSでリマインドメッセージを送信する。
- 支払い期限のリマインド
- クレジットカード会社や公共料金の請求において、支払い期限が近い顧客に自動で架電を行い、支払いを促す。
- 顧客が電話に出なかった場合、Eメールでリマインドを行う。
- キャンペーンや新商品のお知らせ
- EC サイトや小売業者が、特定の顧客に対してキャンペーン情報や新商品の案内を自動で架電する。 興味を持った顧客にはオペレーターへ転送する。
- 契約の更新案内
- 保険会社が、契約更新の時期が近づいた顧客に自動で架電し、更新手続きを促す。
- 顧客が電話に出なかった場合、架電リストを更新し、再架電を試みる
Connectフロー
オートコール用のConnectフローは以下の通りです。
[コンタクト属性の設定] ブロックでは、キー「answered」、値「true」の設定が必須です。
架電時に電話が繋がった場合、true
と定義することで、通話が成功したことを確認できます。
架電リスト
架電リストを保存するS3バケット(cm-hirai-sf-csv-read)を作成し、架電リストのCSVファイル(destination_phone_numbers.csv)をアップロードします。
waittime,DestinationPhoneNumber
1,+8190xxxxxxx1
2,+8190xxxxxxx2
3,+8190xxxxxxx3
4,+8190xxxxxxx4
5,+8190xxxxxxx5
架電リストの CSV ファイルで waittime
を設定する理由は後述します。
ステートマシン用IAMポリシーの作成
ステートマシン用の IAM ポリシーを作成します。
S3 バケット名や Connect インスタンス ID は、各自の環境に合わせて変更してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::cm-hirai-sf-csv-read",
"arn:aws:s3:::cm-hirai-sf-csv-read/*"
]
},
{
"Effect": "Allow",
"Action": [
"connect:StartOutboundVoiceContact",
"connect:GetContactAttributes"
],
"Resource": [
"arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c",
"arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/contact-flow/*",
"arn:aws:connect:ap-northeast-1:111111111111:instance/3ff2093d-af96-43fd-b038-3c07cdd7609c/contact/*"
]
},
{
"Effect": "Allow",
"Action": [
"states:StartExecution",
"states:DescribeExecution",
"states:StopExecution",
"states:redriveExecution"
],
"Resource": "*"
}
]
}
ステートマシンの作成
ステートマシンを作成します。
以下のコードを貼り付けます。必要に応じて、以下の項目を変更してください。
- Bucket:バケット名
- Prefix:ファイル名
- Key:ファイル名
- ContactFlowId:Connect フロー ID
- InstanceId:Connect インスタンス ID
- SourcePhoneNumber:Connect の発信用電話番号
{
"Comment": "S3 バケット cm-hirai-sf-csv-read から DestinationPhoneNumber を読み取り、1秒あたり2リクエストに制御しつつ並列で StartOutboundVoiceContact を実行",
"StartAt": "CheckFileExists",
"States": {
"CheckFileExists": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:s3:listObjectsV2",
"Arguments": {
"Bucket": "cm-hirai-sf-csv-read",
"Prefix": "destination_phone_numbers.csv",
"MaxKeys": 1
},
"Next": "FileExistsChoice",
"Assign": {
"KeyCount": "{% $states.result.KeyCount %}"
}
},
"FileExistsChoice": {
"Type": "Choice",
"Choices": [
{
"Condition": "{% $KeyCount = 1 %}",
"Next": "ReadCSV"
}
],
"Default": "FileNotFound"
},
"FileNotFound": {
"Type": "Succeed",
"Comment": "S3 にファイルが存在しないため、処理をスキップして正常終了"
},
"ReadCSV": {
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:getObject",
"ReaderConfig": {
"InputType": "CSV",
"CSVHeaderLocation": "FIRST_ROW"
},
"Arguments": {
"Bucket": "cm-hirai-sf-csv-read",
"Key": "destination_phone_numbers.csv"
}
},
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "DISTRIBUTED",
"ExecutionType": "STANDARD"
},
"StartAt": "WaitBeforeStartOutboundVoiceContact",
"States": {
"WaitBeforeStartOutboundVoiceContact": {
"Type": "Wait",
"Seconds": "{% $number($states.input.waittime) %}",
"Next": "StartOutboundVoiceContact"
},
"StartOutboundVoiceContact": {
"Type": "Task",
"Arguments": {
"ContactFlowId": "91cc561d-32f7-4f7d-9c79-042013854b5c",
"DestinationPhoneNumber": "{% $states.input.DestinationPhoneNumber %}",
"InstanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c",
"SourcePhoneNumber": "+81xxxxxxxxx",
"Attributes": {
"answered": "false",
"callId": "{% $states.context.Execution.Id %}"
}
},
"Resource": "arn:aws:states:::aws-sdk:connect:startOutboundVoiceContact",
"Next": "WaitForCallResult",
"Assign": {
"ContactId": "{% $states.result.ContactId %}"
}
},
"WaitForCallResult": {
"Type": "Wait",
"Seconds": 65,
"Next": "GetContactAttributes"
},
"GetContactAttributes": {
"Type": "Task",
"Arguments": {
"InitialContactId": "{% $ContactId %}",
"InstanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c"
},
"Resource": "arn:aws:states:::aws-sdk:connect:getContactAttributes",
"Next": "EvaluateCallOutcome",
"Assign": {
"answered": "{% $states.result.Attributes.answered %}"
}
},
"EvaluateCallOutcome": {
"Type": "Choice",
"Choices": [
{
"Next": "CallUnansweredSuccess",
"Condition": "{% $answered = 'false' %}"
},
{
"Next": "CallAnsweredSuccess",
"Condition": "{% $answered = 'true' %}"
}
],
"Default": "CallProcessingFailed"
},
"CallUnansweredSuccess": {
"Type": "Succeed"
},
"CallProcessingFailed": {
"Type": "Fail"
},
"CallAnsweredSuccess": {
"Type": "Succeed"
}
}
},
"MaxConcurrency": 1000,
"End": true
}
},
"QueryLanguage": "JSONata"
}
処理の概要は以下の通りです。
-
ファイルの存在確認 (
CheckFileExists
)- S3バケット
cm-hirai-sf-csv-read
にdestination_phone_numbers.csv
が存在するか確認する。 KeyCount
を取得し、次のステップへ進む。
- S3バケット
-
ファイルの有無による分岐 (
FileExistsChoice
)KeyCount = 1
の場合、ReadCSV
ステートへ進む。- ファイルが存在しない場合、
FileNotFound
ステートへ進み、処理を終了する。
-
CSVファイルの読み取り (
ReadCSV
)- S3バケット
cm-hirai-sf-csv-read
からdestination_phone_numbers.csv
を取得し、CSVデータを読み込む。 waittime
とDestinationPhoneNumber
を取得し、並列処理を開始する。 (waittimeの設定理由は後述)
- S3バケット
-
架電前の待機 (
WaitBeforeStartOutboundVoiceContact
)waittime
の秒数だけ待機し、次のステップへ進む。 (設定理由は後述)
-
架電の実行 (
StartOutboundVoiceContact
)StartOutboundVoiceContact
API を使用して Amazon Connect で架電を実行する。- 架電時、コンタクト属性でキー名:answered、値:falseを設定します。(設定理由は後述)
ContactId
を取得し、次のステップへ進む。
-
架電結果の待機 (
WaitForCallResult
)- 65秒間待機し、次のステップへ進む。(待機時間の設定理由は後述)
-
架電結果の取得 (
GetContactAttributes
)GetContactAttributes
API を使用して、架電結果(answered
)を取得する。
-
架電結果の評価 (
EvaluateCallOutcome
)answered = 'false'
(電話が繋がらなかった) の場合、CallUnansweredSuccess
へ進む。- 架電失敗 (
CallUnansweredSuccess
)- 架電が失敗した場合、処理を正常終了する。
- 架電失敗 (
answered = 'true'
(電話が繋がった) の場合、CallAnsweredSuccess
へ進む。- 架電成功 (
CallAnsweredSuccess
)- 架電が成功した場合、処理を正常終了する。
- 架電成功 (
- それ以外の場合、
CallProcessingFailed
へ進む。- 架電処理エラー (
CallProcessingFailed
)- 何らかのエラーが発生した場合、処理を失敗として終了する。
- 架電処理エラー (
作成時に IAM ロールとポリシーが自動作成されますが、一部の IAM ポリシーは手動で作成する必要があります。
そのため、先ほど作成したポリシーを適用しましょう。
waittimeが必要な理由
- 架電前の待機 (
WaitBeforeStartOutboundVoiceContact
)
waittime
の秒数だけ待機し、次のステップへ進む。
StartOutboundVoiceContact API には1秒あたり2リクエストの制限があり、この制限を超えるとRate Limit Exceeded(レート制限超過)のエラーが発生し、架電処理が失敗します。
そのため、リクエストの送信間隔を適切に調整する必要があります。
上限緩和は可能ですが、今回は行いません。
この問題を解決するために、destination_phone_numbers.csv
に waittime
列を追加し、StartOutboundVoiceContact
実行前に Wait
ステートで待機するようにしています。
waittime,DestinationPhoneNumber
1,+8190xxxxxxx1
2,+8190xxxxxxx2
3,+8190xxxxxxx3
4,+8190xxxxxxx4
5,+8190xxxxxxx5
このwaittimeの値を使用し、各リクエストがn秒待機してからStartOutboundVoiceContactを実行することで、1秒あたり2リクエストの制限を超えないように制御できます。
Step Functions の Wait ステートでは、待機時間を Seconds に JSONata 式を指定できます。これにより、CSV の waittime 列の値を動的に適用し、各リクエストの送信タイミングを調整できます。
"WaitBeforeStartOutboundVoiceContact": {
"Type": "Wait",
"Seconds": "{% $number($states.input.waittime) %}",
"Next": "StartOutboundVoiceContact"
}
この方法を採用することで、API 制限を遵守しつつ、エラーを回避しながら効率的に架電処理を実行できます。
また、架電リストの各エントリごとに異なる待機時間を設定できるため、リクエストの送信タイミングを柔軟に調整することが可能です。
さらに、架電処理の並列実行を維持しつつ、リクエストを適切に制御できるため、全体の処理効率を向上させることができます。
架電結果の取得ロジック
- 架電の実行 (
StartOutboundVoiceContact
)
StartOutboundVoiceContact
API を使用して Amazon Connect で架電を実行する。- 架電時、コンタクト属性でキー名:answered、値:falseを設定します。(設定理由は後述)
ContactId
を取得し、次のステップへ進む。
StartOutboundVoiceContact
API 実行時に、コンタクト属性として キー名: "answered"
、値: "false"
を設定します。
電話が繋がった場合、Amazon Connect のフロー内で設定している [コンタクト属性の設定] ブロックにおいて、 キー: "answered"
、値: "true"
に更新されます。
そのため、GetContactAttributes
API を使用して架電結果を取得すると、
- 電話が繋がった場合 →
"answered": "true"
- 電話が繋がらなかった場合 →
"answered": "false"
が取得できる仕組みになっています。
以下のように Step Functions の状態を設定することで、架電結果を取得し、処理を分岐できます。
"GetContactAttributes": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:connect:getContactAttributes",
"Arguments": {
"InitialContactId": "{% $ContactId %}",
"InstanceId": "3ff2093d-af96-43fd-b038-3c07cdd7609c"
},
"Next": "EvaluateCallOutcome",
"Assign": {
"answered": "{% $states.result.Attributes.answered %}"
}
}
取得した "answered"
の値に基づき、以下のように処理を分岐します。
"EvaluateCallOutcome": {
"Type": "Choice",
"Choices": [
{
"Next": "CallAnsweredSuccess",
"Condition": "{% $answered = 'true' %}"
},
{
"Next": "CallUnansweredSuccess",
"Condition": "{% $answered = 'false' %}"
}
],
"Default": "CallProcessingFailed"
}
このロジックにより、
- 通話が成功した場合 →
"CallAnsweredSuccess"
に進む - 通話が失敗した場合 →
"CallUnansweredSuccess"
に進む - その他のエラーが発生した場合 →
"CallProcessingFailed"
に進む
これにより、架電結果に応じた適切な処理を実装できます。
架電結果取得までの待機時間について
- 架電結果の待機 (
WaitForCallResult
)
- 65秒間待機し、次のステップへ進む。(待機時間の設定理由は後述)
Step Functions では、架電後に一定時間待機してから GetContactAttributes
API を呼び出します。
待機時間の設定理由として、StartOutboundVoiceContact
API を使用して発信した場合、60秒間応答がないと自動的に通話が切断される 仕様があるためです。
そのため、架電結果を取得する前に十分な待機時間(例: 65秒)を設定することで、通話の成否を正しく判定できます。
"WaitForCallResult": {
"Type": "Wait",
"Seconds": 65,
"Next": "GetContactAttributes"
}
この待機時間を適切に設定することで、架電結果の取得タイミングを最適化し、正確な通話ステータスを取得することができます。
試してみる
ステートマシンを実行すると、架電リストに設定した電話番号に順次発信されました。
各顧客の発信結果は、実行結果の「マップ実行」から確認できます。
今回は、顧客リストには5件登録したため、5件実行結果が確認できます。
電話が繋がらなかった場合
電話が繋がった場合
最後に
本記事では、AWS Step Functions を活用して CSVファイルの架電リストを基に Amazon Connect でオートコールを行い、架電結果を取得する方法 を解説しました。
本記事のポイントは以下の点です。
- Step Functions を使用した架電処理の自動化
- S3 に保存された CSV ファイルを読み取り、Amazon Connect で自動発信を実行。
- API 制限を考慮した処理設計
StartOutboundVoiceContact
API の 1秒あたり2リクエスト 制限を回避するため、waittime
を設定し、リクエストの送信間隔を調整。
- 架電結果の取得と処理の分岐
GetContactAttributes
API を使用し、コンタクト属性"answered"
の値を取得。- 電話が繋がった場合 →
"answered": "true"
- 電話が繋がらなかった場合 →
"answered": "false"
- 取得結果に基づき、適切な処理を実行可能。
AWS Step Functions を活用することで、柔軟かつスケーラブルな架電システムを構築でき、業務の自動化や効率化を実現できます。