In this tutorial we will show how to automate the routing of calls from customers to your support agents. In this example customers would select a product, then be connected to a specialist for that product. If no one is available our customer's number will be saved so that our agent can call them back.
In order to instruct TaskRouter to handle the Tasks, we need to configure a Workspace. We can do this in the TaskRouter Console or programmatically using the TaskRouter REST API.
In this Ruby on Rails application, this step will be executed in the initialization phase every time you run the app.
A Workspace is the container element for any TaskRouter application. The elements are:
We'll use a TaskRouterClient
provided in the twilio-ruby gem to create and configure the workspace.
lib/workspace_config.rb
_168class WorkspaceConfig_168 WORKSPACE_NAME = 'Rails Workspace'.freeze_168 WORKFLOW_NAME = 'Sales'.freeze_168 WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze_168 QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze_168 ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze_168 EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze_168 BOB_NUMBER = ENV['BOB_NUMBER'].freeze_168 ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze_168_168 def self.setup_168 puts 'Configuring workspace, please wait ...'_168 new.setup_168 puts 'Workspace ready!'_168 end_168_168 def initialize_168 @account_sid = ENV['TWILIO_ACCOUNT_SID']_168 @auth_token = ENV['TWILIO_AUTH_TOKEN']_168 @client = taskrouter_client_168 end_168_168 def setup_168 @workspace_sid = create_workspace_168 @client = taskrouter_client_168 WorkspaceInfo.instance.workers = create_workers_168 workflow_sid = create_workflow.sid_168 WorkspaceInfo.instance.workflow_sid = workflow_sid_168 idle_activity_sid = activity_by_name('Idle').sid_168 WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid_168 WorkspaceInfo.instance.workspace_sid = @workspace_sid_168 end_168_168 private_168_168 attr_reader :client, :account_sid, :auth_token_168_168 def taskrouter_client_168 client_instance = Twilio::REST::Client.new(_168 account_sid,_168 auth_token_168 )_168_168 client_instance.taskrouter.v1_168 end_168_168 def create_workspace_168 workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first_168 workspace.delete unless workspace.nil?_168_168 workspace = client.workspaces.create(_168 friendly_name: WORKSPACE_NAME,_168 event_callback_url: EVENT_CALLBACK_URL_168 )_168_168 workspace.sid_168 end_168_168 def create_workers_168 bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"_168 alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"_168_168 bob = create_worker('Bob', bob_attributes)_168 alice = create_worker('Alice', alice_attributes)_168_168 {_168 BOB_NUMBER => { sid: bob.sid, name: 'Bob' },_168 ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }_168 }_168 end_168_168 def create_worker(name, attributes)_168 client.workspaces(@workspace_sid).workers.create(_168 friendly_name: name,_168 attributes: attributes,_168 activity_sid: activity_by_name('Idle').sid_168 )_168 end_168_168 def activity_by_name(name)_168 client.workspaces(@workspace_sid).activities.list(friendly_name: name).first_168 end_168_168 def create_task_queues_168 reservation_activity_sid = activity_by_name('Reserved').sid_168 assignment_activity_sid = activity_by_name('Busy').sid_168_168 voice_queue = create_task_queue('Voice', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableVoice'")_168_168 sms_queue = create_task_queue('SMS', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableSMS'")_168_168 all_queue = create_task_queue('All', reservation_activity_sid,_168 assignment_activity_sid, '1==1')_168_168 { voice: voice_queue, sms: sms_queue, all: all_queue }_168 end_168_168 def create_task_queue(name, reservation_sid, assignment_sid, target_workers)_168 client.workspaces(@workspace_sid).task_queues.create(_168 friendly_name: name,_168 reservation_activity_sid: reservation_sid,_168 assignment_activity_sid: assignment_sid,_168 target_workers: target_workers_168 )_168 end_168_168 def create_workflow_168 queues = create_task_queues_168 config = workflow_config(queues)_168_168 client.workspaces(@workspace_sid).workflows.create(_168 configuration: config.to_json,_168 friendly_name: WORKFLOW_NAME,_168 assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 task_reservation_timeout: WORKFLOW_TIMEOUT_168 )_168 end_168_168 def workspace_sid_168 @workspace_sid || 'no_workspace_yet'_168 end_168_168 def workflow_config(queues)_168 default_target = default_rule_target(queues[:all].sid)_168_168 {_168 task_routing: {_168 filters: [_168 {_168 expression: 'selected_product=="ProgrammableVoice"',_168 targets: [_168 rule_target(queues[:voice].sid),_168 default_target_168 ]_168 },_168 {_168 expression: 'selected_product=="ProgrammableSMS"',_168 targets: [_168 rule_target(queues[:sms].sid),_168 default_target_168 ]_168 }_168 ],_168 default_filter: default_target_168 }_168 }_168 end_168_168 def rule_target(sid)_168 { queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }_168 end_168_168 def default_rule_target(sid)_168 {_168 queue: sid,_168 priority: 1,_168 timeout: QUEUE_TIMEOUT,_168 expression: '1==1'_168 }_168 end_168end
Now let's look in more detail at all the steps, starting with the creation of the workspace itself.
Before creating a workspace, we need to delete any others with the same friendly_name
as the one we are trying to create. In order to create a workspace we need to provide a friendly_name
and a callback_url
where a requests will be made every time an event is triggered in our workspace.
lib/workspace_config.rb
_168class WorkspaceConfig_168 WORKSPACE_NAME = 'Rails Workspace'.freeze_168 WORKFLOW_NAME = 'Sales'.freeze_168 WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze_168 QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze_168 ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze_168 EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze_168 BOB_NUMBER = ENV['BOB_NUMBER'].freeze_168 ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze_168_168 def self.setup_168 puts 'Configuring workspace, please wait ...'_168 new.setup_168 puts 'Workspace ready!'_168 end_168_168 def initialize_168 @account_sid = ENV['TWILIO_ACCOUNT_SID']_168 @auth_token = ENV['TWILIO_AUTH_TOKEN']_168 @client = taskrouter_client_168 end_168_168 def setup_168 @workspace_sid = create_workspace_168 @client = taskrouter_client_168 WorkspaceInfo.instance.workers = create_workers_168 workflow_sid = create_workflow.sid_168 WorkspaceInfo.instance.workflow_sid = workflow_sid_168 idle_activity_sid = activity_by_name('Idle').sid_168 WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid_168 WorkspaceInfo.instance.workspace_sid = @workspace_sid_168 end_168_168 private_168_168 attr_reader :client, :account_sid, :auth_token_168_168 def taskrouter_client_168 client_instance = Twilio::REST::Client.new(_168 account_sid,_168 auth_token_168 )_168_168 client_instance.taskrouter.v1_168 end_168_168 def create_workspace_168 workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first_168 workspace.delete unless workspace.nil?_168_168 workspace = client.workspaces.create(_168 friendly_name: WORKSPACE_NAME,_168 event_callback_url: EVENT_CALLBACK_URL_168 )_168_168 workspace.sid_168 end_168_168 def create_workers_168 bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"_168 alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"_168_168 bob = create_worker('Bob', bob_attributes)_168 alice = create_worker('Alice', alice_attributes)_168_168 {_168 BOB_NUMBER => { sid: bob.sid, name: 'Bob' },_168 ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }_168 }_168 end_168_168 def create_worker(name, attributes)_168 client.workspaces(@workspace_sid).workers.create(_168 friendly_name: name,_168 attributes: attributes,_168 activity_sid: activity_by_name('Idle').sid_168 )_168 end_168_168 def activity_by_name(name)_168 client.workspaces(@workspace_sid).activities.list(friendly_name: name).first_168 end_168_168 def create_task_queues_168 reservation_activity_sid = activity_by_name('Reserved').sid_168 assignment_activity_sid = activity_by_name('Busy').sid_168_168 voice_queue = create_task_queue('Voice', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableVoice'")_168_168 sms_queue = create_task_queue('SMS', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableSMS'")_168_168 all_queue = create_task_queue('All', reservation_activity_sid,_168 assignment_activity_sid, '1==1')_168_168 { voice: voice_queue, sms: sms_queue, all: all_queue }_168 end_168_168 def create_task_queue(name, reservation_sid, assignment_sid, target_workers)_168 client.workspaces(@workspace_sid).task_queues.create(_168 friendly_name: name,_168 reservation_activity_sid: reservation_sid,_168 assignment_activity_sid: assignment_sid,_168 target_workers: target_workers_168 )_168 end_168_168 def create_workflow_168 queues = create_task_queues_168 config = workflow_config(queues)_168_168 client.workspaces(@workspace_sid).workflows.create(_168 configuration: config.to_json,_168 friendly_name: WORKFLOW_NAME,_168 assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 task_reservation_timeout: WORKFLOW_TIMEOUT_168 )_168 end_168_168 def workspace_sid_168 @workspace_sid || 'no_workspace_yet'_168 end_168_168 def workflow_config(queues)_168 default_target = default_rule_target(queues[:all].sid)_168_168 {_168 task_routing: {_168 filters: [_168 {_168 expression: 'selected_product=="ProgrammableVoice"',_168 targets: [_168 rule_target(queues[:voice].sid),_168 default_target_168 ]_168 },_168 {_168 expression: 'selected_product=="ProgrammableSMS"',_168 targets: [_168 rule_target(queues[:sms].sid),_168 default_target_168 ]_168 }_168 ],_168 default_filter: default_target_168 }_168 }_168 end_168_168 def rule_target(sid)_168 { queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }_168 end_168_168 def default_rule_target(sid)_168 {_168 queue: sid,_168 priority: 1,_168 timeout: QUEUE_TIMEOUT,_168 expression: '1==1'_168 }_168 end_168end
We have a brand new workspace, now we need workers. Let's create them on the next step.
We'll create two workers, Bob and Alice. They each have two attributes: contact_uri
a phone number and products
, a list of products each worker is specialized in. We also need to specify an activity_sid
and a name for each worker. The selected activity will define the status of the worker.
A set of default activities is created with your workspace. We use the Idle
activity to make a worker available for incoming calls.
lib/workspace_config.rb
_168class WorkspaceConfig_168 WORKSPACE_NAME = 'Rails Workspace'.freeze_168 WORKFLOW_NAME = 'Sales'.freeze_168 WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze_168 QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze_168 ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze_168 EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze_168 BOB_NUMBER = ENV['BOB_NUMBER'].freeze_168 ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze_168_168 def self.setup_168 puts 'Configuring workspace, please wait ...'_168 new.setup_168 puts 'Workspace ready!'_168 end_168_168 def initialize_168 @account_sid = ENV['TWILIO_ACCOUNT_SID']_168 @auth_token = ENV['TWILIO_AUTH_TOKEN']_168 @client = taskrouter_client_168 end_168_168 def setup_168 @workspace_sid = create_workspace_168 @client = taskrouter_client_168 WorkspaceInfo.instance.workers = create_workers_168 workflow_sid = create_workflow.sid_168 WorkspaceInfo.instance.workflow_sid = workflow_sid_168 idle_activity_sid = activity_by_name('Idle').sid_168 WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid_168 WorkspaceInfo.instance.workspace_sid = @workspace_sid_168 end_168_168 private_168_168 attr_reader :client, :account_sid, :auth_token_168_168 def taskrouter_client_168 client_instance = Twilio::REST::Client.new(_168 account_sid,_168 auth_token_168 )_168_168 client_instance.taskrouter.v1_168 end_168_168 def create_workspace_168 workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first_168 workspace.delete unless workspace.nil?_168_168 workspace = client.workspaces.create(_168 friendly_name: WORKSPACE_NAME,_168 event_callback_url: EVENT_CALLBACK_URL_168 )_168_168 workspace.sid_168 end_168_168 def create_workers_168 bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"_168 alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"_168_168 bob = create_worker('Bob', bob_attributes)_168 alice = create_worker('Alice', alice_attributes)_168_168 {_168 BOB_NUMBER => { sid: bob.sid, name: 'Bob' },_168 ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }_168 }_168 end_168_168 def create_worker(name, attributes)_168 client.workspaces(@workspace_sid).workers.create(_168 friendly_name: name,_168 attributes: attributes,_168 activity_sid: activity_by_name('Idle').sid_168 )_168 end_168_168 def activity_by_name(name)_168 client.workspaces(@workspace_sid).activities.list(friendly_name: name).first_168 end_168_168 def create_task_queues_168 reservation_activity_sid = activity_by_name('Reserved').sid_168 assignment_activity_sid = activity_by_name('Busy').sid_168_168 voice_queue = create_task_queue('Voice', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableVoice'")_168_168 sms_queue = create_task_queue('SMS', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableSMS'")_168_168 all_queue = create_task_queue('All', reservation_activity_sid,_168 assignment_activity_sid, '1==1')_168_168 { voice: voice_queue, sms: sms_queue, all: all_queue }_168 end_168_168 def create_task_queue(name, reservation_sid, assignment_sid, target_workers)_168 client.workspaces(@workspace_sid).task_queues.create(_168 friendly_name: name,_168 reservation_activity_sid: reservation_sid,_168 assignment_activity_sid: assignment_sid,_168 target_workers: target_workers_168 )_168 end_168_168 def create_workflow_168 queues = create_task_queues_168 config = workflow_config(queues)_168_168 client.workspaces(@workspace_sid).workflows.create(_168 configuration: config.to_json,_168 friendly_name: WORKFLOW_NAME,_168 assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 task_reservation_timeout: WORKFLOW_TIMEOUT_168 )_168 end_168_168 def workspace_sid_168 @workspace_sid || 'no_workspace_yet'_168 end_168_168 def workflow_config(queues)_168 default_target = default_rule_target(queues[:all].sid)_168_168 {_168 task_routing: {_168 filters: [_168 {_168 expression: 'selected_product=="ProgrammableVoice"',_168 targets: [_168 rule_target(queues[:voice].sid),_168 default_target_168 ]_168 },_168 {_168 expression: 'selected_product=="ProgrammableSMS"',_168 targets: [_168 rule_target(queues[:sms].sid),_168 default_target_168 ]_168 }_168 ],_168 default_filter: default_target_168 }_168 }_168 end_168_168 def rule_target(sid)_168 { queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }_168 end_168_168 def default_rule_target(sid)_168 {_168 queue: sid,_168 priority: 1,_168 timeout: QUEUE_TIMEOUT,_168 expression: '1==1'_168 }_168 end_168end
After creating our workers, let's set up the Task Queues.
Next, we set up the Task Queues. Each with a friendly_name
and a targetWorkers
, which is an expression to match Workers. Our Task Queues are:
SMS
- Will target Workers specialized in Programmable SMS, such as Bob, using the expression
"products HAS \"ProgrammableSMS\""
.
Voice
- Will do the same for Programmable Voice Workers, such as Alice, using the expression
"products HAS \"ProgrammableVoice\""
.
All
- This queue targets all users and can be used when there are no specialist around for the chosen product. We can use the
"1==1"
expression here.
lib/workspace_config.rb
_168class WorkspaceConfig_168 WORKSPACE_NAME = 'Rails Workspace'.freeze_168 WORKFLOW_NAME = 'Sales'.freeze_168 WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze_168 QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze_168 ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze_168 EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze_168 BOB_NUMBER = ENV['BOB_NUMBER'].freeze_168 ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze_168_168 def self.setup_168 puts 'Configuring workspace, please wait ...'_168 new.setup_168 puts 'Workspace ready!'_168 end_168_168 def initialize_168 @account_sid = ENV['TWILIO_ACCOUNT_SID']_168 @auth_token = ENV['TWILIO_AUTH_TOKEN']_168 @client = taskrouter_client_168 end_168_168 def setup_168 @workspace_sid = create_workspace_168 @client = taskrouter_client_168 WorkspaceInfo.instance.workers = create_workers_168 workflow_sid = create_workflow.sid_168 WorkspaceInfo.instance.workflow_sid = workflow_sid_168 idle_activity_sid = activity_by_name('Idle').sid_168 WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid_168 WorkspaceInfo.instance.workspace_sid = @workspace_sid_168 end_168_168 private_168_168 attr_reader :client, :account_sid, :auth_token_168_168 def taskrouter_client_168 client_instance = Twilio::REST::Client.new(_168 account_sid,_168 auth_token_168 )_168_168 client_instance.taskrouter.v1_168 end_168_168 def create_workspace_168 workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first_168 workspace.delete unless workspace.nil?_168_168 workspace = client.workspaces.create(_168 friendly_name: WORKSPACE_NAME,_168 event_callback_url: EVENT_CALLBACK_URL_168 )_168_168 workspace.sid_168 end_168_168 def create_workers_168 bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"_168 alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"_168_168 bob = create_worker('Bob', bob_attributes)_168 alice = create_worker('Alice', alice_attributes)_168_168 {_168 BOB_NUMBER => { sid: bob.sid, name: 'Bob' },_168 ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }_168 }_168 end_168_168 def create_worker(name, attributes)_168 client.workspaces(@workspace_sid).workers.create(_168 friendly_name: name,_168 attributes: attributes,_168 activity_sid: activity_by_name('Idle').sid_168 )_168 end_168_168 def activity_by_name(name)_168 client.workspaces(@workspace_sid).activities.list(friendly_name: name).first_168 end_168_168 def create_task_queues_168 reservation_activity_sid = activity_by_name('Reserved').sid_168 assignment_activity_sid = activity_by_name('Busy').sid_168_168 voice_queue = create_task_queue('Voice', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableVoice'")_168_168 sms_queue = create_task_queue('SMS', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableSMS'")_168_168 all_queue = create_task_queue('All', reservation_activity_sid,_168 assignment_activity_sid, '1==1')_168_168 { voice: voice_queue, sms: sms_queue, all: all_queue }_168 end_168_168 def create_task_queue(name, reservation_sid, assignment_sid, target_workers)_168 client.workspaces(@workspace_sid).task_queues.create(_168 friendly_name: name,_168 reservation_activity_sid: reservation_sid,_168 assignment_activity_sid: assignment_sid,_168 target_workers: target_workers_168 )_168 end_168_168 def create_workflow_168 queues = create_task_queues_168 config = workflow_config(queues)_168_168 client.workspaces(@workspace_sid).workflows.create(_168 configuration: config.to_json,_168 friendly_name: WORKFLOW_NAME,_168 assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 task_reservation_timeout: WORKFLOW_TIMEOUT_168 )_168 end_168_168 def workspace_sid_168 @workspace_sid || 'no_workspace_yet'_168 end_168_168 def workflow_config(queues)_168 default_target = default_rule_target(queues[:all].sid)_168_168 {_168 task_routing: {_168 filters: [_168 {_168 expression: 'selected_product=="ProgrammableVoice"',_168 targets: [_168 rule_target(queues[:voice].sid),_168 default_target_168 ]_168 },_168 {_168 expression: 'selected_product=="ProgrammableSMS"',_168 targets: [_168 rule_target(queues[:sms].sid),_168 default_target_168 ]_168 }_168 ],_168 default_filter: default_target_168 }_168 }_168 end_168_168 def rule_target(sid)_168 { queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }_168 end_168_168 def default_rule_target(sid)_168 {_168 queue: sid,_168 priority: 1,_168 timeout: QUEUE_TIMEOUT,_168 expression: '1==1'_168 }_168 end_168end
We have a Workspace, Workers and Task Queues... what's left? A Workflow. Let's see how to create one next!
Finally, we create the Workflow using the following parameters:
friendly_name
as the name of a Workflow.
assignment_callback_url
and
fallback_assignment_callback_url
as the public URL where a request will be made when this Workflow assigns a Task to a Worker. We will learn how to implement it on the next steps.
task_reservation_timeout
as the maximum time we want to wait until a Worker is available for handling a Task.
configuration
which is a set of rules for placing Tasks into Task Queues. The routing configuration will take a Task's attribute and match this with Task Queues. This application's Workflow rules are defined as:
"selected_product==\ "ProgrammableSMS\""
expression for
SMS
Task Queue. This expression will match any Task with
ProgrammableSMS
as the
selected_product
attribute.
"selected_product==\ "ProgrammableVoice\""
expression for
Voice
Task Queue.
lib/workspace_config.rb
_168class WorkspaceConfig_168 WORKSPACE_NAME = 'Rails Workspace'.freeze_168 WORKFLOW_NAME = 'Sales'.freeze_168 WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze_168 QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze_168 ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze_168 EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze_168 BOB_NUMBER = ENV['BOB_NUMBER'].freeze_168 ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze_168_168 def self.setup_168 puts 'Configuring workspace, please wait ...'_168 new.setup_168 puts 'Workspace ready!'_168 end_168_168 def initialize_168 @account_sid = ENV['TWILIO_ACCOUNT_SID']_168 @auth_token = ENV['TWILIO_AUTH_TOKEN']_168 @client = taskrouter_client_168 end_168_168 def setup_168 @workspace_sid = create_workspace_168 @client = taskrouter_client_168 WorkspaceInfo.instance.workers = create_workers_168 workflow_sid = create_workflow.sid_168 WorkspaceInfo.instance.workflow_sid = workflow_sid_168 idle_activity_sid = activity_by_name('Idle').sid_168 WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid_168 WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid_168 WorkspaceInfo.instance.workspace_sid = @workspace_sid_168 end_168_168 private_168_168 attr_reader :client, :account_sid, :auth_token_168_168 def taskrouter_client_168 client_instance = Twilio::REST::Client.new(_168 account_sid,_168 auth_token_168 )_168_168 client_instance.taskrouter.v1_168 end_168_168 def create_workspace_168 workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first_168 workspace.delete unless workspace.nil?_168_168 workspace = client.workspaces.create(_168 friendly_name: WORKSPACE_NAME,_168 event_callback_url: EVENT_CALLBACK_URL_168 )_168_168 workspace.sid_168 end_168_168 def create_workers_168 bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"_168 alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"_168_168 bob = create_worker('Bob', bob_attributes)_168 alice = create_worker('Alice', alice_attributes)_168_168 {_168 BOB_NUMBER => { sid: bob.sid, name: 'Bob' },_168 ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }_168 }_168 end_168_168 def create_worker(name, attributes)_168 client.workspaces(@workspace_sid).workers.create(_168 friendly_name: name,_168 attributes: attributes,_168 activity_sid: activity_by_name('Idle').sid_168 )_168 end_168_168 def activity_by_name(name)_168 client.workspaces(@workspace_sid).activities.list(friendly_name: name).first_168 end_168_168 def create_task_queues_168 reservation_activity_sid = activity_by_name('Reserved').sid_168 assignment_activity_sid = activity_by_name('Busy').sid_168_168 voice_queue = create_task_queue('Voice', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableVoice'")_168_168 sms_queue = create_task_queue('SMS', reservation_activity_sid,_168 assignment_activity_sid,_168 "products HAS 'ProgrammableSMS'")_168_168 all_queue = create_task_queue('All', reservation_activity_sid,_168 assignment_activity_sid, '1==1')_168_168 { voice: voice_queue, sms: sms_queue, all: all_queue }_168 end_168_168 def create_task_queue(name, reservation_sid, assignment_sid, target_workers)_168 client.workspaces(@workspace_sid).task_queues.create(_168 friendly_name: name,_168 reservation_activity_sid: reservation_sid,_168 assignment_activity_sid: assignment_sid,_168 target_workers: target_workers_168 )_168 end_168_168 def create_workflow_168 queues = create_task_queues_168 config = workflow_config(queues)_168_168 client.workspaces(@workspace_sid).workflows.create(_168 configuration: config.to_json,_168 friendly_name: WORKFLOW_NAME,_168 assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,_168 task_reservation_timeout: WORKFLOW_TIMEOUT_168 )_168 end_168_168 def workspace_sid_168 @workspace_sid || 'no_workspace_yet'_168 end_168_168 def workflow_config(queues)_168 default_target = default_rule_target(queues[:all].sid)_168_168 {_168 task_routing: {_168 filters: [_168 {_168 expression: 'selected_product=="ProgrammableVoice"',_168 targets: [_168 rule_target(queues[:voice].sid),_168 default_target_168 ]_168 },_168 {_168 expression: 'selected_product=="ProgrammableSMS"',_168 targets: [_168 rule_target(queues[:sms].sid),_168 default_target_168 ]_168 }_168 ],_168 default_filter: default_target_168 }_168 }_168 end_168_168 def rule_target(sid)_168 { queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }_168 end_168_168 def default_rule_target(sid)_168 {_168 queue: sid,_168 priority: 1,_168 timeout: QUEUE_TIMEOUT,_168 expression: '1==1'_168 }_168 end_168end
Our workspace is completely setup. Now it's time to see how we use it to route calls.
Right after receiving a call, Twilio will send a request to the URL specified on the number's configuration.
The endpoint will then process the request and generate a TwiML response. We'll use the Say verb to give the user product alternatives, and a key they can press in order to select one. The Gather verb allows us to capture the user's key press.
lib/twiml_generator.rb
_29module TwimlGenerator_29 def self.generate_gather_product(callback_url)_29 response = Twilio::TwiML::VoiceResponse.new_29 gather = Twilio::TwiML::Gather.new(num_digits: 1,_29 action: callback_url,_29 method: 'POST')_29 gather.say 'Welcome to the Twilio support line!'_29 gather.say 'To get specialized help with programmable voice press 1, '\_29 'or press 2 for programmable SMS'_29_29 response.append(gather)_29 response.to_s_29 end_29_29 def self.generate_task_enqueue(selected_product)_29 enqueue = Twilio::TwiML::Enqueue.new(nil, workflow_sid: WorkspaceInfo.instance.workflow_sid)_29 enqueue.task "{\"selected_product\": \"#{selected_product}\"}"_29_29 response = Twilio::TwiML::VoiceResponse.new_29 response.append(enqueue)_29 response.to_s_29 end_29_29 def self.generate_confirm_message(status)_29 response = Twilio::TwiML::MessagingResponse.new_29 response.message(body: "Your status has changed to #{status}")_29 response.to_s_29 end_29end
We just asked the caller to choose a product, next we will use their choice to create the appropiate Task.
This is the endpoint set as the action
URL on the Gather
verb on the previous step. A request is made to this endpoint when the user presses a key during the call. This request has a Digits
parameter that holds the pressed keys. A Task
will be created based on the pressed digit with the selected_product
as an attribute. The Workflow will take this Task's attributes and match them with the configured expressions in order to find a Task Queue for this Task, so an appropriate available Worker can be assigned to handle it.
We use the Enqueue
verb with a WorkflowSid
attribute to integrate with TaskRouter. Then the voice call will be put on hold while TaskRouter tries to find an available Worker to handle this Task.
lib/twiml_generator.rb
_29module TwimlGenerator_29 def self.generate_gather_product(callback_url)_29 response = Twilio::TwiML::VoiceResponse.new_29 gather = Twilio::TwiML::Gather.new(num_digits: 1,_29 action: callback_url,_29 method: 'POST')_29 gather.say 'Welcome to the Twilio support line!'_29 gather.say 'To get specialized help with programmable voice press 1, '\_29 'or press 2 for programmable SMS'_29_29 response.append(gather)_29 response.to_s_29 end_29_29 def self.generate_task_enqueue(selected_product)_29 enqueue = Twilio::TwiML::Enqueue.new(nil, workflow_sid: WorkspaceInfo.instance.workflow_sid)_29 enqueue.task "{\"selected_product\": \"#{selected_product}\"}"_29_29 response = Twilio::TwiML::VoiceResponse.new_29 response.append(enqueue)_29 response.to_s_29 end_29_29 def self.generate_confirm_message(status)_29 response = Twilio::TwiML::MessagingResponse.new_29 response.message(body: "Your status has changed to #{status}")_29 response.to_s_29 end_29end
After sending a Task to Twilio, let's see how we tell TaskRouter which Worker to use to execute that task.
When TaskRouter selects a Worker, it does the following:
POST
request is made to the Workflow's AssignmentCallbackURL, which was configured using the
WorkspaceConfig
class when the application is initialized. This request includes the full details of the Task, the selected Worker, and the Reservation.
Handling this Assignment Callback is a key component of building a TaskRouter application as we can instruct how the Worker will handle a Task. We could send a text, email, push notifications or make a call.
Since we created this Task during a voice call with an Enqueue
verb, let's instruct TaskRouter to dequeue the call and dial a Worker. If we do not specify a to
parameter with a phone number, TaskRouter will pick the Worker's contact_uri
attribute.
We also send a post_work_activity_sid
which will tell TaskRouter which Activity to assign this worker after the call ends.
app/controllers/callback_controller.rb
_60class CallbackController < ApplicationController_60 skip_before_filter :verify_authenticity_token_60_60 def assignment_60 instruction = {_60 instruction: 'dequeue',_60 post_work_activity_sid: WorkspaceInfo.instance.post_work_activity_sid_60 }_60_60 render json: instruction_60 end_60_60 def events_60 event_type = params[:EventType]_60_60 if ['workflow.timeout', 'task.canceled'].include?(event_type)_60 task_attributes = JSON.parse(params[:TaskAttributes])_60_60 MissedCall.create(_60 selected_product: task_attributes['selected_product'],_60 phone_number: task_attributes['from']_60 )_60_60 redirect_to_voicemail(task_attributes['call_sid']) if event_type == 'workflow.timeout'_60 elsif event_type == 'worker.activity.update' &&_60 params[:WorkerActivityName] == 'Offline'_60_60 worker_attributes = JSON.parse(params[:WorkerAttributes])_60 notify_offline_status(worker_attributes['contact_uri'])_60 end_60_60 render nothing: true_60 end_60_60 private_60_60 def redirect_to_voicemail(call_sid)_60 email = ENV['MISSED_CALLS_EMAIL_ADDRESS']_60 message = 'Sorry, All agents are busy. Please leave a message. We\'ll call you as soon as possible'_60 url_message = { Message: message }.to_query_60 redirect_url =_60 "http://twimlets.com/voicemail?Email=#{email}&#{url_message}"_60_60 client.calls(call_sid).update(url: redirect_url)_60 end_60_60 def notify_offline_status(phone_number)_60 message = 'Your status has changed to Offline. Reply with '\_60 '"On" to get back Online'_60 client.messages.create(_60 to: phone_number,_60 from: ENV['TWILIO_NUMBER'],_60 body: message_60 )_60 end_60_60 def client_60 Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])_60 end_60end
Now that our Tasks are routed properly, let's deal with missed calls in the next step.
This endpoint will be called after each TaskRouter Event is triggered. In our application, we are trying to collect missed calls, so we would like to handle the workflow.timeout
event. This event is triggered when the Task waits more than the limit set on Workflow Configuration-- or rather when no worker is available.
Here we use TwilioRestClient to route this call to a Voicemail Twimlet. Twimlets are tiny web applications for voice. This one will generate a TwiML
response using Say
verb and record a message using Record
verb. The recorded message will then be transcribed and sent to the email address configured.
Note that we are also listening for task.canceled
. This is triggered when the customer hangs up before being assigned to an agent, therefore canceling the task. Capturing this event allows us to collect the information from the customers that hang up before the Workflow times out.
app/controllers/callback_controller.rb
_60class CallbackController < ApplicationController_60 skip_before_filter :verify_authenticity_token_60_60 def assignment_60 instruction = {_60 instruction: 'dequeue',_60 post_work_activity_sid: WorkspaceInfo.instance.post_work_activity_sid_60 }_60_60 render json: instruction_60 end_60_60 def events_60 event_type = params[:EventType]_60_60 if ['workflow.timeout', 'task.canceled'].include?(event_type)_60 task_attributes = JSON.parse(params[:TaskAttributes])_60_60 MissedCall.create(_60 selected_product: task_attributes['selected_product'],_60 phone_number: task_attributes['from']_60 )_60_60 redirect_to_voicemail(task_attributes['call_sid']) if event_type == 'workflow.timeout'_60 elsif event_type == 'worker.activity.update' &&_60 params[:WorkerActivityName] == 'Offline'_60_60 worker_attributes = JSON.parse(params[:WorkerAttributes])_60 notify_offline_status(worker_attributes['contact_uri'])_60 end_60_60 render nothing: true_60 end_60_60 private_60_60 def redirect_to_voicemail(call_sid)_60 email = ENV['MISSED_CALLS_EMAIL_ADDRESS']_60 message = 'Sorry, All agents are busy. Please leave a message. We\'ll call you as soon as possible'_60 url_message = { Message: message }.to_query_60 redirect_url =_60 "http://twimlets.com/voicemail?Email=#{email}&#{url_message}"_60_60 client.calls(call_sid).update(url: redirect_url)_60 end_60_60 def notify_offline_status(phone_number)_60 message = 'Your status has changed to Offline. Reply with '\_60 '"On" to get back Online'_60 client.messages.create(_60 to: phone_number,_60 from: ENV['TWILIO_NUMBER'],_60 body: message_60 )_60 end_60_60 def client_60 Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])_60 end_60end
See how to allow Workers change their availability status
We have created this endpoint, so a worker can send an SMS message to the support line with the command "On" or "Off" to change their availability status.
This is important as a worker's activity will change to Offline
when they miss a call. When this happens, they receive an SMS letting them know that their activity has changed, and that they can reply with the On
command to make themselves available for incoming calls again.
app/controllers/message_controller.rb
_36class MessageController < ApplicationController_36 skip_before_filter :verify_authenticity_token_36_36 def incoming_36 command = params['Body'].downcase_36 from_number = params['From']_36_36 if command == 'off'_36 status = 'Offline'_36 activity_sid = WorkspaceInfo.instance.offline_activity_sid_36 else_36 status = 'Idle'_36 activity_sid = WorkspaceInfo.instance.idle_activity_sid_36 end_36_36 worker_sid = WorkspaceInfo.instance.workers[from_number][:sid]_36 client_36 .workspaces(WorkspaceInfo.instance.workspace_sid)_36 .workers(worker_sid)_36 .fetch_36 .update(activity_sid: activity_sid)_36_36 render xml: TwimlGenerator.generate_confirm_message(status)_36 end_36_36 private_36_36 def client_36 client_instance = Twilio::REST::Client.new(_36 ENV['TWILIO_ACCOUNT_SID'],_36 ENV['TWILIO_AUTH_TOKEN']_36 )_36_36 client_instance.taskrouter.v1_36 end_36end
Congratulations! You finished this tutorial. As you can see, using Twilio's TaskRouter is quite simple.
If you're a Ruby developer working with Twilio, you might also enjoy these tutorials:
Voice JavaScript SDK Quickstart
Learn how to use Twilio JavaScript Voice SDK to make browser-to-phone and browser-to-browser calls with ease.
Learn how to implement ETA Notifications using Ruby on Rails and Twilio.
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think!