Skip to contentSkip to navigationSkip to topbar
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

1
class WorkspaceConfig
2
WORKSPACE_NAME = 'Rails Workspace'.freeze
3
WORKFLOW_NAME = 'Sales'.freeze
4
WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze
5
QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze
6
ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze
7
EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze
8
BOB_NUMBER = ENV['BOB_NUMBER'].freeze
9
ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze
10
11
def self.setup
12
puts 'Configuring workspace, please wait ...'
13
new.setup
14
puts 'Workspace ready!'
15
end
16
17
def initialize
18
@account_sid = ENV['TWILIO_ACCOUNT_SID']
19
@auth_token = ENV['TWILIO_AUTH_TOKEN']
20
@client = taskrouter_client
21
end
22
23
def setup
24
@workspace_sid = create_workspace
25
@client = taskrouter_client
26
WorkspaceInfo.instance.workers = create_workers
27
workflow_sid = create_workflow.sid
28
WorkspaceInfo.instance.workflow_sid = workflow_sid
29
idle_activity_sid = activity_by_name('Idle').sid
30
WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid
31
WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid
32
WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid
33
WorkspaceInfo.instance.workspace_sid = @workspace_sid
34
end
35
36
private
37
38
attr_reader :client, :account_sid, :auth_token
39
40
def taskrouter_client
41
client_instance = Twilio::REST::Client.new(
42
account_sid,
43
auth_token
44
)
45
46
client_instance.taskrouter.v1
47
end
48
49
def create_workspace
50
workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first
51
workspace.delete unless workspace.nil?
52
53
workspace = client.workspaces.create(
54
friendly_name: WORKSPACE_NAME,
55
event_callback_url: EVENT_CALLBACK_URL
56
)
57
58
workspace.sid
59
end
60
61
def create_workers
62
bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"
63
alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"
64
65
bob = create_worker('Bob', bob_attributes)
66
alice = create_worker('Alice', alice_attributes)
67
68
{
69
BOB_NUMBER => { sid: bob.sid, name: 'Bob' },
70
ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }
71
}
72
end
73
74
def create_worker(name, attributes)
75
client.workspaces(@workspace_sid).workers.create(
76
friendly_name: name,
77
attributes: attributes,
78
activity_sid: activity_by_name('Idle').sid
79
)
80
end
81
82
def activity_by_name(name)
83
client.workspaces(@workspace_sid).activities.list(friendly_name: name).first
84
end
85
86
def create_task_queues
87
reservation_activity_sid = activity_by_name('Reserved').sid
88
assignment_activity_sid = activity_by_name('Busy').sid
89
90
voice_queue = create_task_queue('Voice', reservation_activity_sid,
91
assignment_activity_sid,
92
"products HAS 'ProgrammableVoice'")
93
94
sms_queue = create_task_queue('SMS', reservation_activity_sid,
95
assignment_activity_sid,
96
"products HAS 'ProgrammableSMS'")
97
98
all_queue = create_task_queue('All', reservation_activity_sid,
99
assignment_activity_sid, '1==1')
100
101
{ voice: voice_queue, sms: sms_queue, all: all_queue }
102
end
103
104
def create_task_queue(name, reservation_sid, assignment_sid, target_workers)
105
client.workspaces(@workspace_sid).task_queues.create(
106
friendly_name: name,
107
reservation_activity_sid: reservation_sid,
108
assignment_activity_sid: assignment_sid,
109
target_workers: target_workers
110
)
111
end
112
113
def create_workflow
114
queues = create_task_queues
115
config = workflow_config(queues)
116
117
client.workspaces(@workspace_sid).workflows.create(
118
configuration: config.to_json,
119
friendly_name: WORKFLOW_NAME,
120
assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
121
fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
122
task_reservation_timeout: WORKFLOW_TIMEOUT
123
)
124
end
125
126
def workspace_sid
127
@workspace_sid || 'no_workspace_yet'
128
end
129
130
def workflow_config(queues)
131
default_target = default_rule_target(queues[:all].sid)
132
133
{
134
task_routing: {
135
filters: [
136
{
137
expression: 'selected_product=="ProgrammableVoice"',
138
targets: [
139
rule_target(queues[:voice].sid),
140
default_target
141
]
142
},
143
{
144
expression: 'selected_product=="ProgrammableSMS"',
145
targets: [
146
rule_target(queues[:sms].sid),
147
default_target
148
]
149
}
150
],
151
default_filter: default_target
152
}
153
}
154
end
155
156
def rule_target(sid)
157
{ queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }
158
end
159
160
def default_rule_target(sid)
161
{
162
queue: sid,
163
priority: 1,
164
timeout: QUEUE_TIMEOUT,
165
expression: '1==1'
166
}
167
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

1
class WorkspaceConfig
2
WORKSPACE_NAME = 'Rails Workspace'.freeze
3
WORKFLOW_NAME = 'Sales'.freeze
4
WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze
5
QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze
6
ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze
7
EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze
8
BOB_NUMBER = ENV['BOB_NUMBER'].freeze
9
ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze
10
11
def self.setup
12
puts 'Configuring workspace, please wait ...'
13
new.setup
14
puts 'Workspace ready!'
15
end
16
17
def initialize
18
@account_sid = ENV['TWILIO_ACCOUNT_SID']
19
@auth_token = ENV['TWILIO_AUTH_TOKEN']
20
@client = taskrouter_client
21
end
22
23
def setup
24
@workspace_sid = create_workspace
25
@client = taskrouter_client
26
WorkspaceInfo.instance.workers = create_workers
27
workflow_sid = create_workflow.sid
28
WorkspaceInfo.instance.workflow_sid = workflow_sid
29
idle_activity_sid = activity_by_name('Idle').sid
30
WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid
31
WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid
32
WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid
33
WorkspaceInfo.instance.workspace_sid = @workspace_sid
34
end
35
36
private
37
38
attr_reader :client, :account_sid, :auth_token
39
40
def taskrouter_client
41
client_instance = Twilio::REST::Client.new(
42
account_sid,
43
auth_token
44
)
45
46
client_instance.taskrouter.v1
47
end
48
49
def create_workspace
50
workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first
51
workspace.delete unless workspace.nil?
52
53
workspace = client.workspaces.create(
54
friendly_name: WORKSPACE_NAME,
55
event_callback_url: EVENT_CALLBACK_URL
56
)
57
58
workspace.sid
59
end
60
61
def create_workers
62
bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"
63
alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"
64
65
bob = create_worker('Bob', bob_attributes)
66
alice = create_worker('Alice', alice_attributes)
67
68
{
69
BOB_NUMBER => { sid: bob.sid, name: 'Bob' },
70
ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }
71
}
72
end
73
74
def create_worker(name, attributes)
75
client.workspaces(@workspace_sid).workers.create(
76
friendly_name: name,
77
attributes: attributes,
78
activity_sid: activity_by_name('Idle').sid
79
)
80
end
81
82
def activity_by_name(name)
83
client.workspaces(@workspace_sid).activities.list(friendly_name: name).first
84
end
85
86
def create_task_queues
87
reservation_activity_sid = activity_by_name('Reserved').sid
88
assignment_activity_sid = activity_by_name('Busy').sid
89
90
voice_queue = create_task_queue('Voice', reservation_activity_sid,
91
assignment_activity_sid,
92
"products HAS 'ProgrammableVoice'")
93
94
sms_queue = create_task_queue('SMS', reservation_activity_sid,
95
assignment_activity_sid,
96
"products HAS 'ProgrammableSMS'")
97
98
all_queue = create_task_queue('All', reservation_activity_sid,
99
assignment_activity_sid, '1==1')
100
101
{ voice: voice_queue, sms: sms_queue, all: all_queue }
102
end
103
104
def create_task_queue(name, reservation_sid, assignment_sid, target_workers)
105
client.workspaces(@workspace_sid).task_queues.create(
106
friendly_name: name,
107
reservation_activity_sid: reservation_sid,
108
assignment_activity_sid: assignment_sid,
109
target_workers: target_workers
110
)
111
end
112
113
def create_workflow
114
queues = create_task_queues
115
config = workflow_config(queues)
116
117
client.workspaces(@workspace_sid).workflows.create(
118
configuration: config.to_json,
119
friendly_name: WORKFLOW_NAME,
120
assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
121
fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
122
task_reservation_timeout: WORKFLOW_TIMEOUT
123
)
124
end
125
126
def workspace_sid
127
@workspace_sid || 'no_workspace_yet'
128
end
129
130
def workflow_config(queues)
131
default_target = default_rule_target(queues[:all].sid)
132
133
{
134
task_routing: {
135
filters: [
136
{
137
expression: 'selected_product=="ProgrammableVoice"',
138
targets: [
139
rule_target(queues[:voice].sid),
140
default_target
141
]
142
},
143
{
144
expression: 'selected_product=="ProgrammableSMS"',
145
targets: [
146
rule_target(queues[:sms].sid),
147
default_target
148
]
149
}
150
],
151
default_filter: default_target
152
}
153
}
154
end
155
156
def rule_target(sid)
157
{ queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }
158
end
159
160
def default_rule_target(sid)
161
{
162
queue: sid,
163
priority: 1,
164
timeout: QUEUE_TIMEOUT,
165
expression: '1==1'
166
}
167
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

1
class WorkspaceConfig
2
WORKSPACE_NAME = 'Rails Workspace'.freeze
3
WORKFLOW_NAME = 'Sales'.freeze
4
WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze
5
QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze
6
ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze
7
EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze
8
BOB_NUMBER = ENV['BOB_NUMBER'].freeze
9
ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze
10
11
def self.setup
12
puts 'Configuring workspace, please wait ...'
13
new.setup
14
puts 'Workspace ready!'
15
end
16
17
def initialize
18
@account_sid = ENV['TWILIO_ACCOUNT_SID']
19
@auth_token = ENV['TWILIO_AUTH_TOKEN']
20
@client = taskrouter_client
21
end
22
23
def setup
24
@workspace_sid = create_workspace
25
@client = taskrouter_client
26
WorkspaceInfo.instance.workers = create_workers
27
workflow_sid = create_workflow.sid
28
WorkspaceInfo.instance.workflow_sid = workflow_sid
29
idle_activity_sid = activity_by_name('Idle').sid
30
WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid
31
WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid
32
WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid
33
WorkspaceInfo.instance.workspace_sid = @workspace_sid
34
end
35
36
private
37
38
attr_reader :client, :account_sid, :auth_token
39
40
def taskrouter_client
41
client_instance = Twilio::REST::Client.new(
42
account_sid,
43
auth_token
44
)
45
46
client_instance.taskrouter.v1
47
end
48
49
def create_workspace
50
workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first
51
workspace.delete unless workspace.nil?
52
53
workspace = client.workspaces.create(
54
friendly_name: WORKSPACE_NAME,
55
event_callback_url: EVENT_CALLBACK_URL
56
)
57
58
workspace.sid
59
end
60
61
def create_workers
62
bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"
63
alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"
64
65
bob = create_worker('Bob', bob_attributes)
66
alice = create_worker('Alice', alice_attributes)
67
68
{
69
BOB_NUMBER => { sid: bob.sid, name: 'Bob' },
70
ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }
71
}
72
end
73
74
def create_worker(name, attributes)
75
client.workspaces(@workspace_sid).workers.create(
76
friendly_name: name,
77
attributes: attributes,
78
activity_sid: activity_by_name('Idle').sid
79
)
80
end
81
82
def activity_by_name(name)
83
client.workspaces(@workspace_sid).activities.list(friendly_name: name).first
84
end
85
86
def create_task_queues
87
reservation_activity_sid = activity_by_name('Reserved').sid
88
assignment_activity_sid = activity_by_name('Busy').sid
89
90
voice_queue = create_task_queue('Voice', reservation_activity_sid,
91
assignment_activity_sid,
92
"products HAS 'ProgrammableVoice'")
93
94
sms_queue = create_task_queue('SMS', reservation_activity_sid,
95
assignment_activity_sid,
96
"products HAS 'ProgrammableSMS'")
97
98
all_queue = create_task_queue('All', reservation_activity_sid,
99
assignment_activity_sid, '1==1')
100
101
{ voice: voice_queue, sms: sms_queue, all: all_queue }
102
end
103
104
def create_task_queue(name, reservation_sid, assignment_sid, target_workers)
105
client.workspaces(@workspace_sid).task_queues.create(
106
friendly_name: name,
107
reservation_activity_sid: reservation_sid,
108
assignment_activity_sid: assignment_sid,
109
target_workers: target_workers
110
)
111
end
112
113
def create_workflow
114
queues = create_task_queues
115
config = workflow_config(queues)
116
117
client.workspaces(@workspace_sid).workflows.create(
118
configuration: config.to_json,
119
friendly_name: WORKFLOW_NAME,
120
assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
121
fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
122
task_reservation_timeout: WORKFLOW_TIMEOUT
123
)
124
end
125
126
def workspace_sid
127
@workspace_sid || 'no_workspace_yet'
128
end
129
130
def workflow_config(queues)
131
default_target = default_rule_target(queues[:all].sid)
132
133
{
134
task_routing: {
135
filters: [
136
{
137
expression: 'selected_product=="ProgrammableVoice"',
138
targets: [
139
rule_target(queues[:voice].sid),
140
default_target
141
]
142
},
143
{
144
expression: 'selected_product=="ProgrammableSMS"',
145
targets: [
146
rule_target(queues[:sms].sid),
147
default_target
148
]
149
}
150
],
151
default_filter: default_target
152
}
153
}
154
end
155
156
def rule_target(sid)
157
{ queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }
158
end
159
160
def default_rule_target(sid)
161
{
162
queue: sid,
163
priority: 1,
164
timeout: QUEUE_TIMEOUT,
165
expression: '1==1'
166
}
167
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

1
class WorkspaceConfig
2
WORKSPACE_NAME = 'Rails Workspace'.freeze
3
WORKFLOW_NAME = 'Sales'.freeze
4
WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze
5
QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze
6
ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze
7
EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze
8
BOB_NUMBER = ENV['BOB_NUMBER'].freeze
9
ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze
10
11
def self.setup
12
puts 'Configuring workspace, please wait ...'
13
new.setup
14
puts 'Workspace ready!'
15
end
16
17
def initialize
18
@account_sid = ENV['TWILIO_ACCOUNT_SID']
19
@auth_token = ENV['TWILIO_AUTH_TOKEN']
20
@client = taskrouter_client
21
end
22
23
def setup
24
@workspace_sid = create_workspace
25
@client = taskrouter_client
26
WorkspaceInfo.instance.workers = create_workers
27
workflow_sid = create_workflow.sid
28
WorkspaceInfo.instance.workflow_sid = workflow_sid
29
idle_activity_sid = activity_by_name('Idle').sid
30
WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid
31
WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid
32
WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid
33
WorkspaceInfo.instance.workspace_sid = @workspace_sid
34
end
35
36
private
37
38
attr_reader :client, :account_sid, :auth_token
39
40
def taskrouter_client
41
client_instance = Twilio::REST::Client.new(
42
account_sid,
43
auth_token
44
)
45
46
client_instance.taskrouter.v1
47
end
48
49
def create_workspace
50
workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first
51
workspace.delete unless workspace.nil?
52
53
workspace = client.workspaces.create(
54
friendly_name: WORKSPACE_NAME,
55
event_callback_url: EVENT_CALLBACK_URL
56
)
57
58
workspace.sid
59
end
60
61
def create_workers
62
bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"
63
alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"
64
65
bob = create_worker('Bob', bob_attributes)
66
alice = create_worker('Alice', alice_attributes)
67
68
{
69
BOB_NUMBER => { sid: bob.sid, name: 'Bob' },
70
ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }
71
}
72
end
73
74
def create_worker(name, attributes)
75
client.workspaces(@workspace_sid).workers.create(
76
friendly_name: name,
77
attributes: attributes,
78
activity_sid: activity_by_name('Idle').sid
79
)
80
end
81
82
def activity_by_name(name)
83
client.workspaces(@workspace_sid).activities.list(friendly_name: name).first
84
end
85
86
def create_task_queues
87
reservation_activity_sid = activity_by_name('Reserved').sid
88
assignment_activity_sid = activity_by_name('Busy').sid
89
90
voice_queue = create_task_queue('Voice', reservation_activity_sid,
91
assignment_activity_sid,
92
"products HAS 'ProgrammableVoice'")
93
94
sms_queue = create_task_queue('SMS', reservation_activity_sid,
95
assignment_activity_sid,
96
"products HAS 'ProgrammableSMS'")
97
98
all_queue = create_task_queue('All', reservation_activity_sid,
99
assignment_activity_sid, '1==1')
100
101
{ voice: voice_queue, sms: sms_queue, all: all_queue }
102
end
103
104
def create_task_queue(name, reservation_sid, assignment_sid, target_workers)
105
client.workspaces(@workspace_sid).task_queues.create(
106
friendly_name: name,
107
reservation_activity_sid: reservation_sid,
108
assignment_activity_sid: assignment_sid,
109
target_workers: target_workers
110
)
111
end
112
113
def create_workflow
114
queues = create_task_queues
115
config = workflow_config(queues)
116
117
client.workspaces(@workspace_sid).workflows.create(
118
configuration: config.to_json,
119
friendly_name: WORKFLOW_NAME,
120
assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
121
fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
122
task_reservation_timeout: WORKFLOW_TIMEOUT
123
)
124
end
125
126
def workspace_sid
127
@workspace_sid || 'no_workspace_yet'
128
end
129
130
def workflow_config(queues)
131
default_target = default_rule_target(queues[:all].sid)
132
133
{
134
task_routing: {
135
filters: [
136
{
137
expression: 'selected_product=="ProgrammableVoice"',
138
targets: [
139
rule_target(queues[:voice].sid),
140
default_target
141
]
142
},
143
{
144
expression: 'selected_product=="ProgrammableSMS"',
145
targets: [
146
rule_target(queues[:sms].sid),
147
default_target
148
]
149
}
150
],
151
default_filter: default_target
152
}
153
}
154
end
155
156
def rule_target(sid)
157
{ queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }
158
end
159
160
def default_rule_target(sid)
161
{
162
queue: sid,
163
priority: 1,
164
timeout: QUEUE_TIMEOUT,
165
expression: '1==1'
166
}
167
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

1
class WorkspaceConfig
2
WORKSPACE_NAME = 'Rails Workspace'.freeze
3
WORKFLOW_NAME = 'Sales'.freeze
4
WORKFLOW_TIMEOUT = ENV['WORKFLOW_TIMEOUT'].freeze
5
QUEUE_TIMEOUT = ENV['QUEUE_TIMEOUT'].freeze
6
ASSIGNMENT_CALLBACK_URL = ENV['ASSIGNMENT_CALLBACK_URL'].freeze
7
EVENT_CALLBACK_URL = ENV['EVENT_CALLBACK_URL'].freeze
8
BOB_NUMBER = ENV['BOB_NUMBER'].freeze
9
ALICE_NUMBER = ENV['ALICE_NUMBER'].freeze
10
11
def self.setup
12
puts 'Configuring workspace, please wait ...'
13
new.setup
14
puts 'Workspace ready!'
15
end
16
17
def initialize
18
@account_sid = ENV['TWILIO_ACCOUNT_SID']
19
@auth_token = ENV['TWILIO_AUTH_TOKEN']
20
@client = taskrouter_client
21
end
22
23
def setup
24
@workspace_sid = create_workspace
25
@client = taskrouter_client
26
WorkspaceInfo.instance.workers = create_workers
27
workflow_sid = create_workflow.sid
28
WorkspaceInfo.instance.workflow_sid = workflow_sid
29
idle_activity_sid = activity_by_name('Idle').sid
30
WorkspaceInfo.instance.post_work_activity_sid = idle_activity_sid
31
WorkspaceInfo.instance.idle_activity_sid = idle_activity_sid
32
WorkspaceInfo.instance.offline_activity_sid = activity_by_name('Offline').sid
33
WorkspaceInfo.instance.workspace_sid = @workspace_sid
34
end
35
36
private
37
38
attr_reader :client, :account_sid, :auth_token
39
40
def taskrouter_client
41
client_instance = Twilio::REST::Client.new(
42
account_sid,
43
auth_token
44
)
45
46
client_instance.taskrouter.v1
47
end
48
49
def create_workspace
50
workspace = client.workspaces.list(friendly_name: WORKSPACE_NAME).first
51
workspace.delete unless workspace.nil?
52
53
workspace = client.workspaces.create(
54
friendly_name: WORKSPACE_NAME,
55
event_callback_url: EVENT_CALLBACK_URL
56
)
57
58
workspace.sid
59
end
60
61
def create_workers
62
bob_attributes = "{\"products\": [\"ProgrammableSMS\"], \"contact_uri\": \"#{BOB_NUMBER}\"}"
63
alice_attributes = "{\"products\": [\"ProgrammableVoice\"], \"contact_uri\": \"#{ALICE_NUMBER}\"}"
64
65
bob = create_worker('Bob', bob_attributes)
66
alice = create_worker('Alice', alice_attributes)
67
68
{
69
BOB_NUMBER => { sid: bob.sid, name: 'Bob' },
70
ALICE_NUMBER => { sid: alice.sid, name: 'Alice' }
71
}
72
end
73
74
def create_worker(name, attributes)
75
client.workspaces(@workspace_sid).workers.create(
76
friendly_name: name,
77
attributes: attributes,
78
activity_sid: activity_by_name('Idle').sid
79
)
80
end
81
82
def activity_by_name(name)
83
client.workspaces(@workspace_sid).activities.list(friendly_name: name).first
84
end
85
86
def create_task_queues
87
reservation_activity_sid = activity_by_name('Reserved').sid
88
assignment_activity_sid = activity_by_name('Busy').sid
89
90
voice_queue = create_task_queue('Voice', reservation_activity_sid,
91
assignment_activity_sid,
92
"products HAS 'ProgrammableVoice'")
93
94
sms_queue = create_task_queue('SMS', reservation_activity_sid,
95
assignment_activity_sid,
96
"products HAS 'ProgrammableSMS'")
97
98
all_queue = create_task_queue('All', reservation_activity_sid,
99
assignment_activity_sid, '1==1')
100
101
{ voice: voice_queue, sms: sms_queue, all: all_queue }
102
end
103
104
def create_task_queue(name, reservation_sid, assignment_sid, target_workers)
105
client.workspaces(@workspace_sid).task_queues.create(
106
friendly_name: name,
107
reservation_activity_sid: reservation_sid,
108
assignment_activity_sid: assignment_sid,
109
target_workers: target_workers
110
)
111
end
112
113
def create_workflow
114
queues = create_task_queues
115
config = workflow_config(queues)
116
117
client.workspaces(@workspace_sid).workflows.create(
118
configuration: config.to_json,
119
friendly_name: WORKFLOW_NAME,
120
assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
121
fallback_assignment_callback_url: ASSIGNMENT_CALLBACK_URL,
122
task_reservation_timeout: WORKFLOW_TIMEOUT
123
)
124
end
125
126
def workspace_sid
127
@workspace_sid || 'no_workspace_yet'
128
end
129
130
def workflow_config(queues)
131
default_target = default_rule_target(queues[:all].sid)
132
133
{
134
task_routing: {
135
filters: [
136
{
137
expression: 'selected_product=="ProgrammableVoice"',
138
targets: [
139
rule_target(queues[:voice].sid),
140
default_target
141
]
142
},
143
{
144
expression: 'selected_product=="ProgrammableSMS"',
145
targets: [
146
rule_target(queues[:sms].sid),
147
default_target
148
]
149
}
150
],
151
default_filter: default_target
152
}
153
}
154
end
155
156
def rule_target(sid)
157
{ queue: sid, priority: 5, timeout: QUEUE_TIMEOUT }
158
end
159
160
def default_rule_target(sid)
161
{
162
queue: sid,
163
priority: 1,
164
timeout: QUEUE_TIMEOUT,
165
expression: '1==1'
166
}
167
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

1
module TwimlGenerator
2
def self.generate_gather_product(callback_url)
3
response = Twilio::TwiML::VoiceResponse.new
4
gather = Twilio::TwiML::Gather.new(num_digits: 1,
5
action: callback_url,
6
method: 'POST')
7
gather.say 'Welcome to the Twilio support line!'
8
gather.say 'To get specialized help with programmable voice press 1, '\
9
'or press 2 for programmable SMS'
10
11
response.append(gather)
12
response.to_s
13
end
14
15
def self.generate_task_enqueue(selected_product)
16
enqueue = Twilio::TwiML::Enqueue.new(nil, workflow_sid: WorkspaceInfo.instance.workflow_sid)
17
enqueue.task "{\"selected_product\": \"#{selected_product}\"}"
18
19
response = Twilio::TwiML::VoiceResponse.new
20
response.append(enqueue)
21
response.to_s
22
end
23
24
def self.generate_confirm_message(status)
25
response = Twilio::TwiML::MessagingResponse.new
26
response.message(body: "Your status has changed to #{status}")
27
response.to_s
28
end
29
end

We just asked the caller to choose a product, next we will use their choice to create the appropriate 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

1
module TwimlGenerator
2
def self.generate_gather_product(callback_url)
3
response = Twilio::TwiML::VoiceResponse.new
4
gather = Twilio::TwiML::Gather.new(num_digits: 1,
5
action: callback_url,
6
method: 'POST')
7
gather.say 'Welcome to the Twilio support line!'
8
gather.say 'To get specialized help with programmable voice press 1, '\
9
'or press 2 for programmable SMS'
10
11
response.append(gather)
12
response.to_s
13
end
14
15
def self.generate_task_enqueue(selected_product)
16
enqueue = Twilio::TwiML::Enqueue.new(nil, workflow_sid: WorkspaceInfo.instance.workflow_sid)
17
enqueue.task "{\"selected_product\": \"#{selected_product}\"}"
18
19
response = Twilio::TwiML::VoiceResponse.new
20
response.append(enqueue)
21
response.to_s
22
end
23
24
def self.generate_confirm_message(status)
25
response = Twilio::TwiML::MessagingResponse.new
26
response.message(body: "Your status has changed to #{status}")
27
response.to_s
28
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

1
class CallbackController < ApplicationController
2
skip_before_filter :verify_authenticity_token
3
4
def assignment
5
instruction = {
6
instruction: 'dequeue',
7
post_work_activity_sid: WorkspaceInfo.instance.post_work_activity_sid
8
}
9
10
render json: instruction
11
end
12
13
def events
14
event_type = params[:EventType]
15
16
if ['workflow.timeout', 'task.canceled'].include?(event_type)
17
task_attributes = JSON.parse(params[:TaskAttributes])
18
19
MissedCall.create(
20
selected_product: task_attributes['selected_product'],
21
phone_number: task_attributes['from']
22
)
23
24
redirect_to_voicemail(task_attributes['call_sid']) if event_type == 'workflow.timeout'
25
elsif event_type == 'worker.activity.update' &&
26
params[:WorkerActivityName] == 'Offline'
27
28
worker_attributes = JSON.parse(params[:WorkerAttributes])
29
notify_offline_status(worker_attributes['contact_uri'])
30
end
31
32
render nothing: true
33
end
34
35
private
36
37
def redirect_to_voicemail(call_sid)
38
email = ENV['MISSED_CALLS_EMAIL_ADDRESS']
39
message = 'Sorry, All agents are busy. Please leave a message. We\'ll call you as soon as possible'
40
url_message = { Message: message }.to_query
41
redirect_url =
42
"http://twimlets.com/voicemail?Email=#{email}&#{url_message}"
43
44
client.calls(call_sid).update(url: redirect_url)
45
end
46
47
def notify_offline_status(phone_number)
48
message = 'Your status has changed to Offline. Reply with '\
49
'"On" to get back Online'
50
client.messages.create(
51
to: phone_number,
52
from: ENV['TWILIO_NUMBER'],
53
body: message
54
)
55
end
56
57
def client
58
Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])
59
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

1
class CallbackController < ApplicationController
2
skip_before_filter :verify_authenticity_token
3
4
def assignment
5
instruction = {
6
instruction: 'dequeue',
7
post_work_activity_sid: WorkspaceInfo.instance.post_work_activity_sid
8
}
9
10
render json: instruction
11
end
12
13
def events
14
event_type = params[:EventType]
15
16
if ['workflow.timeout', 'task.canceled'].include?(event_type)
17
task_attributes = JSON.parse(params[:TaskAttributes])
18
19
MissedCall.create(
20
selected_product: task_attributes['selected_product'],
21
phone_number: task_attributes['from']
22
)
23
24
redirect_to_voicemail(task_attributes['call_sid']) if event_type == 'workflow.timeout'
25
elsif event_type == 'worker.activity.update' &&
26
params[:WorkerActivityName] == 'Offline'
27
28
worker_attributes = JSON.parse(params[:WorkerAttributes])
29
notify_offline_status(worker_attributes['contact_uri'])
30
end
31
32
render nothing: true
33
end
34
35
private
36
37
def redirect_to_voicemail(call_sid)
38
email = ENV['MISSED_CALLS_EMAIL_ADDRESS']
39
message = 'Sorry, All agents are busy. Please leave a message. We\'ll call you as soon as possible'
40
url_message = { Message: message }.to_query
41
redirect_url =
42
"http://twimlets.com/voicemail?Email=#{email}&#{url_message}"
43
44
client.calls(call_sid).update(url: redirect_url)
45
end
46
47
def notify_offline_status(phone_number)
48
message = 'Your status has changed to Offline. Reply with '\
49
'"On" to get back Online'
50
client.messages.create(
51
to: phone_number,
52
from: ENV['TWILIO_NUMBER'],
53
body: message
54
)
55
end
56
57
def client
58
Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])
59
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

1
class MessageController < ApplicationController
2
skip_before_filter :verify_authenticity_token
3
4
def incoming
5
command = params['Body'].downcase
6
from_number = params['From']
7
8
if command == 'off'
9
status = 'Offline'
10
activity_sid = WorkspaceInfo.instance.offline_activity_sid
11
else
12
status = 'Idle'
13
activity_sid = WorkspaceInfo.instance.idle_activity_sid
14
end
15
16
worker_sid = WorkspaceInfo.instance.workers[from_number][:sid]
17
client
18
.workspaces(WorkspaceInfo.instance.workspace_sid)
19
.workers(worker_sid)
20
.fetch
21
.update(activity_sid: activity_sid)
22
23
render xml: TwimlGenerator.generate_confirm_message(status)
24
end
25
26
private
27
28
def client
29
client_instance = Twilio::REST::Client.new(
30
ENV['TWILIO_ACCOUNT_SID'],
31
ENV['TWILIO_AUTH_TOKEN']
32
)
33
34
client_instance.taskrouter.v1
35
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.

Need some help?

Terms of service

Copyright © 2024 Twilio Inc.