Twilio Studioを使って簡易的な受付電話を作る

# はじめに
Twilio Studioは簡単に電話応答フローを作ることができます。
今回は想定されるシナリオに対して、いくつかのTipsを紹介します。

# 今回の想定シナリオ
- 営業時間内に電話がかかってくると、自動応答で接続する部署のメンバーに転送する。
- エンジニア部門には、担当者全員のケータイに一斉コール。
- リサーチ部門には、担当者間で予め決めておいた順番でコール。
- 営業時間外に電話がかかってきたら、留守番電話に録音して、担当者に通知。

# 今回の必要となる処理
- 複数の担当者のケータイ電話を同時に鳴らす
- 複数の担当者のケータイ電話を順番に鳴らす
- アナウンス後、ボタンをプッシュして部署を切り分け
- 営業時間外判定をする
- 留守番電話録音して通知する

# 各処理を作っていく
## 複数の担当者のケータイ電話を同時に鳴らす
`Connect Call To`ウィジェットを利用します。
![スクリーンショット 2022-07-13 15.38.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/1bfaf57c-2ce7-dc80-ccae-41f1f730ac84.png)
ウィジェットの設定で`CONNECT CALL TO`を`Multiple Numbers (Simulring)`に設定します。  
`PHONE NUMBERS OR CLIENTS`には、カンマ(,)区切りで転送先電話番号を複数指定します。ちなみに電話番号は+81で始まるいわゆる国際電話番号のフォーマット(E.164)で指定する必要があります。

## 複数の担当者のケータイ電話を順番に鳴らす
TwiML Binsを使いTwiMLを作成し、`ADD TWIML REDIRECT`ウィジェットを使ってTwiML BinsをStudioから呼び出すようにします。TwiML Binsは管理コンソール左から選択できます。
![スクリーンショット 2022-07-13 15.54.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/2ac408ec-c6e1-29a8-8b7b-788804415c2a.png)
`TWIML`には以下を記載します。
```xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
	<Dial callerId="+8150********" timeout="20" sequential="true">
		<Number>+819000000001</Number>
		<Number>+819000000002</Number>
	</Dial>
</Response>
```
`callerId`には受付電話番号をE.164フォーマットで記載、`Number`には、転送したい番号をE.164フォーマットで`<Number></Number>`で囲い記載します。記載した電話番号の順番に架電をします。
Saveをクリックすると、`Properties`の`URL`にURLが記載されますので、そちらをコピーしておきます。

Studioの`ADD TWIML REDIRECT`ウィジェットを使って、さきほどのTwiML Binsを呼び出します。
![Twiml呼び出し.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/d8e9bff3-61e1-06d6-6bbe-b800a54deda3.png)

`URL`には、さきほどのTwiML BinsのURLを入力します。


## アナウンス後、ボタンをプッシュして部署を切り分け
アナウンスとボタンのプッシュを取得するために`Gather Input on Call`ウィジェット、押されたボタンの切り分けのために`Split Based On...`ウィジェットを使います。
![スクリーンショット 2022-07-13 17.02.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/1df66e89-4b99-89e6-b5b5-d2fb7690168a.png)
`Gather Input on Call`の`TEXT TO SAY`には、アナウンスする内容を記入します。`LANGUAGE`は`Japanese`にすることをお忘れなく。ちなみに`SAY OR PLAY MESSAGE`で`Play a Message`を選択すると、mp3ファイルを再生させることもできます。
![スクリーンショット 2022-07-13 17.05.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/4ed6de61-4bd7-2eed-ebea-7b13851f389f.png)
`Split Based On...`ウィジェットの設定では、`VARIABLE TO TEST`に`Gather Input on Call`ウィジェットの`Digits`を指定します。これは`Gather Input on Call`ウィジェットでプッシュされた番号が格納されています。その値に対して`CONDITION`を作成し、分岐させます。
`Gather Input on Call`の`User Pressed Keys`と`Split Based On...`をつなぎます。とりあえず今は分岐がわかるように`Say/Play`ウィジェットを接続しておきました。


## 営業時間外判定をする
Twilio Functionsを使い営業時間判定を行い、Studioから呼び出すようにします。
Functionsからの返答を取得し、フローを切り分けるために`Split Based On...`ウィジェットを使います。
`Twilio Functions`は管理コンソール左から選択できます。2種類あるので今回は`Functions(Classic)`を使います。
![スクリーンショット 2022-07-14 8.44.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/2dcbd6a7-10d0-287b-18b2-51ad97c92d33.png)

`CODE`には以下を記載します。`FUNCTION NAME`や`PATH`にはわかりやすい名前を入れておきます。
```javascript
exports.handler = function(context, event, callback) {
	let is_business_times = true;
	let date = new Date(Date.now() + (9 * 60 * 60 * 1000)); // UTC → JST
	let hour = date.getHours();
	let week = date.getDay();
    
    // 平日(月〜金)の9時から17時までを営業時間とする
    if(hour < 9 || hour >= 17 || week === 0 || week == 6){
        is_business_times = false;
    }
	callback(null, {"is_business_times" : is_business_times} );
};
```
callbackの第２引数にStudioに返却するデータを入れておくと良いです。
作ったFunctionをStudioで呼び出します。
![スクリーンショット 2022-07-13 20.53.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/898b60b6-f52b-437e-acf2-3de41e1179e3.png)
`RUN FUNCTION`ウィジェットを使います。`SERVICE`は`Default`を指定します。これはFunctions(Classic)のことになります。`FUNCTION URL`には、プルダウンで上記で作成したFunctionの名前を指定します。
![スクリーンショット 2022-07-13 20.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/4c9f8af7-7ccf-22bd-84f8-5eed5b486136.png)
`Split Based On...`ウィジェットでは、`VARIABLE TO TEST`に以下を記述します。
````
widgets.IsBusinessTimes.parsed.is_business_times
````
これは、`widgets.{RUN FUNCTIONウィジェットの名前}.parsed.{プロバティ名}`となっています。
これで、Functionが返した値を取得することができます。
とりあえず今は分岐がわかるように`Say/Play`ウィジェットを接続しておきました。trueなら営業時間、その他は営業時間外というようなアナウンスをSayするようにしています。


## 留守番電話録音して通知する
留守番電話録音するために`record_voicemail`ウィジェットを使います。
また、録音データをSMSで担当者に通知するための処理をTwilioFunctionsを使って作ります。
まずはTwilio FunctionsでSMSで担当者に通知する処理を作ります。
`Twilio Functions`は管理コンソール左から選択できます。2種類あるので今回は`Functions(Classic)`を使います。
![スクリーンショット 2022-07-13 21.40.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/59b83a76-b2aa-c09c-f700-363fcac123d0.png)
`CODE`には以下を記載します。`FUNCTION NAME`や`PATH`にはわかりやすい名前を入れておきます。
```javascript
exports.handler = async function (context, event, callback) {
    if (event.RecordingDuration !== 0 && event.RecordingStatus == 'completed') {
        let client = context.getTwilioClient();
        let body = '留守番電話にメッセージが届いています。' + event.RecordingUrl;
        await client.messages
            .create({
                body: body,
                from: '{送信元の電話番号}',
                to: '{SMS送信先の電話番号}'
            });
    }
    callback(null, {});
}
```
`from`には、送信元となるTwilioで購入したSMS送信可能な電話番号を記載します。`to`には通知を受けたいケータイ電話番号をE.164フォーマットで記載します。
Twilio Studioでは`RECORD VOICEMAIL`ウィジェットを使います。
![スクリーンショット 2022-07-13 21.47.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/cab90cef-7265-de16-4681-21ed33df8645.png)
`RECORDING STATUS CALLBACK`に、録音データ通知用に作成したFunctionの`URL`を記入します。

留守番電話に録音がされると、以下のようなSMSが指定した電話番号に届きます。メッセージの末尾に記載されているURLにアクセスすると録音された内容を聞くことができます。
````
留守番電話にメッセージが届いています。https://api.twilio.com/2010-04-01/Accounts/AC**************************/Recordings/RE************************
````

# 各処理をつなぎ合わせる
シナリオに沿って、各処理をつなぎ合わせると、以下のような感じのフローになります。
![スクリーンショット 2022-07-13 21.59.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/39497808-2d49-2939-d294-f36cd9dcbf82.png)
※エラー時フローはぐちゃぐちゃするので記載してません。任意に追加してくださいませ。

# 補足
この記事は[Qiita Engineer Festa 2022「Twilioを使うためのコツ、Tipsやおもしろ実装など、Twilioのことなら何でも共有しよう！」](https://qiita.com/official-events/be9762611b62168d13ce)への参加記事になります。
最近、リリースされた[Dev Phone](https://www.twilio.com/docs/labs/dev-phone)がテストをするときなどにかなり便利です。オススメ。