This software is to be considered "sample code", a Type B Deliverable, and is delivered "as-is" to the user. Twilio bears no responsibility to support the use or implementation of this software.
The native Flex UI only supports handling a single voice call task at a time. If a TaskRouter worker's voice task is increased beyond 1 and they accept a second incoming voice task, they will prompted to accept the new incoming call which will disconnect the current active call.
This sample solution attempts to address this limitation and allow a worker to accept an incoming voice task while they already have an active voice call, without losing that active voice call.
This sample solution was built with a very specific use case in mind: allowing a worker to receive direct calls while on any other type of call.
In many contact center environments, it's common for a worker to have a direct extension or external number and want to be able to answer those direct calls even if they are currently handling another call. That is the scenario this sample solution is attempting to solve for.
In support of the primary use case, the following business logic assumptions have been made:
- Inbound queue calls (ACD calls), or calls that are routed to a group of agents instead of one specific agent, are treated differently than direct calls to a specific worker
- Workers are only allowed to handle a single ACD call at a time, so they won't be routed another ACD call if they already have one assigned
- Workers are only allowed to receive an ACD if they don't have any other active call. So if they are already on a direct call or an outbound call, they will not receive an ACD call
This sample solution provides the basics for demonstrating this use case. It does not cover some natural extensions of this use case such as:
- Validating the worker extension entered in the IVR. Callers that enter an invalid extension will sit on hold indefinitely until they hang up since there is no matching worker to route the call to.
- Preventing a rejected direct call task from immediately routing back to the target worker. Additional logic would be required to do something when the task is rejected, such as updating the call wth TwiML to play a "Please try your call again later" message.
- Voicemail option for direct calls to workers that are not available
- Sending a direct call to voicemail if the targeted worker does not answer or rejects the task
- Visual presentation of a worker's voicemails
Since call tasks transferred in Flex to a TaskQueue only use TaskQueue expressions to determine eligible workers, it's necessary to use a worker attribute to control whether a worker should be able to receive a queue (ACD) call (see Business Logic Assumptions above).
This means that when a worker is not eligible to receive an ACD call because they have another call already, they will be removed from the TaskQueue and will not be part of the TaskQueue worker count on the Flex Realtime Queue View.
Also, when an active call is parked so an incoming call can be answered, that call will not show up as an active call on any TaskQueues since there is no task associated with the call while it is parked.
When a parked call is picked up, it will show up as an active call on the "Park Pickup" TaskQueue. The reasoning for this is explained below.
-
An active Twilio account with Flex provisioned. Refer to the Flex Quickstart to create one.
-
npm version 5.0.0 or later installed (type
npm -v
in your terminal to check) -
Node.js version 12 or later installed (type
node -v
in your terminal to check) -
Twilio CLI along with the Flex CLI Plugin and the Serverless Plugin. Run the following commands to install them:
# Install the Twilio CLI npm install twilio-cli -g # Install the Serverless and Flex as Plugins twilio plugins:install @twilio-labs/plugin-serverless twilio plugins:install @twilio-labs/plugin-flex
Some of the following setup steps will require Serverless Functions URLs, so we will deploy the Twilio Serverless Functions before proceeding with the remaining steps.
Login to your Twilio Console and capture the following account settings for use in serverless function environment variables:
Variable Name | Description |
---|---|
WORKSPACE_SID | Your Flex Task Assignment workspace SID. Find this in the TaskRouter Workspaces page. |
SYNC_SERVICE_SID | Your Sync "Default Service" SID. Find this in the Sync Services page. |
- Clone this repo to your local development environment
- Change to the
serverless/multi-call-handling
directory and install the dependenciescd serverless/multi-call-handling npm install
- Copy the
.env.sample
file to.env
cp .env.sample .env
- Edit
.env
and replace the environment variables with the values you captured in the "Twilio Account Settings" section aboveWORKSPACE_SID=WSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX SYNC_SERVICE_SID=ISXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- Verify your Twilio CLI is using the Twilio Flex account you want to test with. Run the following command and note which Twilio account has
Active
astrue
twilio profiles:list
- If you need to change the active Twilio account, run the following command with the desired profile name
twilio profiles:use PROFILE
- If you do not yet have a Twilio CLI profile for the desired Twilio Flex account, run the following command to connect to that account and give it your desired profile name
twilio profiles:create --profile=PROFILE
- With your desired Twilio Flex account profile active in the Twilio CLI, change to the
serverless/multi-call-handling
directory and deploy the Twilio Serverless Functions and Assetscd serverless/multi-call-handling twilio serverless:deploy
- When the deployment completes, copy the following Deployment Details that will be used in subsequent setup and configuration steps
Domain
Functions
URL that ends in/call-status-handler
This section outlines the required configuration in your Twilio Flex account for this sample solution to operate as intended. Login to your Twilio Console and follow the steps in each section below.
- Navigate to TaskRouter -> Workspaces -> Flex Task Assignment -> Task Queues
- Create a new TaskQueue called "Inbound ACD" that will handle all inbound queue calls that are not intended for a specific worker
- For the "Queue Expression" use the following:
isAcdReady == true
- This expression will allow precise control of when a worker should receive ACD calls by modifying the
isAcdReady
worker attribute
- Leave all other configuration fields at their default values
- For the "Queue Expression" use the following:
- Create a new TaskQueue called "Park Pickup" that will be used for routing calls back to the target worker when they pick them up from being parked
- For the "Queue Expression" use the following:
1 == 1
- This ensures any worker is eligible to receive calls in this queue. We will use Workflow worker expressions to only target the worker picking up the call.
- For the "Queue Expression" use the following:
- Navigate to TaskRouter -> Workspaces -> Flex Task Assignment -> Workflows
- Create a new Workflow called "Inbound ACD"
- Set the Task Reservation Timeout to 15 seconds (a common timeout for ACD calls to minimize re-routing delay if a targeted worker doesn't accept the reservation)
- Add a Filter named "Park Pickup OR Hangup"
- Set the "Matching Tasks" expression to:
isParkPickup == true OR isParkHangup == true
- Under the "Routing Step", set the following:
- Task Queue to
Park Pickup
- Known Worker to
Worker SID
- Task Field with Worker SID to
task.targetWorker
- Leave all other fields at their default values
- Task Queue to
- Set the "Matching Tasks" expression to:
- Add a Filter named "Any Task"
- Set the "Matching Tasks" expression to:
1 == 1
(Will display asAny Task
)
- Under the "Routing Step", set the following:
- Task Queue to
Inbound ACD
- Leave all other fields at their default values
- Task Queue to
- Set the "Matching Tasks" expression to:
- Create a new Workflow called "Direct Extension"
- Set the Task Reservation Timeout to 60 seconds (it's common to allow workers more time to pickup direct calls since the call will not route to any other workers)
- Add a Filter named "Park Pickup OR Hangup"
- Set the "Matching Tasks" expression to:
isParkPickup == true OR isParkHangup == true
- Under the "Routing Step", set the following:
- Task Queue to
Park Pickup
- Known Worker to
Worker SID
- Task Field with Worker SID to
task.targetWorker
- Leave all other fields at their default values
- Task Queue to
- Set the "Matching Tasks" expression to:
- Add a Filter named "Any Task"
- Set the "Matching Tasks" expression to:
1 == 1
(Will display asAny Task
)
- Under the "Routing Step", set the following:
- Task Queue to
Everyone
- Expression to
task.directExtension == worker.directExtension
- Leave all other fields at their default values
- Task Queue to
- Set the "Matching Tasks" expression to:
To support the primary use case of workers receiving direct calls to them while they're on another call, we will need to create a directExtension
attribute on each worker we want to test. The "Direct Extension IVR" sample Studio Flow is designed to support 4 digit extensions.
With that in mind let's configure at least one test worker.
- Navigate to TaskRouter -> Workspaces -> Flex Task Assignment -> Workers
- Click on the first worker you want to test with
- In the attributes JSON object, add a
"directExtension"
property with a 4 digit string, for example:{ ..., "directExtension": "1234", ... }
- Save the changes to that worker and navigate back to the Workers list
- Repeat the previous two steps for all other workers you want to test with, being careful not to assign the same extension to more than one worker
- Navigate to Studio -> Flows
- Create a new Flow called "Inbound ACD IVR"
- Select template "Import from JSON"
- Copy the inbound-acd-ivr sample Studio JSON from the studio folder of this repo
- Paste the JSON into the New Flow dialog, overwriting anything already there, and click Next
- Select the "SendToFlex" widget and perform the following:
- Click the Workflow dropdown and select
Inbound ACD
- Click the Channel dropdown and select
Voice
- Click the Save widget button
- Click the Workflow dropdown and select
- Click the Publish button at the top of the Flow and Publish again on the "Publish Flow?" dialog that pops up
- Create a new Flow called "Direct Extension IVR"
- Select template "Import from JSON"
- Copy the direct-extension-ivr sample Studio JSON from the studio folder of this repo
- Paste the JSON into the New Flow dialog, overwriting anything already there, and click Next
- Select the "SendToFlex" widget and perform the following:
- Click the Workflow dropdown and select
Direct Extension
- Click the Channel dropdown and select
Voice
- Click the Save widget button
- Click the Workflow dropdown and select
- Click the Publish button at the top of the Flow and Publish again on the "Publish Flow?" dialog that pops up
- Navigate to Phone Numbers -> Manage -> Active Numbers
- If you already have two phone numbers to test with, proceed to the next step
- If not, purchase additional numbers as necessary until you have two numbers for testing this solution
- Click on the phone number you would like to use for inbound ACD calls and update the following:
- Under the "Voice and Fax" section, click the dropdown under "A Call Comes In" and select "Studio Flow"
- Click the Studio Flow dropdown and select "Inbound ACD IVR"
- In the "Call Status Changes" field, enter the Serverless Functions URL ending in
/call-status-handler
you copied during the Serverless Functions Deploy section above - Save these changes and go back to the list of Active Numbers
- Click on the phone number you would like to use for Direct Extension calls and update the following:
- Under the "Voice and Fax" section, click the dropdown under "A Call Comes In" and select "Studio Flow"
- Click the Studio Flow dropdown and select "Direct Extension IVR"
- In the "Call Status Changes" field, enter the Serverless Functions URL ending in
/call-status-handler
you copied during the Serverless Functions Deploy section above - Save these changes. Your numbers are now ready for testing.
This section will go through the steps to prepare the Flex plugins in this sample solution for use in your development environment and deployment to your Flex account.
Timing of events in this solution is critical for proper operation. For example, when a worker is completing a task, their voice task channel capacity and isAcdReady
worker attribute must be updated correctly before the task is allowed to be completed.
Since it's possible for two plugins attempting to update worker attributes at the same time to conflict with each other, resulting in one plugin's updates being overridden by another, this sample solution introduces the concept of a shared services plugin.
Routing worker attribute updates through this plugin instead of each plugin updating the worker attributes separately ensures simultaneous attribute updates are handled sequentially and will not conflict with each other.
This is the core plugin of the sample solution. All frontend facing logic and interface components for managing multiple active calls is contained here.
- Navigate to the
plugin-shared-services
directory, install dependencies, copy the.env.sample
andpublic/appConfig.example.js
filescd plugin-shared-services npm install cp .env.sample .env cp public/appConfig.example.js public/appConfig.js
- Edit the
.env
file and populateREACT_APP_SERVERLESS_DOMAIN
with the Domain value you copied in the Serverless Functions Deploy section aboveREACT_APP_SERVERLESS_DOMAIN=multi-call-handling-1234-dev.twil.io
- Navigate to the
plugin-multi-call-handling
directory, install dependencies, copy theenv.sample
andpublic/appConfig.example.js
filescd ../plugin-multi-call-handling npm install cp .env.sample .env cp public/appConfig.example.js public/appConfig.js
- Edit the
.env
file and populateREACT_APP_SERVERLESS_DOMAIN
with the Domain value you copied in the Serverless Functions Deploy section aboveREACT_APP_SERVERLESS_DOMAIN=multi-call-handling-1234-dev.twil.io
Since this sample solution requires both Flex plugins to be running, and more specifically for plugin-shared-services
to be loaded before plugin-multi-call-handling
, the following steps are required the first time you are setting up this solution before you can start testing it locally.
- Navigate to
plugin-shared-services
and start the plugincd plugin-shared-services twilio flex:plugins:start
- After the plugin finishes compiling and launches your browser to localhost:3000, close your browser tab and stop the plugin web server by pressing
Ctrl + c
- Navigate to
plugin-multi-call-handling
and start the plugincd ../plugin-multi-call-handling twilio flex:plugins:start
- After the plugin finishes compiling and launches your browser to localhost:3000, close your browser tab and stop the plugin web server by pressing
Ctrl + c
Now that both plugins have been run once, the Twilio CLI is aware of them and they can be run together. Anytime you want to test this sample solution locally, you just need to run the following from any directory to start them together and ensure they are loaded by the Flex UI in the right order:
twilio flex:plugins:start --name plugin-shared-services --name plugin-multi-call-handling
Once you login to Flex running on your local dev environment at localhost:3000
, the Flex UI will load plugins in the order they are listed after the twilio flex:plugins:start
command. At that point you are ready to test and further develop the Flex plugins.
Once you are happy with your plugin, you have to deploy then release the plugin for it to take affect on Twilio hosted Flex.
- Verify your Twilio CLI is using the Twilio Flex account you want to test with. Run the following command and note which Twilio account has
Active
astrue
twilio profiles:list
- If you need to change the active Twilio account, run the following command with the desired profile name
twilio profiles:use PROFILE
- Navigate to the
plugins-shared-services
directory and deploy the plugincd plugin-shared-services twilio flex:plugins:deploy --major --changelog "Notes for this version" --description "Functionality of the plugin"
- Note the plugin version returned after it's deployed, for example
[email protected]
- Navigate to the
plugin-multi-call-handling
directory and deploy the plugincd ../plugin-multi-call-handling twilio flex:plugins:deploy --major --changelog "Notes for this version" --description "Functionality of the plugin"
- Note the plugin version returned after it's deployed, for example
[email protected]
- Create a release to enable these two newly deployed plugins, ensuring
plugin-shared-services
is defined first in the release listtwilio flex:plugins:release --plugin [email protected] --plugin [email protected] --name "Name for this release" --description "Description of this release"
After you've deployed and released your plugins, you can confirm they are enabled in the Flex Admin Plugins dashboard at https://flex.twilio.com/admin/plugins.
For more details on deploying your plugin, refer to the Flex plugin deploy and release guide.
Note: Common packages like React
, ReactDOM
, Redux
and ReactRedux
are not bundled with the build because they are treated as external dependencies so the plugin will depend on Flex to provide them globally.
Once the Twilio Serverless Functions and Assets are deployed, the account configurations are complete, and both plugins are either running in your local development environment or in your hosted Flex instance, you are ready to test the solution.
- Login to the Flex UI (either your local dev or hosted Flex instance, depending on where the plugins are running)
- Change your activity to Available
- Place a call to the Twilio number routing to your "Inbound ACD IVR" Studio Flow
- Accept the incoming voice task and wait for the call to connect with your browser WebRTC client
- While that call is active, place a second call, this time to the Twilio number routing to your "Direct Extension IVR" Studio Flow
- When the IVR asks for the 4 digit extension of the person you would like to call, enter the
directExtension
value for the worker you're testing with - Accept the incoming voice task, at which point the following should happen:
- The first caller starts hearing hold music
- That call shows up in the "Parked Calls" list in the Flex Task List
- The task associated with the first call is automatically completed
- The second voice task is accepted and connects with your browser WebRTC client
- To switch between calls, double click on the call in the "Parked Calls" list. At that point the following should happen:
- The item in the "Parked Calls" list disappears and a new voice task shows up in the Flex Task List
- The second caller starts hearing hold music and that call shows up in the "Parked Calls" list in the Flex Task List
- The new voice task associated with the first caller is automatically accepted and the call connected with your browser WebRTC client
- The task associated with the second call is automatically completed
- You can continue to switch between the two calls as necessary by simply double clicking on the parked call you want to pick up
- Login to the Flex UI (either your local dev or hosted Flex instance, depending on where the plugins are running)
- Either place a call to the Twilio number routing to your "Inbound ACD IVR" Studio Flow and accept the voice task, or place an outbound call from the Flex dialpad
- Once the call is active with your browser WebRTC client, click the Park Button on the task item in the Flex Task List. That should cause the following to happen:
- Hold music will begin playing to the caller / called party
- The call shows up in the "Parked Calls" list in the Flex Task List
- The task associated with the call is automatically completed
- Place an outbound call from the Flex dialpad. Normally the dialpad is disabled when you have an active voice task, so parking the active call allows you to use the dialpad while that call is parked.
- Once that call connects, switching between the active calls is the same as in the previous test, simply double click on the parked call to pick it up and automatically park your current active call
When a call is parked, the Call SID is updated with the following TwiML instruction:
<Response>
<Play loop="99">https://{your-serverless-domain}/hold_music_1.mp3</Play>
</Response>
If you'd like to change the hold music the caller hears, you can simply replace serverless/multi-call-handling/assets/hold_music_1.mp3
with the desired MP3 audio file and re-deploy the Twilio Serverless Functions and Assets.
If you want to modify the TwiML instruction, such as to randomize the hold music or loop through multiple audio files, that logic is controlled in the serverless/multi-call-handling/functions/park-call.js
file.