こんにちは、エンジニアの君島です。
ギークフィード、クラスメソッドさん、ユニフォームネクストさん、AWSJさんの有志でAmazon ConnectのAdvent Calendarを開催しています。
今年もre:Invent前後でConnect関連で200以上ものアップデートがありました。
にも関わらず、ConnectではなくAWS Lambdaのアップデートをフィーチャーしつつ、Amazon Connectを入力チャネルに使ってみることにしました。
目次
はじめに
AWS Lambdaに永続関数、Durable Functionsの機能が追加されました。
そこで、どんな時に利用できるか使いどころを思案していたのですが、シンプルなワークフローで人間の入力が介在するようなケースで使えそうではないかと考えました。せっかくなのでAmazon Connectで承認をする仕組みを考えてみることにします。
AWS Step Functionsでも同様のことができるわけですが、もっとシンプルなアーキテクチャでも実現できそうなので試してみることにしました。
なお、Durable Functionsは、つい最近までusリージョンでしか使用できなかったようですが、東京リージョンでも利用することができるようになっていました。
re:invent直後はusリージョンでしか使用できない機能が多いですが、2,3週もすると日本も対応しているのはありがたいですね。
Connectの3rd-party アプリの設定もMCP機能が東京リージョンでも追加されていたりました。
さて、話を戻してアーキテクチャを考えていきましょう。
アーキテクチャ
Durable FunctionsのLambda関数
ワークフロー処理の本体。LambdaのDurable Functionsとして実装します。
以下のような実装を行いました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import { withDurableExecution } from '@aws/durable-execution-sdk-js'; import { ConnectClient, StartOutboundVoiceContactCommand } from "@aws-sdk/client-connect"; import { DynamoDBClient } from "@aws-sdk/client-dynamodb"; import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb"; const connect = new ConnectClient({}); const ddbDoc = DynamoDBDocumentClient.from(new DynamoDBClient({})); export const handler = withDurableExecution(async (event, context) => { context.logger.info(`Lambda Started.`); const [promise, callbackId] = await context.createCallback("approval", { timeout: { mitute: 1 }, }); const phoneNumber = process.env.DESTINATION_PHONE_NUMBER; const contactId = await context.step("MakeIVRCall", async () => { const command = new StartOutboundVoiceContactCommand({ DestinationPhoneNumber: phoneNumber, ContactFlowId: process.env.CONTACT_FLOW_ID, InstanceId: process.env.INSTANCE_ID, SourcePhoneNumber: process.env.SOURCE_PHONE_NUMBER }); const response = await connect.send(command); return response.ContactId; }); context.logger.info(`Call initiated. ContactId: ${contactId}`); await context.step("StoreToken", async () => { await ddbDoc.send(new PutCommand({ TableName: process.env.TABLE, Item: { ContactId: contactId, CallbackToken: callbackId } })); }); context.logger.info("waiting.."); const approvalRaw = await promise; const approval = typeof approvalRaw === 'string' ? JSON.parse(approvalRaw) : approvalRaw; await context.step("NotifySlack", async () => { context.logger.info(`approval: ${JSON.stringify(approval, null, 2)}`); context.logger.info(`approval.digits: ${approval.digits}`); const statusMsg = approval.digits === "1" ? "✅承認" : "❌否認"; await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', body: JSON.stringify({ text: `IVR結果: ${statusMsg}\\n対象: ${phoneNumber}` }) }); }); return { statusCode: 200, body: JSON.stringify({ message: "Hello from Durable Lambda!" }), }; }); |
Durable Functionを再開させるためにはCallback Idが必要になるのですが、マネコン上からは表示されないようです。
そのため、コード上で取得したものをDynamoDBに保存するようにしました。
また、Durable Functionsの最後はSlack通知にしたため、以下のような通知結果となります。

Durable Functionsを再開させるLambda関数
Amazon Connectのフローから呼び出され、上記のDurable Functionsを再開させるLambda関数です。再開させるためにはDurable FunctionsのCallbackIDが必要となります。
こちらはDurable Functionとして作成する必要はありませんが、SendDurableExecutionCallbackSuccessCommandがLambda標準のAWS-SDKにはまだ含まれていないようなので、ローカル上で最新のSDKを利用してデプロイして下さい。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
const { LambdaClient, SendDurableExecutionCallbackSuccessCommand } = require("@aws-sdk/client-lambda"); const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); const { DynamoDBDocumentClient, GetCommand } = require("@aws-sdk/lib-dynamodb"); const client = new LambdaClient({}); const dynamoClient = new DynamoDBClient({}); const docClient = DynamoDBDocumentClient.from(dynamoClient); exports.handler = async (event, context) => { const contactId = event.Details?.ContactData?.ContactId; const sentDigits = event.Details?.Parameters?.SentDigits || ''; try { const params = { TableName: process.env.TABLE, Key: { ContactId: contactId } }; const result = await docClient.send(new GetCommand(params)); if (!result.Item) { throw new Error(`No item found for ContactId: ${contactId}`); } if (!result.Item.CallbackToken) { throw new Error(`CallbackToken not found in item for ContactId: ${contactId}`); } const command = new SendDurableExecutionCallbackSuccessCommand({ FunctionName: "DurationApproval", CallbackId: result.Item.CallbackToken, Result: JSON.stringify({ approved: true, approver: "kimishima", digits: sentDigits }) }); await client.send(command); return { statusCode: 200, body: JSON.stringify({ message: 'Success' }) }; } catch (error) { console.error(`Error: ${error.message}`); throw error; } }; |
Amazon Connect
音声によるユーザーの入力を実現し、再開させるLambda関数をフローから呼び出します。
例えば、以下のようなフローを作成しておきます。

フローから呼ばれるLambda関数の設定も忘れないようにしておきましょう。

DynamoDB
通話ごとに一意に定まるContactIdと、Durable FunctionsのCallbackIdをキーに持つテーブルが必要となります。レコード自体はDurable Functionsでの待ち時間以上に保存する必要がないので、TTLでレコードを削除させるのが現実的かと思います。
ワークフローの実行
以下のような流れとなります。
- ワークフローを開始する代わりに、Durable FunctionsのLambda関数を実行します。
- Durable FunctionsのLambda関数からStartOutboundVoiceContactCommandで発信通話実行
- Durable FunctionsのLambda関数がDynamoDBに通話のContact IdとDurable FunctionsのCallback Idを保存
- ユーザーが通話に出た場合、DTMF入力によって承認を実施
- Amazon Connectのコールフロー設定により、Durable Functionsを再開させるLambda関数を実行
- Durable Functionsを再開させるLambda関数がContact IdをキーにDynamoDBを検索し、Callback Idを元にDurable Functionsを再開する
- Durable FunctionsのLambda関数が再度実行される
Durable Functions自体の実行結果は以下のように、Stepとタイムラインがマネコン上に表示されるようになっています。

まとめ
AWS LambdaでリリースしたばかりのDurable Functionsを使って、Amazon Connectで有人承認を行うワークフローを作成してみました。電話で追い立てられる承認は結果が素早く出ますね。自分が承認する側だったら、あると怖いなとも思いました。
複雑な遷移が発生しないで済むような場合であれば、このようなシンプルなサービス構成でもワークフローの実現が可能でした。
今回は考慮していないですが、ユーザーが承認できなかった場合のリトライも現実には必要になってくるでしょう。Durable Functionsでフローを定義しないといけないので、そのままの勢いで単純にStartOutboundVoiceContactCommandの実行にリトライを噛ませてしまうと、APIの実行自体のエラーハンドリングとなってしまうので注意です。ユーザーの操作に応じて変えるのであれば、DynamoDBのレコードを監視するか、コールフロー上で設定するようにする必要がありそうです。
また、承認相手が複数いる場合には、これまた新機能のConnectのデータテーブルで複数の電話番号を格納しておくことで活用することもできそうです。
- Amazon ConnectでLambdaのDurable Functionsを承認して動してみよう - 2025-12-20
- 【困ってなかった?】AWS CloudShellの700MB縛りがKiro CLIで解かれた - 2025-12-05
- 優しい易しいKiro入門~設定とプラクティスを公開します~ - 2025-12-03
- AWS CloudShellでAmazon Q Developer CLIに名残を惜しみつつKiro CLIを使ってみる - 2025-11-25
- 過去に開発したアプリをKiroのスペック駆動で再開発してみた - 2025-10-04
【採用情報】一緒に働く仲間を募集しています



