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 Django application we'll do this setup when we start up the app.
A Workspace is the container element for any TaskRouter application. The elements are:
In order to build a client for this API, we need a TWILIO_ACCOUNT_SID
and TWILIO_AUTH_TOKEN
which you can find on Twilio Console. The function build_client
configures and returns a TwilioTaskRouterClient, which is provided by the Twilio Python library.
task_router/workspace.py
_163import json_163_163from django.conf import settings_163from twilio.rest import Client_163_163HOST = settings.HOST_163ALICE_NUMBER = settings.ALICE_NUMBER_163BOB_NUMBER = settings.BOB_NUMBER_163WORKSPACE_NAME = 'Twilio Workspace'_163_163_163def first(items):_163 return items[0] if items else None_163_163_163def build_client():_163 account_sid = settings.TWILIO_ACCOUNT_SID_163 auth_token = settings.TWILIO_AUTH_TOKEN_163 return Client(account_sid, auth_token)_163_163_163CACHE = {}_163_163_163def activities_dict(client, workspace_sid):_163 activities = client.taskrouter.workspaces(workspace_sid)\_163 .activities.list()_163_163 return {activity.friendly_name: activity for activity in activities}_163_163_163class WorkspaceInfo:_163_163 def __init__(self, workspace, workflow, activities, workers):_163 self.workflow_sid = workflow.sid_163 self.workspace_sid = workspace.sid_163 self.activities = activities_163 self.post_work_activity_sid = activities['Available'].sid_163 self.workers = workers_163_163_163def setup():_163 client = build_client()_163 if 'WORKSPACE_INFO' not in CACHE:_163 workspace = create_workspace(client)_163 activities = activities_dict(client, workspace.sid)_163 workers = create_workers(client, workspace, activities)_163 queues = create_task_queues(client, workspace, activities)_163 workflow = create_workflow(client, workspace, queues)_163 CACHE['WORKSPACE_INFO'] = WorkspaceInfo(workspace, workflow, activities, workers)_163 return CACHE['WORKSPACE_INFO']_163_163_163def create_workspace(client):_163 try:_163 workspace = first(client.taskrouter.workspaces.list(friendly_name=WORKSPACE_NAME))_163 client.taskrouter.workspaces(workspace.sid).delete()_163 except Exception:_163 pass_163_163 events_callback = HOST + '/events/'_163_163 return client.taskrouter.workspaces.create(_163 friendly_name=WORKSPACE_NAME,_163 event_callback_url=events_callback,_163 template=None)_163_163_163def create_workers(client, workspace, activities):_163 alice_attributes = {_163 "products": ["ProgrammableVoice"],_163 "contact_uri": ALICE_NUMBER_163 }_163_163 alice = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Alice',_163 attributes=json.dumps(alice_attributes))_163_163 bob_attributes = {_163 "products": ["ProgrammableSMS"],_163 "contact_uri": BOB_NUMBER_163 }_163_163 bob = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Bob',_163 attributes=json.dumps(bob_attributes))_163_163 return {BOB_NUMBER: bob.sid, ALICE_NUMBER: alice.sid}_163_163_163def create_task_queues(client, workspace, activities):_163 default_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Default',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='1==1')_163_163 sms_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='SMS',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableSMS" in products')_163_163 voice_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Voice',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableVoice" in products')_163_163 return {'sms': sms_queue, 'voice': voice_queue, 'default': default_queue}_163_163_163def create_workflow(client, workspace, queues):_163 defaultTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 smsTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 voiceTarget = {_163 'queue': queues['voice'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 default_filter = {_163 'filter_friendly_name': 'Default Filter',_163 'queue': queues['default'].sid,_163 'Expression': '1==1',_163 'priority': 1,_163 'timeout': 30_163 }_163_163 voiceFilter = {_163 'filter_friendly_name': 'Voice Filter',_163 'expression': 'selected_product=="ProgrammableVoice"',_163 'targets': [voiceTarget, defaultTarget]_163 }_163_163 smsFilter = {_163 'filter_friendly_name': 'SMS Filter',_163 'expression': 'selected_product=="ProgrammableSMS"',_163 'targets': [smsTarget, defaultTarget]_163 }_163_163 config = {_163 'task_routing': {_163 'filters': [voiceFilter, smsFilter],_163 'default_filter': default_filter_163 }_163 }_163_163 callback_url = HOST + '/assignment/'_163_163 return client.taskrouter.workspaces(workspace.sid)\_163 .workflows.create(friendly_name='Sales',_163 assignment_callback_url=callback_url,_163 fallback_assignment_callback_url=callback_url,_163 task_reservation_timeout=15,_163 configuration=json.dumps(config))
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 event_callback_url
where a requests will be made every time an event is triggered in our workspace.
task_router/workspace.py
_163import json_163_163from django.conf import settings_163from twilio.rest import Client_163_163HOST = settings.HOST_163ALICE_NUMBER = settings.ALICE_NUMBER_163BOB_NUMBER = settings.BOB_NUMBER_163WORKSPACE_NAME = 'Twilio Workspace'_163_163_163def first(items):_163 return items[0] if items else None_163_163_163def build_client():_163 account_sid = settings.TWILIO_ACCOUNT_SID_163 auth_token = settings.TWILIO_AUTH_TOKEN_163 return Client(account_sid, auth_token)_163_163_163CACHE = {}_163_163_163def activities_dict(client, workspace_sid):_163 activities = client.taskrouter.workspaces(workspace_sid)\_163 .activities.list()_163_163 return {activity.friendly_name: activity for activity in activities}_163_163_163class WorkspaceInfo:_163_163 def __init__(self, workspace, workflow, activities, workers):_163 self.workflow_sid = workflow.sid_163 self.workspace_sid = workspace.sid_163 self.activities = activities_163 self.post_work_activity_sid = activities['Available'].sid_163 self.workers = workers_163_163_163def setup():_163 client = build_client()_163 if 'WORKSPACE_INFO' not in CACHE:_163 workspace = create_workspace(client)_163 activities = activities_dict(client, workspace.sid)_163 workers = create_workers(client, workspace, activities)_163 queues = create_task_queues(client, workspace, activities)_163 workflow = create_workflow(client, workspace, queues)_163 CACHE['WORKSPACE_INFO'] = WorkspaceInfo(workspace, workflow, activities, workers)_163 return CACHE['WORKSPACE_INFO']_163_163_163def create_workspace(client):_163 try:_163 workspace = first(client.taskrouter.workspaces.list(friendly_name=WORKSPACE_NAME))_163 client.taskrouter.workspaces(workspace.sid).delete()_163 except Exception:_163 pass_163_163 events_callback = HOST + '/events/'_163_163 return client.taskrouter.workspaces.create(_163 friendly_name=WORKSPACE_NAME,_163 event_callback_url=events_callback,_163 template=None)_163_163_163def create_workers(client, workspace, activities):_163 alice_attributes = {_163 "products": ["ProgrammableVoice"],_163 "contact_uri": ALICE_NUMBER_163 }_163_163 alice = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Alice',_163 attributes=json.dumps(alice_attributes))_163_163 bob_attributes = {_163 "products": ["ProgrammableSMS"],_163 "contact_uri": BOB_NUMBER_163 }_163_163 bob = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Bob',_163 attributes=json.dumps(bob_attributes))_163_163 return {BOB_NUMBER: bob.sid, ALICE_NUMBER: alice.sid}_163_163_163def create_task_queues(client, workspace, activities):_163 default_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Default',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='1==1')_163_163 sms_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='SMS',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableSMS" in products')_163_163 voice_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Voice',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableVoice" in products')_163_163 return {'sms': sms_queue, 'voice': voice_queue, 'default': default_queue}_163_163_163def create_workflow(client, workspace, queues):_163 defaultTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 smsTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 voiceTarget = {_163 'queue': queues['voice'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 default_filter = {_163 'filter_friendly_name': 'Default Filter',_163 'queue': queues['default'].sid,_163 'Expression': '1==1',_163 'priority': 1,_163 'timeout': 30_163 }_163_163 voiceFilter = {_163 'filter_friendly_name': 'Voice Filter',_163 'expression': 'selected_product=="ProgrammableVoice"',_163 'targets': [voiceTarget, defaultTarget]_163 }_163_163 smsFilter = {_163 'filter_friendly_name': 'SMS Filter',_163 'expression': 'selected_product=="ProgrammableSMS"',_163 'targets': [smsTarget, defaultTarget]_163 }_163_163 config = {_163 'task_routing': {_163 'filters': [voiceFilter, smsFilter],_163 'default_filter': default_filter_163 }_163 }_163_163 callback_url = HOST + '/assignment/'_163_163 return client.taskrouter.workspaces(workspace.sid)\_163 .workflows.create(friendly_name='Sales',_163 assignment_callback_url=callback_url,_163 fallback_assignment_callback_url=callback_url,_163 task_reservation_timeout=15,_163 configuration=json.dumps(config))
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.
task_router/workspace.py
_163import json_163_163from django.conf import settings_163from twilio.rest import Client_163_163HOST = settings.HOST_163ALICE_NUMBER = settings.ALICE_NUMBER_163BOB_NUMBER = settings.BOB_NUMBER_163WORKSPACE_NAME = 'Twilio Workspace'_163_163_163def first(items):_163 return items[0] if items else None_163_163_163def build_client():_163 account_sid = settings.TWILIO_ACCOUNT_SID_163 auth_token = settings.TWILIO_AUTH_TOKEN_163 return Client(account_sid, auth_token)_163_163_163CACHE = {}_163_163_163def activities_dict(client, workspace_sid):_163 activities = client.taskrouter.workspaces(workspace_sid)\_163 .activities.list()_163_163 return {activity.friendly_name: activity for activity in activities}_163_163_163class WorkspaceInfo:_163_163 def __init__(self, workspace, workflow, activities, workers):_163 self.workflow_sid = workflow.sid_163 self.workspace_sid = workspace.sid_163 self.activities = activities_163 self.post_work_activity_sid = activities['Available'].sid_163 self.workers = workers_163_163_163def setup():_163 client = build_client()_163 if 'WORKSPACE_INFO' not in CACHE:_163 workspace = create_workspace(client)_163 activities = activities_dict(client, workspace.sid)_163 workers = create_workers(client, workspace, activities)_163 queues = create_task_queues(client, workspace, activities)_163 workflow = create_workflow(client, workspace, queues)_163 CACHE['WORKSPACE_INFO'] = WorkspaceInfo(workspace, workflow, activities, workers)_163 return CACHE['WORKSPACE_INFO']_163_163_163def create_workspace(client):_163 try:_163 workspace = first(client.taskrouter.workspaces.list(friendly_name=WORKSPACE_NAME))_163 client.taskrouter.workspaces(workspace.sid).delete()_163 except Exception:_163 pass_163_163 events_callback = HOST + '/events/'_163_163 return client.taskrouter.workspaces.create(_163 friendly_name=WORKSPACE_NAME,_163 event_callback_url=events_callback,_163 template=None)_163_163_163def create_workers(client, workspace, activities):_163 alice_attributes = {_163 "products": ["ProgrammableVoice"],_163 "contact_uri": ALICE_NUMBER_163 }_163_163 alice = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Alice',_163 attributes=json.dumps(alice_attributes))_163_163 bob_attributes = {_163 "products": ["ProgrammableSMS"],_163 "contact_uri": BOB_NUMBER_163 }_163_163 bob = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Bob',_163 attributes=json.dumps(bob_attributes))_163_163 return {BOB_NUMBER: bob.sid, ALICE_NUMBER: alice.sid}_163_163_163def create_task_queues(client, workspace, activities):_163 default_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Default',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='1==1')_163_163 sms_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='SMS',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableSMS" in products')_163_163 voice_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Voice',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableVoice" in products')_163_163 return {'sms': sms_queue, 'voice': voice_queue, 'default': default_queue}_163_163_163def create_workflow(client, workspace, queues):_163 defaultTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 smsTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 voiceTarget = {_163 'queue': queues['voice'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 default_filter = {_163 'filter_friendly_name': 'Default Filter',_163 'queue': queues['default'].sid,_163 'Expression': '1==1',_163 'priority': 1,_163 'timeout': 30_163 }_163_163 voiceFilter = {_163 'filter_friendly_name': 'Voice Filter',_163 'expression': 'selected_product=="ProgrammableVoice"',_163 'targets': [voiceTarget, defaultTarget]_163 }_163_163 smsFilter = {_163 'filter_friendly_name': 'SMS Filter',_163 'expression': 'selected_product=="ProgrammableSMS"',_163 'targets': [smsTarget, defaultTarget]_163 }_163_163 config = {_163 'task_routing': {_163 'filters': [voiceFilter, smsFilter],_163 'default_filter': default_filter_163 }_163 }_163_163 callback_url = HOST + '/assignment/'_163_163 return client.taskrouter.workspaces(workspace.sid)\_163 .workflows.create(friendly_name='Sales',_163 assignment_callback_url=callback_url,_163 fallback_assignment_callback_url=callback_url,_163 task_reservation_timeout=15,_163 configuration=json.dumps(config))
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
'"ProgrammableSMS" in products'
.
Voice
- Will do the same for Programmable Voice Workers, such as Alice, using the expression
'"ProgrammableVoice" in products'
.
Default
- 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.
task_router/workspace.py
_163import json_163_163from django.conf import settings_163from twilio.rest import Client_163_163HOST = settings.HOST_163ALICE_NUMBER = settings.ALICE_NUMBER_163BOB_NUMBER = settings.BOB_NUMBER_163WORKSPACE_NAME = 'Twilio Workspace'_163_163_163def first(items):_163 return items[0] if items else None_163_163_163def build_client():_163 account_sid = settings.TWILIO_ACCOUNT_SID_163 auth_token = settings.TWILIO_AUTH_TOKEN_163 return Client(account_sid, auth_token)_163_163_163CACHE = {}_163_163_163def activities_dict(client, workspace_sid):_163 activities = client.taskrouter.workspaces(workspace_sid)\_163 .activities.list()_163_163 return {activity.friendly_name: activity for activity in activities}_163_163_163class WorkspaceInfo:_163_163 def __init__(self, workspace, workflow, activities, workers):_163 self.workflow_sid = workflow.sid_163 self.workspace_sid = workspace.sid_163 self.activities = activities_163 self.post_work_activity_sid = activities['Available'].sid_163 self.workers = workers_163_163_163def setup():_163 client = build_client()_163 if 'WORKSPACE_INFO' not in CACHE:_163 workspace = create_workspace(client)_163 activities = activities_dict(client, workspace.sid)_163 workers = create_workers(client, workspace, activities)_163 queues = create_task_queues(client, workspace, activities)_163 workflow = create_workflow(client, workspace, queues)_163 CACHE['WORKSPACE_INFO'] = WorkspaceInfo(workspace, workflow, activities, workers)_163 return CACHE['WORKSPACE_INFO']_163_163_163def create_workspace(client):_163 try:_163 workspace = first(client.taskrouter.workspaces.list(friendly_name=WORKSPACE_NAME))_163 client.taskrouter.workspaces(workspace.sid).delete()_163 except Exception:_163 pass_163_163 events_callback = HOST + '/events/'_163_163 return client.taskrouter.workspaces.create(_163 friendly_name=WORKSPACE_NAME,_163 event_callback_url=events_callback,_163 template=None)_163_163_163def create_workers(client, workspace, activities):_163 alice_attributes = {_163 "products": ["ProgrammableVoice"],_163 "contact_uri": ALICE_NUMBER_163 }_163_163 alice = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Alice',_163 attributes=json.dumps(alice_attributes))_163_163 bob_attributes = {_163 "products": ["ProgrammableSMS"],_163 "contact_uri": BOB_NUMBER_163 }_163_163 bob = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Bob',_163 attributes=json.dumps(bob_attributes))_163_163 return {BOB_NUMBER: bob.sid, ALICE_NUMBER: alice.sid}_163_163_163def create_task_queues(client, workspace, activities):_163 default_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Default',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='1==1')_163_163 sms_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='SMS',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableSMS" in products')_163_163 voice_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Voice',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableVoice" in products')_163_163 return {'sms': sms_queue, 'voice': voice_queue, 'default': default_queue}_163_163_163def create_workflow(client, workspace, queues):_163 defaultTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 smsTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 voiceTarget = {_163 'queue': queues['voice'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 default_filter = {_163 'filter_friendly_name': 'Default Filter',_163 'queue': queues['default'].sid,_163 'Expression': '1==1',_163 'priority': 1,_163 'timeout': 30_163 }_163_163 voiceFilter = {_163 'filter_friendly_name': 'Voice Filter',_163 'expression': 'selected_product=="ProgrammableVoice"',_163 'targets': [voiceTarget, defaultTarget]_163 }_163_163 smsFilter = {_163 'filter_friendly_name': 'SMS Filter',_163 'expression': 'selected_product=="ProgrammableSMS"',_163 'targets': [smsTarget, defaultTarget]_163 }_163_163 config = {_163 'task_routing': {_163 'filters': [voiceFilter, smsFilter],_163 'default_filter': default_filter_163 }_163 }_163_163 callback_url = HOST + '/assignment/'_163_163 return client.taskrouter.workspaces(workspace.sid)\_163 .workflows.create(friendly_name='Sales',_163 assignment_callback_url=callback_url,_163 fallback_assignment_callback_url=callback_url,_163 task_reservation_timeout=15,_163 configuration=json.dumps(config))
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 attribute and match it 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.
task_router/workspace.py
_163import json_163_163from django.conf import settings_163from twilio.rest import Client_163_163HOST = settings.HOST_163ALICE_NUMBER = settings.ALICE_NUMBER_163BOB_NUMBER = settings.BOB_NUMBER_163WORKSPACE_NAME = 'Twilio Workspace'_163_163_163def first(items):_163 return items[0] if items else None_163_163_163def build_client():_163 account_sid = settings.TWILIO_ACCOUNT_SID_163 auth_token = settings.TWILIO_AUTH_TOKEN_163 return Client(account_sid, auth_token)_163_163_163CACHE = {}_163_163_163def activities_dict(client, workspace_sid):_163 activities = client.taskrouter.workspaces(workspace_sid)\_163 .activities.list()_163_163 return {activity.friendly_name: activity for activity in activities}_163_163_163class WorkspaceInfo:_163_163 def __init__(self, workspace, workflow, activities, workers):_163 self.workflow_sid = workflow.sid_163 self.workspace_sid = workspace.sid_163 self.activities = activities_163 self.post_work_activity_sid = activities['Available'].sid_163 self.workers = workers_163_163_163def setup():_163 client = build_client()_163 if 'WORKSPACE_INFO' not in CACHE:_163 workspace = create_workspace(client)_163 activities = activities_dict(client, workspace.sid)_163 workers = create_workers(client, workspace, activities)_163 queues = create_task_queues(client, workspace, activities)_163 workflow = create_workflow(client, workspace, queues)_163 CACHE['WORKSPACE_INFO'] = WorkspaceInfo(workspace, workflow, activities, workers)_163 return CACHE['WORKSPACE_INFO']_163_163_163def create_workspace(client):_163 try:_163 workspace = first(client.taskrouter.workspaces.list(friendly_name=WORKSPACE_NAME))_163 client.taskrouter.workspaces(workspace.sid).delete()_163 except Exception:_163 pass_163_163 events_callback = HOST + '/events/'_163_163 return client.taskrouter.workspaces.create(_163 friendly_name=WORKSPACE_NAME,_163 event_callback_url=events_callback,_163 template=None)_163_163_163def create_workers(client, workspace, activities):_163 alice_attributes = {_163 "products": ["ProgrammableVoice"],_163 "contact_uri": ALICE_NUMBER_163 }_163_163 alice = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Alice',_163 attributes=json.dumps(alice_attributes))_163_163 bob_attributes = {_163 "products": ["ProgrammableSMS"],_163 "contact_uri": BOB_NUMBER_163 }_163_163 bob = client.taskrouter.workspaces(workspace.sid)\_163 .workers.create(friendly_name='Bob',_163 attributes=json.dumps(bob_attributes))_163_163 return {BOB_NUMBER: bob.sid, ALICE_NUMBER: alice.sid}_163_163_163def create_task_queues(client, workspace, activities):_163 default_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Default',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='1==1')_163_163 sms_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='SMS',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableSMS" in products')_163_163 voice_queue = client.taskrouter.workspaces(workspace.sid).task_queues\_163 .create(friendly_name='Voice',_163 assignment_activity_sid=activities['Unavailable'].sid,_163 target_workers='"ProgrammableVoice" in products')_163_163 return {'sms': sms_queue, 'voice': voice_queue, 'default': default_queue}_163_163_163def create_workflow(client, workspace, queues):_163 defaultTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 smsTarget = {_163 'queue': queues['sms'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 voiceTarget = {_163 'queue': queues['voice'].sid,_163 'priority': 5,_163 'timeout': 30_163 }_163_163 default_filter = {_163 'filter_friendly_name': 'Default Filter',_163 'queue': queues['default'].sid,_163 'Expression': '1==1',_163 'priority': 1,_163 'timeout': 30_163 }_163_163 voiceFilter = {_163 'filter_friendly_name': 'Voice Filter',_163 'expression': 'selected_product=="ProgrammableVoice"',_163 'targets': [voiceTarget, defaultTarget]_163 }_163_163 smsFilter = {_163 'filter_friendly_name': 'SMS Filter',_163 'expression': 'selected_product=="ProgrammableSMS"',_163 'targets': [smsTarget, defaultTarget]_163 }_163_163 config = {_163 'task_routing': {_163 'filters': [voiceFilter, smsFilter],_163 'default_filter': default_filter_163 }_163 }_163_163 callback_url = HOST + '/assignment/'_163_163 return client.taskrouter.workspaces(workspace.sid)\_163 .workflows.create(friendly_name='Sales',_163 assignment_callback_url=callback_url,_163 fallback_assignment_callback_url=callback_url,_163 task_reservation_timeout=15,_163 configuration=json.dumps(config))
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.
task_router/views.py
_128import json_128from urllib.parse import quote_plus_128_128from django.conf import settings_128from django.http import HttpResponse, JsonResponse_128from django.shortcuts import render_128from django.urls import reverse_128from django.views.decorators.csrf import csrf_exempt_128from twilio.rest import Client_128from twilio.twiml.messaging_response import MessagingResponse_128from twilio.twiml.voice_response import VoiceResponse_128_128from . import sms_sender, workspace_128from .models import MissedCall_128_128if not getattr(settings, 'TESTING', False):_128 WORKSPACE_INFO = workspace.setup()_128else:_128 WORKSPACE_INFO = None_128_128ACCOUNT_SID = settings.TWILIO_ACCOUNT_SID_128AUTH_TOKEN = settings.TWILIO_AUTH_TOKEN_128TWILIO_NUMBER = settings.TWILIO_NUMBER_128EMAIL = settings.MISSED_CALLS_EMAIL_ADDRESS_128_128_128def root(request):_128 """ Renders a missed calls list, with product and phone number """_128 missed_calls = MissedCall.objects.order_by('-created')_128 return render(request, 'index.html', {_128 'missed_calls': missed_calls_128 })_128_128_128@csrf_exempt_128def incoming_sms(request):_128 """ Changes worker activity and returns a confirmation """_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 activity = 'Available' if request.POST['Body'].lower().strip() == 'on' else 'Offline'_128 activity_sid = WORKSPACE_INFO.activities[activity].sid_128 worker_sid = WORKSPACE_INFO.workers[request.POST['From']]_128 workspace_sid = WORKSPACE_INFO.workspace_sid_128_128 client.workspaces(workspace_sid)\_128 .workers(worker_sid)\_128 .update(activity_sid=activity_sid)_128_128 resp = MessagingResponse()_128 message = 'Your status has changed to ' + activity_128 resp.message(message)_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def incoming_call(request):_128 """ Returns TwiML instructions to Twilio's POST requests """_128 resp = VoiceResponse()_128 gather = resp.gather(numDigits=1, action=reverse('enqueue'), method="POST")_128 gather.say("For Programmable SMS, press one. For Voice, press any other key.")_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def enqueue(request):_128 """ Parses a selected product, creating a Task on Task Router Workflow """_128 resp = VoiceResponse()_128 digits = request.POST['Digits']_128 selected_product = 'ProgrammableSMS' if digits == '1' else 'ProgrammableVoice'_128 task = {'selected_product': selected_product}_128_128 enqueue = resp.enqueue(None, workflowSid=WORKSPACE_INFO.workflow_sid)_128 enqueue.task(json.dumps(task))_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def assignment(request):_128 """ Task assignment """_128 response = {'instruction': 'dequeue',_128 'post_work_activity_sid': WORKSPACE_INFO.post_work_activity_sid}_128 return JsonResponse(response)_128_128_128@csrf_exempt_128def events(request):_128 """ Events callback for missed calls """_128 POST = request.POST_128 event_type = POST.get('EventType')_128 task_events = ['workflow.timeout', 'task.canceled']_128 worker_event = 'worker.activity.update'_128_128 if event_type in task_events:_128 task_attributes = json.loads(POST['TaskAttributes'])_128 _save_missed_call(task_attributes)_128 if event_type == 'workflow.timeout':_128 _voicemail(task_attributes['call_sid'])_128 elif event_type == worker_event and POST['WorkerActivityName'] == 'Offline':_128 message = 'Your status has changed to Offline. Reply with '\_128 '"On" to get back Online'_128 worker_number = json.loads(POST['WorkerAttributes'])['contact_uri']_128 sms_sender.send(to=worker_number, from_=TWILIO_NUMBER, body=message)_128_128 return HttpResponse('')_128_128_128def _voicemail(call_sid):_128 msg = 'Sorry, All agents are busy. Please leave a message. We will call you as soon as possible'_128 route_url = 'http://twimlets.com/voicemail?Email=' + EMAIL + '&Message=' + quote_plus(msg)_128 route_call(call_sid, route_url)_128_128_128def route_call(call_sid, route_url):_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 client.api.calls(call_sid).update(url=route_url)_128_128_128def _save_missed_call(task_attributes):_128 MissedCall.objects.create(_128 phone_number=task_attributes['from'],_128 selected_product=task_attributes['selected_product'])_128_128_128# Only used by the tests in order to patch requests before any call is made_128def setup_workspace():_128 global WORKSPACE_INFO_128 WORKSPACE_INFO = workspace.setup()
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 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.
task_router/views.py
_128import json_128from urllib.parse import quote_plus_128_128from django.conf import settings_128from django.http import HttpResponse, JsonResponse_128from django.shortcuts import render_128from django.urls import reverse_128from django.views.decorators.csrf import csrf_exempt_128from twilio.rest import Client_128from twilio.twiml.messaging_response import MessagingResponse_128from twilio.twiml.voice_response import VoiceResponse_128_128from . import sms_sender, workspace_128from .models import MissedCall_128_128if not getattr(settings, 'TESTING', False):_128 WORKSPACE_INFO = workspace.setup()_128else:_128 WORKSPACE_INFO = None_128_128ACCOUNT_SID = settings.TWILIO_ACCOUNT_SID_128AUTH_TOKEN = settings.TWILIO_AUTH_TOKEN_128TWILIO_NUMBER = settings.TWILIO_NUMBER_128EMAIL = settings.MISSED_CALLS_EMAIL_ADDRESS_128_128_128def root(request):_128 """ Renders a missed calls list, with product and phone number """_128 missed_calls = MissedCall.objects.order_by('-created')_128 return render(request, 'index.html', {_128 'missed_calls': missed_calls_128 })_128_128_128@csrf_exempt_128def incoming_sms(request):_128 """ Changes worker activity and returns a confirmation """_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 activity = 'Available' if request.POST['Body'].lower().strip() == 'on' else 'Offline'_128 activity_sid = WORKSPACE_INFO.activities[activity].sid_128 worker_sid = WORKSPACE_INFO.workers[request.POST['From']]_128 workspace_sid = WORKSPACE_INFO.workspace_sid_128_128 client.workspaces(workspace_sid)\_128 .workers(worker_sid)\_128 .update(activity_sid=activity_sid)_128_128 resp = MessagingResponse()_128 message = 'Your status has changed to ' + activity_128 resp.message(message)_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def incoming_call(request):_128 """ Returns TwiML instructions to Twilio's POST requests """_128 resp = VoiceResponse()_128 gather = resp.gather(numDigits=1, action=reverse('enqueue'), method="POST")_128 gather.say("For Programmable SMS, press one. For Voice, press any other key.")_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def enqueue(request):_128 """ Parses a selected product, creating a Task on Task Router Workflow """_128 resp = VoiceResponse()_128 digits = request.POST['Digits']_128 selected_product = 'ProgrammableSMS' if digits == '1' else 'ProgrammableVoice'_128 task = {'selected_product': selected_product}_128_128 enqueue = resp.enqueue(None, workflowSid=WORKSPACE_INFO.workflow_sid)_128 enqueue.task(json.dumps(task))_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def assignment(request):_128 """ Task assignment """_128 response = {'instruction': 'dequeue',_128 'post_work_activity_sid': WORKSPACE_INFO.post_work_activity_sid}_128 return JsonResponse(response)_128_128_128@csrf_exempt_128def events(request):_128 """ Events callback for missed calls """_128 POST = request.POST_128 event_type = POST.get('EventType')_128 task_events = ['workflow.timeout', 'task.canceled']_128 worker_event = 'worker.activity.update'_128_128 if event_type in task_events:_128 task_attributes = json.loads(POST['TaskAttributes'])_128 _save_missed_call(task_attributes)_128 if event_type == 'workflow.timeout':_128 _voicemail(task_attributes['call_sid'])_128 elif event_type == worker_event and POST['WorkerActivityName'] == 'Offline':_128 message = 'Your status has changed to Offline. Reply with '\_128 '"On" to get back Online'_128 worker_number = json.loads(POST['WorkerAttributes'])['contact_uri']_128 sms_sender.send(to=worker_number, from_=TWILIO_NUMBER, body=message)_128_128 return HttpResponse('')_128_128_128def _voicemail(call_sid):_128 msg = 'Sorry, All agents are busy. Please leave a message. We will call you as soon as possible'_128 route_url = 'http://twimlets.com/voicemail?Email=' + EMAIL + '&Message=' + quote_plus(msg)_128 route_call(call_sid, route_url)_128_128_128def route_call(call_sid, route_url):_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 client.api.calls(call_sid).update(url=route_url)_128_128_128def _save_missed_call(task_attributes):_128 MissedCall.objects.create(_128 phone_number=task_attributes['from'],_128 selected_product=task_attributes['selected_product'])_128_128_128# Only used by the tests in order to patch requests before any call is made_128def setup_workspace():_128 global WORKSPACE_INFO_128 WORKSPACE_INFO = workspace.setup()
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 while creating the Workflow. 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.
task_router/views.py
_128import json_128from urllib.parse import quote_plus_128_128from django.conf import settings_128from django.http import HttpResponse, JsonResponse_128from django.shortcuts import render_128from django.urls import reverse_128from django.views.decorators.csrf import csrf_exempt_128from twilio.rest import Client_128from twilio.twiml.messaging_response import MessagingResponse_128from twilio.twiml.voice_response import VoiceResponse_128_128from . import sms_sender, workspace_128from .models import MissedCall_128_128if not getattr(settings, 'TESTING', False):_128 WORKSPACE_INFO = workspace.setup()_128else:_128 WORKSPACE_INFO = None_128_128ACCOUNT_SID = settings.TWILIO_ACCOUNT_SID_128AUTH_TOKEN = settings.TWILIO_AUTH_TOKEN_128TWILIO_NUMBER = settings.TWILIO_NUMBER_128EMAIL = settings.MISSED_CALLS_EMAIL_ADDRESS_128_128_128def root(request):_128 """ Renders a missed calls list, with product and phone number """_128 missed_calls = MissedCall.objects.order_by('-created')_128 return render(request, 'index.html', {_128 'missed_calls': missed_calls_128 })_128_128_128@csrf_exempt_128def incoming_sms(request):_128 """ Changes worker activity and returns a confirmation """_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 activity = 'Available' if request.POST['Body'].lower().strip() == 'on' else 'Offline'_128 activity_sid = WORKSPACE_INFO.activities[activity].sid_128 worker_sid = WORKSPACE_INFO.workers[request.POST['From']]_128 workspace_sid = WORKSPACE_INFO.workspace_sid_128_128 client.workspaces(workspace_sid)\_128 .workers(worker_sid)\_128 .update(activity_sid=activity_sid)_128_128 resp = MessagingResponse()_128 message = 'Your status has changed to ' + activity_128 resp.message(message)_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def incoming_call(request):_128 """ Returns TwiML instructions to Twilio's POST requests """_128 resp = VoiceResponse()_128 gather = resp.gather(numDigits=1, action=reverse('enqueue'), method="POST")_128 gather.say("For Programmable SMS, press one. For Voice, press any other key.")_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def enqueue(request):_128 """ Parses a selected product, creating a Task on Task Router Workflow """_128 resp = VoiceResponse()_128 digits = request.POST['Digits']_128 selected_product = 'ProgrammableSMS' if digits == '1' else 'ProgrammableVoice'_128 task = {'selected_product': selected_product}_128_128 enqueue = resp.enqueue(None, workflowSid=WORKSPACE_INFO.workflow_sid)_128 enqueue.task(json.dumps(task))_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def assignment(request):_128 """ Task assignment """_128 response = {'instruction': 'dequeue',_128 'post_work_activity_sid': WORKSPACE_INFO.post_work_activity_sid}_128 return JsonResponse(response)_128_128_128@csrf_exempt_128def events(request):_128 """ Events callback for missed calls """_128 POST = request.POST_128 event_type = POST.get('EventType')_128 task_events = ['workflow.timeout', 'task.canceled']_128 worker_event = 'worker.activity.update'_128_128 if event_type in task_events:_128 task_attributes = json.loads(POST['TaskAttributes'])_128 _save_missed_call(task_attributes)_128 if event_type == 'workflow.timeout':_128 _voicemail(task_attributes['call_sid'])_128 elif event_type == worker_event and POST['WorkerActivityName'] == 'Offline':_128 message = 'Your status has changed to Offline. Reply with '\_128 '"On" to get back Online'_128 worker_number = json.loads(POST['WorkerAttributes'])['contact_uri']_128 sms_sender.send(to=worker_number, from_=TWILIO_NUMBER, body=message)_128_128 return HttpResponse('')_128_128_128def _voicemail(call_sid):_128 msg = 'Sorry, All agents are busy. Please leave a message. We will call you as soon as possible'_128 route_url = 'http://twimlets.com/voicemail?Email=' + EMAIL + '&Message=' + quote_plus(msg)_128 route_call(call_sid, route_url)_128_128_128def route_call(call_sid, route_url):_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 client.api.calls(call_sid).update(url=route_url)_128_128_128def _save_missed_call(task_attributes):_128 MissedCall.objects.create(_128 phone_number=task_attributes['from'],_128 selected_product=task_attributes['selected_product'])_128_128_128# Only used by the tests in order to patch requests before any call is made_128def setup_workspace():_128 global WORKSPACE_INFO_128 WORKSPACE_INFO = workspace.setup()
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.
task_router/views.py
_128import json_128from urllib.parse import quote_plus_128_128from django.conf import settings_128from django.http import HttpResponse, JsonResponse_128from django.shortcuts import render_128from django.urls import reverse_128from django.views.decorators.csrf import csrf_exempt_128from twilio.rest import Client_128from twilio.twiml.messaging_response import MessagingResponse_128from twilio.twiml.voice_response import VoiceResponse_128_128from . import sms_sender, workspace_128from .models import MissedCall_128_128if not getattr(settings, 'TESTING', False):_128 WORKSPACE_INFO = workspace.setup()_128else:_128 WORKSPACE_INFO = None_128_128ACCOUNT_SID = settings.TWILIO_ACCOUNT_SID_128AUTH_TOKEN = settings.TWILIO_AUTH_TOKEN_128TWILIO_NUMBER = settings.TWILIO_NUMBER_128EMAIL = settings.MISSED_CALLS_EMAIL_ADDRESS_128_128_128def root(request):_128 """ Renders a missed calls list, with product and phone number """_128 missed_calls = MissedCall.objects.order_by('-created')_128 return render(request, 'index.html', {_128 'missed_calls': missed_calls_128 })_128_128_128@csrf_exempt_128def incoming_sms(request):_128 """ Changes worker activity and returns a confirmation """_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 activity = 'Available' if request.POST['Body'].lower().strip() == 'on' else 'Offline'_128 activity_sid = WORKSPACE_INFO.activities[activity].sid_128 worker_sid = WORKSPACE_INFO.workers[request.POST['From']]_128 workspace_sid = WORKSPACE_INFO.workspace_sid_128_128 client.workspaces(workspace_sid)\_128 .workers(worker_sid)\_128 .update(activity_sid=activity_sid)_128_128 resp = MessagingResponse()_128 message = 'Your status has changed to ' + activity_128 resp.message(message)_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def incoming_call(request):_128 """ Returns TwiML instructions to Twilio's POST requests """_128 resp = VoiceResponse()_128 gather = resp.gather(numDigits=1, action=reverse('enqueue'), method="POST")_128 gather.say("For Programmable SMS, press one. For Voice, press any other key.")_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def enqueue(request):_128 """ Parses a selected product, creating a Task on Task Router Workflow """_128 resp = VoiceResponse()_128 digits = request.POST['Digits']_128 selected_product = 'ProgrammableSMS' if digits == '1' else 'ProgrammableVoice'_128 task = {'selected_product': selected_product}_128_128 enqueue = resp.enqueue(None, workflowSid=WORKSPACE_INFO.workflow_sid)_128 enqueue.task(json.dumps(task))_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def assignment(request):_128 """ Task assignment """_128 response = {'instruction': 'dequeue',_128 'post_work_activity_sid': WORKSPACE_INFO.post_work_activity_sid}_128 return JsonResponse(response)_128_128_128@csrf_exempt_128def events(request):_128 """ Events callback for missed calls """_128 POST = request.POST_128 event_type = POST.get('EventType')_128 task_events = ['workflow.timeout', 'task.canceled']_128 worker_event = 'worker.activity.update'_128_128 if event_type in task_events:_128 task_attributes = json.loads(POST['TaskAttributes'])_128 _save_missed_call(task_attributes)_128 if event_type == 'workflow.timeout':_128 _voicemail(task_attributes['call_sid'])_128 elif event_type == worker_event and POST['WorkerActivityName'] == 'Offline':_128 message = 'Your status has changed to Offline. Reply with '\_128 '"On" to get back Online'_128 worker_number = json.loads(POST['WorkerAttributes'])['contact_uri']_128 sms_sender.send(to=worker_number, from_=TWILIO_NUMBER, body=message)_128_128 return HttpResponse('')_128_128_128def _voicemail(call_sid):_128 msg = 'Sorry, All agents are busy. Please leave a message. We will call you as soon as possible'_128 route_url = 'http://twimlets.com/voicemail?Email=' + EMAIL + '&Message=' + quote_plus(msg)_128 route_call(call_sid, route_url)_128_128_128def route_call(call_sid, route_url):_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 client.api.calls(call_sid).update(url=route_url)_128_128_128def _save_missed_call(task_attributes):_128 MissedCall.objects.create(_128 phone_number=task_attributes['from'],_128 selected_product=task_attributes['selected_product'])_128_128_128# Only used by the tests in order to patch requests before any call is made_128def setup_workspace():_128 global WORKSPACE_INFO_128 WORKSPACE_INFO = workspace.setup()
Most of the features of our application are implemented. The last piece is allowing the Workers to change their availability status. Let's see how to do that next.
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.
task_router/views.py
_128import json_128from urllib.parse import quote_plus_128_128from django.conf import settings_128from django.http import HttpResponse, JsonResponse_128from django.shortcuts import render_128from django.urls import reverse_128from django.views.decorators.csrf import csrf_exempt_128from twilio.rest import Client_128from twilio.twiml.messaging_response import MessagingResponse_128from twilio.twiml.voice_response import VoiceResponse_128_128from . import sms_sender, workspace_128from .models import MissedCall_128_128if not getattr(settings, 'TESTING', False):_128 WORKSPACE_INFO = workspace.setup()_128else:_128 WORKSPACE_INFO = None_128_128ACCOUNT_SID = settings.TWILIO_ACCOUNT_SID_128AUTH_TOKEN = settings.TWILIO_AUTH_TOKEN_128TWILIO_NUMBER = settings.TWILIO_NUMBER_128EMAIL = settings.MISSED_CALLS_EMAIL_ADDRESS_128_128_128def root(request):_128 """ Renders a missed calls list, with product and phone number """_128 missed_calls = MissedCall.objects.order_by('-created')_128 return render(request, 'index.html', {_128 'missed_calls': missed_calls_128 })_128_128_128@csrf_exempt_128def incoming_sms(request):_128 """ Changes worker activity and returns a confirmation """_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 activity = 'Available' if request.POST['Body'].lower().strip() == 'on' else 'Offline'_128 activity_sid = WORKSPACE_INFO.activities[activity].sid_128 worker_sid = WORKSPACE_INFO.workers[request.POST['From']]_128 workspace_sid = WORKSPACE_INFO.workspace_sid_128_128 client.workspaces(workspace_sid)\_128 .workers(worker_sid)\_128 .update(activity_sid=activity_sid)_128_128 resp = MessagingResponse()_128 message = 'Your status has changed to ' + activity_128 resp.message(message)_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def incoming_call(request):_128 """ Returns TwiML instructions to Twilio's POST requests """_128 resp = VoiceResponse()_128 gather = resp.gather(numDigits=1, action=reverse('enqueue'), method="POST")_128 gather.say("For Programmable SMS, press one. For Voice, press any other key.")_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def enqueue(request):_128 """ Parses a selected product, creating a Task on Task Router Workflow """_128 resp = VoiceResponse()_128 digits = request.POST['Digits']_128 selected_product = 'ProgrammableSMS' if digits == '1' else 'ProgrammableVoice'_128 task = {'selected_product': selected_product}_128_128 enqueue = resp.enqueue(None, workflowSid=WORKSPACE_INFO.workflow_sid)_128 enqueue.task(json.dumps(task))_128_128 return HttpResponse(resp)_128_128_128@csrf_exempt_128def assignment(request):_128 """ Task assignment """_128 response = {'instruction': 'dequeue',_128 'post_work_activity_sid': WORKSPACE_INFO.post_work_activity_sid}_128 return JsonResponse(response)_128_128_128@csrf_exempt_128def events(request):_128 """ Events callback for missed calls """_128 POST = request.POST_128 event_type = POST.get('EventType')_128 task_events = ['workflow.timeout', 'task.canceled']_128 worker_event = 'worker.activity.update'_128_128 if event_type in task_events:_128 task_attributes = json.loads(POST['TaskAttributes'])_128 _save_missed_call(task_attributes)_128 if event_type == 'workflow.timeout':_128 _voicemail(task_attributes['call_sid'])_128 elif event_type == worker_event and POST['WorkerActivityName'] == 'Offline':_128 message = 'Your status has changed to Offline. Reply with '\_128 '"On" to get back Online'_128 worker_number = json.loads(POST['WorkerAttributes'])['contact_uri']_128 sms_sender.send(to=worker_number, from_=TWILIO_NUMBER, body=message)_128_128 return HttpResponse('')_128_128_128def _voicemail(call_sid):_128 msg = 'Sorry, All agents are busy. Please leave a message. We will call you as soon as possible'_128 route_url = 'http://twimlets.com/voicemail?Email=' + EMAIL + '&Message=' + quote_plus(msg)_128 route_call(call_sid, route_url)_128_128_128def route_call(call_sid, route_url):_128 client = Client(ACCOUNT_SID, AUTH_TOKEN)_128 client.api.calls(call_sid).update(url=route_url)_128_128_128def _save_missed_call(task_attributes):_128 MissedCall.objects.create(_128 phone_number=task_attributes['from'],_128 selected_product=task_attributes['selected_product'])_128_128_128# Only used by the tests in order to patch requests before any call is made_128def setup_workspace():_128 global WORKSPACE_INFO_128 WORKSPACE_INFO = workspace.setup()
Congratulations! You finished this tutorial. As you can see, using Twilio's TaskRouter is quite simple.
If you're a Python developer working with Twilio, you might enjoy these other tutorials:
Automate the process of reaching out to your customers prior to an upcoming appointment.
Instantly collect structured data from your users with a survey conducted over a call or SMS text messages.
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!