AWS Step FunctionsでCSVファイルの架電リストをもとにAmazon Connectでオートコールを行い、架電結果を取得してみた

AWS Step FunctionsでCSVファイルの架電リストをもとにAmazon Connectでオートコールを行い、架電結果を取得してみた

Clock Icon2025.03.19

はじめに

本記事では、AWS Step Functionsを使用してCSVファイルの架電リストをもとにAmazon Connectでオートコールを行い、架電結果を取得する方法について紹介します。

業務において、架電対象リストに基づいて自動発信し、通話の成否(電話が繋がったかどうか)に応じて処理を分岐させたいケースは多くあります。このような処理は、AWS Step Functionsを活用することで実現可能です。

本記事で紹介する構成は以下の通りです。
なお、④の架電結果に応じた処理の分岐については、今回は検証のため実装しません。
また、Step Functions は外部 API を呼び出せるため、架電リストは S3 バケットに保存された CSV ファイルだけでなく、CRM システムから取得することも可能です。

cm-hirai-screenshot 2025-03-18 13.26.52

架電結果に応じて、以下のような処理を実装することが可能です。

  • 顧客に電話が繋がらなかった場合、SMSを送信して案内を行う
  • 架電リストのファイルを削除し、電話が繋がらなかった顧客をリストアップして、新たな架電リストとしてS3にアップロードする
  • 架電結果(繋がった/繋がらなかった)に関わらず、CRMに結果を保存する
  • 繋がった場合、顧客が選択したIVRの内容や、エージェントと会話した内容もCRMに保存する

本システムの活用が想定されるユースケースとして、以下のようなシナリオが考えられます。

  • 予約のリマインドコール
    • 病院や美容院、レストランなどの予約システムと連携し、予約日前日に自動でリマインドコールを行う。顧客が電話に出なかった場合はSMSでリマインドメッセージを送信する。
  • 支払い期限のリマインド
    • クレジットカード会社や公共料金の請求において、支払い期限が近い顧客に自動で架電を行い、支払いを促す。
    • 顧客が電話に出なかった場合、Eメールでリマインドを行う。
  • キャンペーンや新商品のお知らせ
    • EC サイトや小売業者が、特定の顧客に対してキャンペーン情報や新商品の案内を自動で架電する。 興味を持った顧客にはオペレーターへ転送する。
  • 契約の更新案内
    • 保険会社が、契約更新の時期が近づいた顧客に自動で架電し、更新手続きを促す。
    • 顧客が電話に出なかった場合、架電リストを更新し、再架電を試みる

Connectフロー

オートコール用のConnectフローは以下の通りです。

cm-hirai-screenshot 2025-03-18 14.07.58

[コンタクト属性の設定] ブロックでは、キー「answered」、値「true」の設定が必須です。
架電時に電話が繋がった場合、true と定義することで、通話が成功したことを確認できます。

cm-hirai-screenshot 2025-03-18 14.10.13

架電リスト

架電リストを保存するS3バケット(cm-hirai-sf-csv-read)を作成し、架電リストのCSVファイル(destination_phone_numbers.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": "*"
        }
    ]
}

ステートマシンの作成

ステートマシンを作成します。

cm-hirai-screenshot 2025-03-18 11.58.15

以下のコードを貼り付けます。必要に応じて、以下の項目を変更してください。

  • 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"
}

cm-hirai-screenshot 2025-03-19 8.48.49
処理の概要は以下の通りです。

  1. ファイルの存在確認 (CheckFileExists)

    • S3バケット cm-hirai-sf-csv-readdestination_phone_numbers.csv が存在するか確認する。
    • KeyCount を取得し、次のステップへ進む。
  2. ファイルの有無による分岐 (FileExistsChoice)

    • KeyCount = 1 の場合、ReadCSV ステートへ進む。
    • ファイルが存在しない場合、FileNotFound ステートへ進み、処理を終了する。
  3. CSVファイルの読み取り (ReadCSV)

    • S3バケット cm-hirai-sf-csv-read から destination_phone_numbers.csv を取得し、CSVデータを読み込む。
    • waittimeDestinationPhoneNumber を取得し、並列処理を開始する。 (waittimeの設定理由は後述)
  4. 架電前の待機 (WaitBeforeStartOutboundVoiceContact)

    • waittime の秒数だけ待機し、次のステップへ進む。 (設定理由は後述)
  5. 架電の実行 (StartOutboundVoiceContact)

    • StartOutboundVoiceContact API を使用して Amazon Connect で架電を実行する。
    • 架電時、コンタクト属性でキー名:answered、値:falseを設定します。(設定理由は後述)
    • ContactId を取得し、次のステップへ進む。
  6. 架電結果の待機 (WaitForCallResult)

    • 65秒間待機し、次のステップへ進む。(待機時間の設定理由は後述)
  7. 架電結果の取得 (GetContactAttributes)

    • GetContactAttributes API を使用して、架電結果(answered)を取得する。
  8. 架電結果の評価 (EvaluateCallOutcome)

    • answered = 'false'(電話が繋がらなかった) の場合、CallUnansweredSuccess へ進む。
      • 架電失敗 (CallUnansweredSuccess)
        • 架電が失敗した場合、処理を正常終了する。
    • answered = 'true'(電話が繋がった) の場合、CallAnsweredSuccess へ進む。
      • 架電成功 (CallAnsweredSuccess)
        • 架電が成功した場合、処理を正常終了する。
    • それ以外の場合、CallProcessingFailed へ進む。
      • 架電処理エラー (CallProcessingFailed)
        • 何らかのエラーが発生した場合、処理を失敗として終了する。

作成時に IAM ロールとポリシーが自動作成されますが、一部の IAM ポリシーは手動で作成する必要があります。
そのため、先ほど作成したポリシーを適用しましょう。
cm-hirai-screenshot 2025-03-18 12.01.38

waittimeが必要な理由

  1. 架電前の待機 (WaitBeforeStartOutboundVoiceContact)
  • waittime の秒数だけ待機し、次のステップへ進む。

StartOutboundVoiceContact API には1秒あたり2リクエストの制限があり、この制限を超えるとRate Limit Exceeded(レート制限超過)のエラーが発生し、架電処理が失敗します。
そのため、リクエストの送信間隔を適切に調整する必要があります。
上限緩和は可能ですが、今回は行いません。

https://docs.aws.amazon.com/ja_jp/general/latest/gr/connect_region.html

この問題を解決するために、destination_phone_numbers.csvwaittime 列を追加し、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 制限を遵守しつつ、エラーを回避しながら効率的に架電処理を実行できます。
また、架電リストの各エントリごとに異なる待機時間を設定できるため、リクエストの送信タイミングを柔軟に調整することが可能です。
さらに、架電処理の並列実行を維持しつつ、リクエストを適切に制御できるため、全体の処理効率を向上させることができます。

架電結果の取得ロジック

  1. 架電の実行 (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" に進む

これにより、架電結果に応じた適切な処理を実装できます。

架電結果取得までの待機時間について

  1. 架電結果の待機 (WaitForCallResult)
    • 65秒間待機し、次のステップへ進む。(待機時間の設定理由は後述)

Step Functions では、架電後に一定時間待機してから GetContactAttributes API を呼び出します。

待機時間の設定理由として、StartOutboundVoiceContact API を使用して発信した場合、60秒間応答がないと自動的に通話が切断される 仕様があるためです。

https://docs.aws.amazon.com/connect/latest/APIReference/API_StartOutboundVoiceContact.html

そのため、架電結果を取得する前に十分な待機時間(例: 65秒)を設定することで、通話の成否を正しく判定できます。

"WaitForCallResult": {
  "Type": "Wait",
  "Seconds": 65,
  "Next": "GetContactAttributes"
}

この待機時間を適切に設定することで、架電結果の取得タイミングを最適化し、正確な通話ステータスを取得することができます。

試してみる

ステートマシンを実行すると、架電リストに設定した電話番号に順次発信されました。
cm-hirai-screenshot 2025-03-19 8.47.54
各顧客の発信結果は、実行結果の「マップ実行」から確認できます。

cm-hirai-screenshot 2025-03-19 9.02.35

今回は、顧客リストには5件登録したため、5件実行結果が確認できます。
cm-hirai-screenshot 2025-03-19 9.04.03

cm-hirai-screenshot 2025-03-19 9.04.16
電話が繋がらなかった場合

cm-hirai-screenshot 2025-03-19 9.04.31
電話が繋がった場合

最後に

本記事では、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 を活用することで、柔軟かつスケーラブルな架電システムを構築でき、業務の自動化や効率化を実現できます。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.