【AWS】Amazon Connectでの自動電話通知について

Amazon Connectは、AWSが提供しているクラウド上のコールセンタープラットフォームです。通話や使用時間に基づいた課金システムを採用しており、これにより従来のコールセンターで発生する電話回線などの機器使用料の心配なく利用できます。

弊社ではインフラ環境に対して24x7で監視・通知を行っており、障害が検知された場合は、その内容によってはインフラ担当またはシステムエンジニアにメール通知する仕組みが構築されています。ただし、メール通知に使用しているメールサーバーは社内のものであるため、社内ネットワークやシステムなどが大規模な障害を起こした場合、メール通知が不可能になるという課題がありました。

これを解決するために、インフラ担当とシステムエンジニアは社用携帯電話を所持しており、障害が発生した際にはメールだけでなく、緊急度によってはSlackや電話で社用携帯に通知が届くような仕組みを検討しました。そして、Amazon Connectの通話機能を活用して、緊急度が高い場合には直接インフラ担当やシステムエンジニアに電話通知できるようなシステムを構築することにしました。
###### ■イメージ
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/2b9ff996-045a-dabb-9550-db66e480634f.png)
※今回はAmazon Connect StartOutboundVoiceContact APIを使用して、監視担当にアウトバウンドコールをする方法、ポイントについて説明します。

###### ■ポイント
Amazon ConnectのStartOutboundVoiceContact APIを利用するLambda関数は、既存のオンプレミス環境での監視プログラムでも使用できるように、API Gatewayを構築します。既存のVBScriptプログラムは、Lambda関数にPOSTリクエストを送信し、監視担当者に電話通知を行います。

通知先の電話番号や関連情報はDynamoDB Table①に保存しています。また、電話が「繋がった」か「繋がっていない」かを管理するために通話履歴はDynamoDB Table②に保存されます。

電話通知はAmazon Connectを介して行われ、通話発信後の待ち時間は30秒となります。監視担当者は順番に電話通知され、A → B → ... といった順序で呼び出しが行われます。

###### ■Amazon Connect
・発信者電話番号を事前に取得する必要です。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/4f3cb155-869e-6ba1-e18f-6a3db1c6534e.png)

・Sample-flow2-SMJの内容（ここで、誤って発信者電話番号に電話をしてくれる場合、どう案内すれば良いか事前定義しておきます）
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/9fa64c11-6d64-ba34-5d80-710ead0af048.png)

・Lambdaからコールする場合、入るフローを定義する（Sample-flow-SMJ）
![キャプ-チャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/ed1704df-6200-b452-356d-93f95cceedfd.png)

:::note info
下記ソースコードはAmazon Connect StartOutboundVoiceContact APIの使い方について作成したサンプルコードであり、イメージ図に記載した仕様に対応したソースコードではありません。
:::

###### Lambda（connect-ms）
###### 実行ロール
```ruby:qiita.rb
AWSLambdaBasicExecutionRole
AmazonConnect_FullAccess
AmazonDynamoDBFullAccess
```
###### conClient.mjs
```ruby:qiita.rb
import { ConnectClient } from "@aws-sdk/client-connect"
const REGION = "ap-northeast-1"
const conClient = new ConnectClient({ region: REGION })	
export { conClient }
```
###### ddbClient.mjs
```ruby:qiita.rb
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
const REGION = "ap-northeast-1"
const ddbClient = new DynamoDBClient({ region: REGION })
export { ddbClient }
```
###### index.mjs
```ruby:qiita.rb
import {StartOutboundVoiceContactCommand,GetContactAttributesCommand,StopContactCommand } from "@aws-sdk/client-connect";
import { PutItemCommand } from "@aws-sdk/client-dynamodb";
import { conClient } from "./conClient.mjs";
import { ddbClient } from "./ddbClient.mjs";
import { setTimeout } from "timers/promises";

export const handler = async (event) => {
let ConContactFlowId = process.env.ConContactFlowId
let ConDestinationPhoneNumber = process.env.ConDestinationPhoneNumber
let ConInstanceId = process.env.ConInstanceId
let ConSourcePhoneNumber = process.env.ConSourcePhoneNumber
let TelMessage = "ただいま、障害は発生しております。至急ホームページにアクセス頂き、内容を確認してください。"
let connect = {}

let paramscon = {
  ContactFlowId: ConContactFlowId,
  DestinationPhoneNumber: ConDestinationPhoneNumber,
  InstanceId: ConInstanceId,
  SourcePhoneNumber: ConSourcePhoneNumber,
  Attributes: {
    'message': TelMessage,
    'isTalking': 'false'
  }
};

console.log(Math.floor(Date.now() / 1000))
let ExpirationTime = Math.floor(Date.now() / 1000) + 3600
let paramsddb = {
  TableName: "Tellogtbl",
  Item: {
    id : { S: new Date() },
    SourcePhoneNumber: { S: ConSourcePhoneNumber },
    DestinationPhoneNumber: { S: ConDestinationPhoneNumber },
    TelMessage: { S: TelMessage },
    ExpirationTime: { N: ExpirationTime.toString() }
  },
};

try {
  connect = await conClient.send(new StartOutboundVoiceContactCommand(paramscon));
  console.log(JSON.stringify(connect, undefined, 2));
  const data = await ddbClient.send(new PutItemCommand(paramsddb));
  console.log("Successfully create item into Tellogtbl.", data);
} catch (err) {
  console.log("Error", err);
}

await setTimeout(30000);
const attributes = await conClient.send(new GetContactAttributesCommand(
  {
    InstanceId: ConInstanceId,
    InitialContactId: connect.ContactId
  }));

console.log(JSON.stringify(attributes, undefined, 2));
if (attributes.isTalking == 'false') {
  const disconnect = await conClient.send(new StopContactCommand({
    InstanceId: ConInstanceId, InitialContactId: connect.ContactId
  }))
  console.log(JSON.stringify(disconnect, undefined, 2));
}
```
###### ■DynamoDB
![無w題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/1b525d04-3773-f16b-67ca-2f54c2efbc33.png)
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600067/52602803-aa24-a964-13e4-91946b330332.png)

自動的に通話履歴は削除されるようTTL設定しました。

###### ■まとめ
Amazon ConnectはAWS Lambdaとの連携により、電話が繋がらなかった場合の分岐条件など、細かな実装が可能です。また、DynamoDBとの連携により、電話が繋がったかの状態を保持できます。これにより、AWSの他のサービスとの連携による柔軟なカスタマイズが要件に合わせて実現できる点が非常に便利です。

さらに、インフラ基盤の状況を24x7でモニタリングし、障害が発生した場合は監視担当者から直接電話でインフラ担当者やシステムエンジニアに連絡するといった、従来の手法と比較して監視担当者が不要になり、それに伴って人件費を大幅に節約できるようになりました。