Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Dynamic Call Center with Ruby and Rails


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.


This is what the application does at a high level

this-is-what-the-application-does-at-a-high-level page anchor
  • Configure a workspace using the Twilio TaskRouter REST API .
  • Listen for incoming calls and let the user select a product with the dial pad.
  • Create a Task with the selected product and let TaskRouter handle it.
  • Store missed calls so agents can return the call to customers.
  • Redirect users to a voice mail when no one answers the call.
  • Allow agents to change their status (Available/Offline) via SMS.

In order to instruct TaskRouter to handle the Tasks, we need to configure a Workspace. We can do this in the TaskRouter Console(link takes you to an external page) 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:

  • Tasks - Represents a customer trying to contact an agent
  • Workers - The agents responsible for handling Tasks
  • Task Queues - Holds Tasks to be consumed by a set of Workers
  • Workflows - Responsible for placing Tasks into Task Queues
  • Activities - Possible states of a Worker. Eg: idle, offline, busy

We'll use a TaskRouterClient provided in the twilio-ruby gem to create and configure the workspace.

Create, Setup and Configure the Workspace

create-setup-and-configure-the-workspace page anchor

lib/workspace_config.rb


_168
class 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
_168
end

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.

See how to create a new workspace

see-how-to-create-a-new-workspace page anchor

lib/workspace_config.rb


_168
class 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
_168
end

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


_168
class 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
_168
end

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:

  1. SMS - Will target Workers specialized in Programmable SMS, such as Bob, using the expression "products HAS \"ProgrammableSMS\"" .
  2. Voice - Will do the same for Programmable Voice Workers, such as Alice, using the expression "products HAS \"ProgrammableVoice\"" .
  3. 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


_168
class 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
_168
end

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:

  1. friendly_name as the name of a Workflow.
  2. 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.
  3. task_reservation_timeout as the maximum time we want to wait until a Worker is available for handling a Task.
  4. 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


_168
class 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
_168
end

Our workspace is completely setup. Now it's time to see how we use it to route calls.


Handle Twilio's Request

handle-twilios-request page anchor

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


_29
module 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
_29
end

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


_29
module 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
_29
end

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:

  1. The Task's Assignment Status is set to 'reserved'.
  2. A Reservation instance is generated, linking the Task to the selected Worker.
  3. At the same time the Reservation is created, a POST request is made to the Workflow's AssignmentCallbackURL, which was configured using the WorkspaceConfig(link takes you to an external page) 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


_60
class 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
_60
end

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


_60
class 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
_60
end

See how to allow Workers change their availability status


Change a Worker's Activity

change-a-workers-activity page anchor

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.

Handle Message to update the Worker Status

handle-message-to-update-the-worker-status page anchor

app/controllers/message_controller.rb


_36
class 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
_36
end

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.

ETA-Notifications(link takes you to an external page)

Learn how to implement ETA Notifications using Ruby on Rails and Twilio.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio(link takes you to an external page) to let us know what you think!


Rate this page: