In this tutorial we will show how to automate the routing of calls from customers to your support agents. Customers will be able to select a product and wait while TaskRouter tries to contact a product specialist for the best support experience. If no one is available, our application will save the customer's number and selected product so an agent can call them back later on.
This is what the application does at a high level:
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.
A Workspace is the container element for any TaskRouter application. The elements are:
resources/workspace.json
_61{_61 "name": "Twilio Workspace",_61 "event_callback": "%(host)s/events",_61 "workers": [_61 {_61 "name": "Bob",_61 "attributes": {_61 "products": [_61 "ProgrammableSMS"_61 ],_61 "contact_uri": "%(bob_phone)s"_61 }_61 },_61 {_61 "name": "Alice",_61 "attributes": {_61 "products": [_61 "ProgrammableVoice"_61 ],_61 "contact_uri": "%(alice_phone)s"_61 }_61 }_61 ],_61 "activities": [_61 {_61 "name": "Offline",_61 "availability": "false"_61 },_61 {_61 "name": "Idle",_61 "availability": "true"_61 },_61 {_61 "name": "Busy",_61 "availability": "false"_61 },_61 {_61 "name": "Reserved",_61 "availability": "false"_61 }_61 ],_61 "task_queues": [_61 {_61 "name": "Default",_61 "targetWorkers": "1==1"_61 },_61 {_61 "name": "SMS",_61 "targetWorkers": "products HAS \"ProgrammableSMS\""_61 },_61 {_61 "name": "Voice",_61 "targetWorkers": "products HAS \"ProgrammableVoice\""_61 }_61 ],_61 "workflow": {_61 "name": "Sales",_61 "callback": "%(host)s/assignment",_61 "timeout": "15"_61 }_61}
In order to build a client for this API, we need as system variables a TWILIO_ACCOUNT_SID
and TWILIO_AUTH_TOKEN
which you can find on Twilio Console. The artisan command workspace:create
creates a Twilio\Rest\Client
, which is provided by the Twilio PHP library. This client is used by WorkspaceFacade which encapsulates all logic related to the creation of a Task Router workflow.
Let's take a look at that command next.
In this application the Artisan command workspace:create
is used to orchestrate calls to our WorkspaceFacade
class in order to build a Workspace from scratch. CreateWorkspace
is the PHP class behind the Artisan command. It uses data provided by workspace.json
and expects 3 arguments:
host
- A public URL to which Twilio can send requests. This can be either a cloud service or
ngrok
, which can expose a local application to the internet.
bob_phone
- The telephone number of Bob, the Programmable SMS specialist.
alice_phone
- Same for Alice, the Programmable Voice specialist.
The function createWorkspaceConfig
is used to load the configuration of the workspace from workspace.json
.
app/Console/Commands/CreateWorkspace.php
_219<?php_219_219namespace App\Console\Commands;_219_219use App\TaskRouter\WorkspaceFacade;_219use Illuminate\Console\Command;_219use Illuminate\Support\Facades\File;_219use TaskRouter_Services_Twilio;_219use Twilio\Rest\Client;_219use Twilio\Rest\Taskrouter;_219use WorkflowRuleTarget;_219_219class CreateWorkspace extends Command_219{_219 /**_219 * The name and signature of the console command._219 *_219 * @var string_219 */_219 protected $signature = 'workspace:create_219 {host : Server hostname in Internet}_219 {bob_phone : Phone of the first agent (Bob)}_219 {alice_phone : Phone of the secondary agent (Alice)}';_219_219 /**_219 * The console command description._219 *_219 * @var string_219 */_219 protected $description = 'Creates a Twilio workspace for 2 call agents';_219_219 private $_twilioClient;_219_219 /**_219 * Create a new command instance._219 */_219 public function __construct(Client $twilioClient)_219 {_219 parent::__construct();_219 $this->_twilioClient = $twilioClient;_219 }_219_219 /**_219 * Execute the console command._219 *_219 * @return mixed_219 */_219 public function handle()_219 {_219 $this->info("Create workspace.");_219 $this->line("- Server: \t{$this->argument('host')}");_219 $this->line("- Bob phone: \t{$this->argument('bob_phone')}");_219 $this->line("- Alice phone: \t{$this->argument('alice_phone')}");_219_219 //Get the configuration_219 $workspaceConfig = $this->createWorkspaceConfig();_219_219 //Create the workspace_219 $params = array();_219 $params['friendlyName'] = $workspaceConfig->name;_219 $params['eventCallbackUrl'] = $workspaceConfig->event_callback;_219 $workspace = WorkspaceFacade::createNewWorkspace(_219 $this->_twilioClient->taskrouter,_219 $params_219 );_219 $this->addWorkersToWorkspace($workspace, $workspaceConfig);_219 $this->addTaskQueuesToWorkspace($workspace, $workspaceConfig);_219 $workflow = $this->addWorkflowToWorkspace($workspace, $workspaceConfig);_219_219 $this->printSuccessAndInstructions($workspace, $workflow);_219 }_219_219 /**_219 * Get the json configuration of the Workspace_219 *_219 * @return mixed_219 */_219 function createWorkspaceConfig()_219 {_219 $fileContent = File::get("resources/workspace.json");_219 $interpolatedContent = sprintfn($fileContent, $this->argument());_219 return json_decode($interpolatedContent);_219 }_219_219 /**_219 * Add workers to workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addWorkersToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Workers.");_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("The activity 'Idle' was not found. Workers cannot be added.");_219 foreach ($workspaceConfig->workers as $workerJson) {_219 $params = array();_219 $params['friendlyName'] = $workerJson->name;_219 $params['activitySid'] = $idleActivity->sid;_219 $params['attributes'] = json_encode($workerJson->attributes);_219 $workspace->addWorker($params);_219 }_219 }_219_219 /**_219 * Add the Task Queues to the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addTaskQueuesToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Task Queues.");_219 $reservedActivity = $workspace->findActivityByName("Reserved");_219 $assignmentActivity = $workspace->findActivityByName("Busy");_219 foreach ($workspaceConfig->task_queues as $taskQueueJson) {_219 $params = array();_219 $params['friendlyName'] = $taskQueueJson->name;_219 $params['targetWorkers'] = $taskQueueJson->targetWorkers;_219 $params['reservationActivitySid'] = $reservedActivity->sid;_219 $params['assignmentActivitySid'] = $assignmentActivity->sid;_219 $workspace->addTaskQueue($params);_219 }_219 }_219_219 /**_219 * Create and configure the workflow to use in the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 *_219 * @return object with added workflow_219 */_219 function addWorkflowToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Worflow.");_219 $workflowJson = $workspaceConfig->workflow;_219 $params = array();_219 $params['friendlyName'] = $workflowJson->name;_219 $params['assignmentCallbackUrl'] = $workflowJson->callback;_219 $params['taskReservationTimeout'] = $workflowJson->timeout;_219 $params['configuration'] = $this->createWorkFlowJsonConfig(_219 $workspace,_219 $workflowJson_219 );_219 return $workspace->addWorkflow($params);_219 }_219_219 /**_219 * Create the workflow configuration in json format_219 *_219 * @param $workspace_219 * @param $workspaceConfig_219 *_219 * @return string configuration of workflow in json format_219 */_219 function createWorkFlowJsonConfig($workspace, $workspaceConfig)_219 {_219 $params = array();_219 $defaultTaskQueue = $workspace->findTaskQueueByName("Default") or die(_219 "The 'Default' task queue was not found. The Workflow cannot be created."_219 );_219 $smsTaskQueue = $workspace->findTaskQueueByName("SMS") or die(_219 "The 'SMS' task queue was not found. The Workflow cannot be created."_219 );_219 $voiceTaskQueue = $workspace->findTaskQueueByName("Voice") or die(_219 "The 'Voice' task queue was not found. The Workflow cannot be created."_219 );_219_219 $params["default_task_queue_sid"] = $defaultTaskQueue->sid;_219 $params["sms_task_queue_sid"] = $smsTaskQueue->sid;_219 $params["voice_task_queue_sid"] = $voiceTaskQueue->sid;_219_219 $fileContent = File::get("resources/workflow.json");_219 $interpolatedContent = sprintfn($fileContent, $params);_219 return $interpolatedContent;_219 }_219_219 /**_219 * Prints the message indicating the workspace was successfully created and_219 * shows the commands to export the workspace variables into the environment._219 *_219 * @param $workspace_219 * @param $workflow_219 */_219 function printSuccessAndInstructions($workspace, $workflow)_219 {_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("Somehow the activity 'Idle' was not found.");_219 $successMsg = "Workspace \"{$workspace->friendlyName}\"" ._219 " was created successfully.";_219 $this->printTitle($successMsg);_219 $this->line(_219 "The following variables will be set automatically."_219 );_219 $encondedWorkersPhone = http_build_query($workspace->getWorkerPhones());_219 $envVars = [_219 "WORKFLOW_SID" => $workflow->sid,_219 "POST_WORK_ACTIVITY_SID" => $idleActivity->sid,_219 "WORKSPACE_SID" => $workspace->sid,_219 "PHONE_TO_WORKER" => $encondedWorkersPhone_219 ];_219 updateEnv($envVars);_219 foreach ($envVars as $key => $value) {_219 $this->warn("export $key=$value");_219 }_219 }_219_219 /**_219 * Prints a text separated up and doNwn by a token based line, usually "*"_219 */_219 function printTitle($text)_219 {_219 $lineLength = strlen($text) + 2;_219 $this->line(str_repeat("*", $lineLength));_219 $this->line(" $text ");_219 $this->line(str_repeat("*", $lineLength));_219 }_219}
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 friendlyName
as identifier. In order to create a workspace we need to provide a friendlyName
, and a eventCallbackUrl
which contains an url to be called every time an event is triggered in the workspace.
app/TaskRouter/WorkspaceFacade.php
_187<?php_187_187namespace App\TaskRouter;_187_187use Twilio\Rest\Taskrouter\V1\Workspace;_187_187_187class WorkspaceFacade_187{_187 private $_taskRouterClient;_187_187 private $_workspace;_187_187 private $_activities;_187_187 public function __construct($taskRouterClient, $workspace)_187 {_187 $this->_taskRouterClient = $taskRouterClient;_187 $this->_workspace = $workspace;_187 }_187_187 public static function createNewWorkspace($taskRouterClient, $params)_187 {_187 $workspaceName = $params["friendlyName"];_187 $existingWorkspace = $taskRouterClient->workspaces->read(_187 array(_187 "friendlyName" => $workspaceName_187 )_187 );_187 if ($existingWorkspace) {_187 $existingWorkspace[0]->delete();_187 }_187_187 $workspace = $taskRouterClient->workspaces_187 ->create($workspaceName, $params);_187 return new WorkspaceFacade($taskRouterClient, $workspace);_187 }_187_187 public static function createBySid($taskRouterClient, $workspaceSid)_187 {_187 $workspace = $taskRouterClient->workspaces($workspaceSid);_187 return new WorkspaceFacade($taskRouterClient, $workspace);_187 }_187_187 /**_187 * Magic getter to lazy load subresources_187 *_187 * @param string $property Subresource to return_187 *_187 * @return \Twilio\ListResource The requested subresource_187 *_187 * @throws \Twilio\Exceptions\TwilioException For unknown subresources_187 */_187 public function __get($property)_187 {_187 return $this->_workspace->$property;_187 }_187_187 /**_187 * Gets an activity instance by its friendly name_187 *_187 * @param $activityName Friendly name of the activity to search for_187 *_187 * @return ActivityInstance of the activity found or null_187 */_187 function findActivityByName($activityName)_187 {_187 $this->cacheActivitiesByName();_187 return $this->_activities[$activityName];_187 }_187_187 /**_187 * Caches the activities in an associative array which links friendlyName with_187 * its ActivityInstance_187 */_187 protected function cacheActivitiesByName()_187 {_187 if (!$this->_activities) {_187 $this->_activities = array();_187 foreach ($this->_workspace->activities->read() as $activity) {_187 $this->_activities[$activity->friendlyName] = $activity;_187 }_187 }_187 }_187_187 /**_187 * Looks for a worker by its SID_187 *_187 * @param $sid string with the Worker SID_187 *_187 * @return mixed worker found or null_187 */_187 function findWorkerBySid($sid)_187 {_187 return $this->_workspace->workers($sid);_187 }_187_187 /**_187 * Returns an associative array with_187 *_187 * @return mixed array with the relation phone -> workerSid_187 */_187 function getWorkerPhones()_187 {_187 $worker_phones = array();_187 foreach ($this->_workspace->workers->read() as $worker) {_187 $workerAttribs = json_decode($worker->attributes);_187 $worker_phones[$workerAttribs->contact_uri] = $worker->sid;_187 }_187 return $worker_phones;_187 }_187_187 /**_187 * Looks for a Task Queue by its friendly name_187 *_187 * @param $taskQueueName string with the friendly name of the task queue to_187 * search for_187 *_187 * @return the activity found or null_187 */_187 function findTaskQueueByName($taskQueueName)_187 {_187 $result = $this->_workspace->taskQueues->read(_187 array(_187 "friendlyName" => $taskQueueName_187 )_187 );_187 if ($result) {_187 return $result[0];_187 }_187 }_187_187 function updateWorkerActivity($worker, $activitySid)_187 {_187 $worker->update(['activitySid' => $activitySid]);_187 }_187_187 /**_187 * Adds workers to the workspace_187 *_187 * @param $params mixed with the attributes to define the new Worker in the_187 * workspace_187 *_187 * @return Workspace\WorkerInstance|Null_187 */_187 function addWorker($params)_187 {_187 return $this->_workspace->workers->create($params['friendlyName'], $params);_187 }_187_187 /**_187 * Adds a Task Queue to the workspace_187 *_187 * @param $params mixed with attributes to define the new Task Queue in the_187 * workspace_187 *_187 * @return Workspace\TaskQueueInstance|Null_187 */_187 function addTaskQueue($params)_187 {_187 return $this->_workspace->taskQueues->create(_187 $params['friendlyName'],_187 $params['reservationActivitySid'],_187 $params['assignmentActivitySid'],_187 $params_187 );_187 }_187_187_187 /**_187 * Adds a workflow to the workspace_187 *_187 * @param $params mixed with attributes to define the new Workflow in the_187 * workspace_187 *_187 * @return Workspace\WorkflowInstance|Null_187 */_187 function addWorkFlow($params)_187 {_187 return $this->_workspace->workflows->create(_187 $params["friendlyName"],_187 $params["configuration"],_187 $params_187 );_187 }_187_187}
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.
app/Console/Commands/CreateWorkspace.php
_219<?php_219_219namespace App\Console\Commands;_219_219use App\TaskRouter\WorkspaceFacade;_219use Illuminate\Console\Command;_219use Illuminate\Support\Facades\File;_219use TaskRouter_Services_Twilio;_219use Twilio\Rest\Client;_219use Twilio\Rest\Taskrouter;_219use WorkflowRuleTarget;_219_219class CreateWorkspace extends Command_219{_219 /**_219 * The name and signature of the console command._219 *_219 * @var string_219 */_219 protected $signature = 'workspace:create_219 {host : Server hostname in Internet}_219 {bob_phone : Phone of the first agent (Bob)}_219 {alice_phone : Phone of the secondary agent (Alice)}';_219_219 /**_219 * The console command description._219 *_219 * @var string_219 */_219 protected $description = 'Creates a Twilio workspace for 2 call agents';_219_219 private $_twilioClient;_219_219 /**_219 * Create a new command instance._219 */_219 public function __construct(Client $twilioClient)_219 {_219 parent::__construct();_219 $this->_twilioClient = $twilioClient;_219 }_219_219 /**_219 * Execute the console command._219 *_219 * @return mixed_219 */_219 public function handle()_219 {_219 $this->info("Create workspace.");_219 $this->line("- Server: \t{$this->argument('host')}");_219 $this->line("- Bob phone: \t{$this->argument('bob_phone')}");_219 $this->line("- Alice phone: \t{$this->argument('alice_phone')}");_219_219 //Get the configuration_219 $workspaceConfig = $this->createWorkspaceConfig();_219_219 //Create the workspace_219 $params = array();_219 $params['friendlyName'] = $workspaceConfig->name;_219 $params['eventCallbackUrl'] = $workspaceConfig->event_callback;_219 $workspace = WorkspaceFacade::createNewWorkspace(_219 $this->_twilioClient->taskrouter,_219 $params_219 );_219 $this->addWorkersToWorkspace($workspace, $workspaceConfig);_219 $this->addTaskQueuesToWorkspace($workspace, $workspaceConfig);_219 $workflow = $this->addWorkflowToWorkspace($workspace, $workspaceConfig);_219_219 $this->printSuccessAndInstructions($workspace, $workflow);_219 }_219_219 /**_219 * Get the json configuration of the Workspace_219 *_219 * @return mixed_219 */_219 function createWorkspaceConfig()_219 {_219 $fileContent = File::get("resources/workspace.json");_219 $interpolatedContent = sprintfn($fileContent, $this->argument());_219 return json_decode($interpolatedContent);_219 }_219_219 /**_219 * Add workers to workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addWorkersToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Workers.");_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("The activity 'Idle' was not found. Workers cannot be added.");_219 foreach ($workspaceConfig->workers as $workerJson) {_219 $params = array();_219 $params['friendlyName'] = $workerJson->name;_219 $params['activitySid'] = $idleActivity->sid;_219 $params['attributes'] = json_encode($workerJson->attributes);_219 $workspace->addWorker($params);_219 }_219 }_219_219 /**_219 * Add the Task Queues to the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addTaskQueuesToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Task Queues.");_219 $reservedActivity = $workspace->findActivityByName("Reserved");_219 $assignmentActivity = $workspace->findActivityByName("Busy");_219 foreach ($workspaceConfig->task_queues as $taskQueueJson) {_219 $params = array();_219 $params['friendlyName'] = $taskQueueJson->name;_219 $params['targetWorkers'] = $taskQueueJson->targetWorkers;_219 $params['reservationActivitySid'] = $reservedActivity->sid;_219 $params['assignmentActivitySid'] = $assignmentActivity->sid;_219 $workspace->addTaskQueue($params);_219 }_219 }_219_219 /**_219 * Create and configure the workflow to use in the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 *_219 * @return object with added workflow_219 */_219 function addWorkflowToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Worflow.");_219 $workflowJson = $workspaceConfig->workflow;_219 $params = array();_219 $params['friendlyName'] = $workflowJson->name;_219 $params['assignmentCallbackUrl'] = $workflowJson->callback;_219 $params['taskReservationTimeout'] = $workflowJson->timeout;_219 $params['configuration'] = $this->createWorkFlowJsonConfig(_219 $workspace,_219 $workflowJson_219 );_219 return $workspace->addWorkflow($params);_219 }_219_219 /**_219 * Create the workflow configuration in json format_219 *_219 * @param $workspace_219 * @param $workspaceConfig_219 *_219 * @return string configuration of workflow in json format_219 */_219 function createWorkFlowJsonConfig($workspace, $workspaceConfig)_219 {_219 $params = array();_219 $defaultTaskQueue = $workspace->findTaskQueueByName("Default") or die(_219 "The 'Default' task queue was not found. The Workflow cannot be created."_219 );_219 $smsTaskQueue = $workspace->findTaskQueueByName("SMS") or die(_219 "The 'SMS' task queue was not found. The Workflow cannot be created."_219 );_219 $voiceTaskQueue = $workspace->findTaskQueueByName("Voice") or die(_219 "The 'Voice' task queue was not found. The Workflow cannot be created."_219 );_219_219 $params["default_task_queue_sid"] = $defaultTaskQueue->sid;_219 $params["sms_task_queue_sid"] = $smsTaskQueue->sid;_219 $params["voice_task_queue_sid"] = $voiceTaskQueue->sid;_219_219 $fileContent = File::get("resources/workflow.json");_219 $interpolatedContent = sprintfn($fileContent, $params);_219 return $interpolatedContent;_219 }_219_219 /**_219 * Prints the message indicating the workspace was successfully created and_219 * shows the commands to export the workspace variables into the environment._219 *_219 * @param $workspace_219 * @param $workflow_219 */_219 function printSuccessAndInstructions($workspace, $workflow)_219 {_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("Somehow the activity 'Idle' was not found.");_219 $successMsg = "Workspace \"{$workspace->friendlyName}\"" ._219 " was created successfully.";_219 $this->printTitle($successMsg);_219 $this->line(_219 "The following variables will be set automatically."_219 );_219 $encondedWorkersPhone = http_build_query($workspace->getWorkerPhones());_219 $envVars = [_219 "WORKFLOW_SID" => $workflow->sid,_219 "POST_WORK_ACTIVITY_SID" => $idleActivity->sid,_219 "WORKSPACE_SID" => $workspace->sid,_219 "PHONE_TO_WORKER" => $encondedWorkersPhone_219 ];_219 updateEnv($envVars);_219 foreach ($envVars as $key => $value) {_219 $this->warn("export $key=$value");_219 }_219 }_219_219 /**_219 * Prints a text separated up and doNwn by a token based line, usually "*"_219 */_219 function printTitle($text)_219 {_219 $lineLength = strlen($text) + 2;_219 $this->line(str_repeat("*", $lineLength));_219 $this->line(" $text ");_219 $this->line(str_repeat("*", $lineLength));_219 }_219}
After creating our workers, let's set up the Task Queues.
Next, we set up the Task Queues. Each with a name and a targetWorkers
property, which is an expression to match Workers. Our Task Queues are:
SMS
- Will target Workers specialized in Programmable SMS, such as Bob, using the expression
"products HAS \"ProgrammableSMS\""
.
Voice
- Will do the same for Programmable Voice Workers, such as Alice, using the expression
"products HAS \"ProgrammableVoice\""
.
All
- This queue targets all users and can be used when there are no specialist around for the chosen product. We can use the
"1==1"
expression here.
app/Console/Commands/CreateWorkspace.php
_219<?php_219_219namespace App\Console\Commands;_219_219use App\TaskRouter\WorkspaceFacade;_219use Illuminate\Console\Command;_219use Illuminate\Support\Facades\File;_219use TaskRouter_Services_Twilio;_219use Twilio\Rest\Client;_219use Twilio\Rest\Taskrouter;_219use WorkflowRuleTarget;_219_219class CreateWorkspace extends Command_219{_219 /**_219 * The name and signature of the console command._219 *_219 * @var string_219 */_219 protected $signature = 'workspace:create_219 {host : Server hostname in Internet}_219 {bob_phone : Phone of the first agent (Bob)}_219 {alice_phone : Phone of the secondary agent (Alice)}';_219_219 /**_219 * The console command description._219 *_219 * @var string_219 */_219 protected $description = 'Creates a Twilio workspace for 2 call agents';_219_219 private $_twilioClient;_219_219 /**_219 * Create a new command instance._219 */_219 public function __construct(Client $twilioClient)_219 {_219 parent::__construct();_219 $this->_twilioClient = $twilioClient;_219 }_219_219 /**_219 * Execute the console command._219 *_219 * @return mixed_219 */_219 public function handle()_219 {_219 $this->info("Create workspace.");_219 $this->line("- Server: \t{$this->argument('host')}");_219 $this->line("- Bob phone: \t{$this->argument('bob_phone')}");_219 $this->line("- Alice phone: \t{$this->argument('alice_phone')}");_219_219 //Get the configuration_219 $workspaceConfig = $this->createWorkspaceConfig();_219_219 //Create the workspace_219 $params = array();_219 $params['friendlyName'] = $workspaceConfig->name;_219 $params['eventCallbackUrl'] = $workspaceConfig->event_callback;_219 $workspace = WorkspaceFacade::createNewWorkspace(_219 $this->_twilioClient->taskrouter,_219 $params_219 );_219 $this->addWorkersToWorkspace($workspace, $workspaceConfig);_219 $this->addTaskQueuesToWorkspace($workspace, $workspaceConfig);_219 $workflow = $this->addWorkflowToWorkspace($workspace, $workspaceConfig);_219_219 $this->printSuccessAndInstructions($workspace, $workflow);_219 }_219_219 /**_219 * Get the json configuration of the Workspace_219 *_219 * @return mixed_219 */_219 function createWorkspaceConfig()_219 {_219 $fileContent = File::get("resources/workspace.json");_219 $interpolatedContent = sprintfn($fileContent, $this->argument());_219 return json_decode($interpolatedContent);_219 }_219_219 /**_219 * Add workers to workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addWorkersToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Workers.");_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("The activity 'Idle' was not found. Workers cannot be added.");_219 foreach ($workspaceConfig->workers as $workerJson) {_219 $params = array();_219 $params['friendlyName'] = $workerJson->name;_219 $params['activitySid'] = $idleActivity->sid;_219 $params['attributes'] = json_encode($workerJson->attributes);_219 $workspace->addWorker($params);_219 }_219 }_219_219 /**_219 * Add the Task Queues to the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addTaskQueuesToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Task Queues.");_219 $reservedActivity = $workspace->findActivityByName("Reserved");_219 $assignmentActivity = $workspace->findActivityByName("Busy");_219 foreach ($workspaceConfig->task_queues as $taskQueueJson) {_219 $params = array();_219 $params['friendlyName'] = $taskQueueJson->name;_219 $params['targetWorkers'] = $taskQueueJson->targetWorkers;_219 $params['reservationActivitySid'] = $reservedActivity->sid;_219 $params['assignmentActivitySid'] = $assignmentActivity->sid;_219 $workspace->addTaskQueue($params);_219 }_219 }_219_219 /**_219 * Create and configure the workflow to use in the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 *_219 * @return object with added workflow_219 */_219 function addWorkflowToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Worflow.");_219 $workflowJson = $workspaceConfig->workflow;_219 $params = array();_219 $params['friendlyName'] = $workflowJson->name;_219 $params['assignmentCallbackUrl'] = $workflowJson->callback;_219 $params['taskReservationTimeout'] = $workflowJson->timeout;_219 $params['configuration'] = $this->createWorkFlowJsonConfig(_219 $workspace,_219 $workflowJson_219 );_219 return $workspace->addWorkflow($params);_219 }_219_219 /**_219 * Create the workflow configuration in json format_219 *_219 * @param $workspace_219 * @param $workspaceConfig_219 *_219 * @return string configuration of workflow in json format_219 */_219 function createWorkFlowJsonConfig($workspace, $workspaceConfig)_219 {_219 $params = array();_219 $defaultTaskQueue = $workspace->findTaskQueueByName("Default") or die(_219 "The 'Default' task queue was not found. The Workflow cannot be created."_219 );_219 $smsTaskQueue = $workspace->findTaskQueueByName("SMS") or die(_219 "The 'SMS' task queue was not found. The Workflow cannot be created."_219 );_219 $voiceTaskQueue = $workspace->findTaskQueueByName("Voice") or die(_219 "The 'Voice' task queue was not found. The Workflow cannot be created."_219 );_219_219 $params["default_task_queue_sid"] = $defaultTaskQueue->sid;_219 $params["sms_task_queue_sid"] = $smsTaskQueue->sid;_219 $params["voice_task_queue_sid"] = $voiceTaskQueue->sid;_219_219 $fileContent = File::get("resources/workflow.json");_219 $interpolatedContent = sprintfn($fileContent, $params);_219 return $interpolatedContent;_219 }_219_219 /**_219 * Prints the message indicating the workspace was successfully created and_219 * shows the commands to export the workspace variables into the environment._219 *_219 * @param $workspace_219 * @param $workflow_219 */_219 function printSuccessAndInstructions($workspace, $workflow)_219 {_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("Somehow the activity 'Idle' was not found.");_219 $successMsg = "Workspace \"{$workspace->friendlyName}\"" ._219 " was created successfully.";_219 $this->printTitle($successMsg);_219 $this->line(_219 "The following variables will be set automatically."_219 );_219 $encondedWorkersPhone = http_build_query($workspace->getWorkerPhones());_219 $envVars = [_219 "WORKFLOW_SID" => $workflow->sid,_219 "POST_WORK_ACTIVITY_SID" => $idleActivity->sid,_219 "WORKSPACE_SID" => $workspace->sid,_219 "PHONE_TO_WORKER" => $encondedWorkersPhone_219 ];_219 updateEnv($envVars);_219 foreach ($envVars as $key => $value) {_219 $this->warn("export $key=$value");_219 }_219 }_219_219 /**_219 * Prints a text separated up and doNwn by a token based line, usually "*"_219 */_219 function printTitle($text)_219 {_219 $lineLength = strlen($text) + 2;_219 $this->line(str_repeat("*", $lineLength));_219 $this->line(" $text ");_219 $this->line(str_repeat("*", $lineLength));_219 }_219}
We have a Workspace, Workers and Task Queues... what's left? A Workflow. Let's see how to create one next!
Finally, we set up the Workflow using the following parameters:
friendlyName
as the name of a Workflow.
assignmentCallbackUrl
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.
taskReservationTimeout
as the maximum time we want to wait until a Worker handles a Task.
configuration
which is a set of rules for placing Task 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.
app/Console/Commands/CreateWorkspace.php
_219<?php_219_219namespace App\Console\Commands;_219_219use App\TaskRouter\WorkspaceFacade;_219use Illuminate\Console\Command;_219use Illuminate\Support\Facades\File;_219use TaskRouter_Services_Twilio;_219use Twilio\Rest\Client;_219use Twilio\Rest\Taskrouter;_219use WorkflowRuleTarget;_219_219class CreateWorkspace extends Command_219{_219 /**_219 * The name and signature of the console command._219 *_219 * @var string_219 */_219 protected $signature = 'workspace:create_219 {host : Server hostname in Internet}_219 {bob_phone : Phone of the first agent (Bob)}_219 {alice_phone : Phone of the secondary agent (Alice)}';_219_219 /**_219 * The console command description._219 *_219 * @var string_219 */_219 protected $description = 'Creates a Twilio workspace for 2 call agents';_219_219 private $_twilioClient;_219_219 /**_219 * Create a new command instance._219 */_219 public function __construct(Client $twilioClient)_219 {_219 parent::__construct();_219 $this->_twilioClient = $twilioClient;_219 }_219_219 /**_219 * Execute the console command._219 *_219 * @return mixed_219 */_219 public function handle()_219 {_219 $this->info("Create workspace.");_219 $this->line("- Server: \t{$this->argument('host')}");_219 $this->line("- Bob phone: \t{$this->argument('bob_phone')}");_219 $this->line("- Alice phone: \t{$this->argument('alice_phone')}");_219_219 //Get the configuration_219 $workspaceConfig = $this->createWorkspaceConfig();_219_219 //Create the workspace_219 $params = array();_219 $params['friendlyName'] = $workspaceConfig->name;_219 $params['eventCallbackUrl'] = $workspaceConfig->event_callback;_219 $workspace = WorkspaceFacade::createNewWorkspace(_219 $this->_twilioClient->taskrouter,_219 $params_219 );_219 $this->addWorkersToWorkspace($workspace, $workspaceConfig);_219 $this->addTaskQueuesToWorkspace($workspace, $workspaceConfig);_219 $workflow = $this->addWorkflowToWorkspace($workspace, $workspaceConfig);_219_219 $this->printSuccessAndInstructions($workspace, $workflow);_219 }_219_219 /**_219 * Get the json configuration of the Workspace_219 *_219 * @return mixed_219 */_219 function createWorkspaceConfig()_219 {_219 $fileContent = File::get("resources/workspace.json");_219 $interpolatedContent = sprintfn($fileContent, $this->argument());_219 return json_decode($interpolatedContent);_219 }_219_219 /**_219 * Add workers to workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addWorkersToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Workers.");_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("The activity 'Idle' was not found. Workers cannot be added.");_219 foreach ($workspaceConfig->workers as $workerJson) {_219 $params = array();_219 $params['friendlyName'] = $workerJson->name;_219 $params['activitySid'] = $idleActivity->sid;_219 $params['attributes'] = json_encode($workerJson->attributes);_219 $workspace->addWorker($params);_219 }_219 }_219_219 /**_219 * Add the Task Queues to the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 */_219 function addTaskQueuesToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Task Queues.");_219 $reservedActivity = $workspace->findActivityByName("Reserved");_219 $assignmentActivity = $workspace->findActivityByName("Busy");_219 foreach ($workspaceConfig->task_queues as $taskQueueJson) {_219 $params = array();_219 $params['friendlyName'] = $taskQueueJson->name;_219 $params['targetWorkers'] = $taskQueueJson->targetWorkers;_219 $params['reservationActivitySid'] = $reservedActivity->sid;_219 $params['assignmentActivitySid'] = $assignmentActivity->sid;_219 $workspace->addTaskQueue($params);_219 }_219 }_219_219 /**_219 * Create and configure the workflow to use in the workspace_219 *_219 * @param $workspace WorkspaceFacade_219 * @param $workspaceConfig string with Json_219 *_219 * @return object with added workflow_219 */_219 function addWorkflowToWorkspace($workspace, $workspaceConfig)_219 {_219 $this->line("Add Worflow.");_219 $workflowJson = $workspaceConfig->workflow;_219 $params = array();_219 $params['friendlyName'] = $workflowJson->name;_219 $params['assignmentCallbackUrl'] = $workflowJson->callback;_219 $params['taskReservationTimeout'] = $workflowJson->timeout;_219 $params['configuration'] = $this->createWorkFlowJsonConfig(_219 $workspace,_219 $workflowJson_219 );_219 return $workspace->addWorkflow($params);_219 }_219_219 /**_219 * Create the workflow configuration in json format_219 *_219 * @param $workspace_219 * @param $workspaceConfig_219 *_219 * @return string configuration of workflow in json format_219 */_219 function createWorkFlowJsonConfig($workspace, $workspaceConfig)_219 {_219 $params = array();_219 $defaultTaskQueue = $workspace->findTaskQueueByName("Default") or die(_219 "The 'Default' task queue was not found. The Workflow cannot be created."_219 );_219 $smsTaskQueue = $workspace->findTaskQueueByName("SMS") or die(_219 "The 'SMS' task queue was not found. The Workflow cannot be created."_219 );_219 $voiceTaskQueue = $workspace->findTaskQueueByName("Voice") or die(_219 "The 'Voice' task queue was not found. The Workflow cannot be created."_219 );_219_219 $params["default_task_queue_sid"] = $defaultTaskQueue->sid;_219 $params["sms_task_queue_sid"] = $smsTaskQueue->sid;_219 $params["voice_task_queue_sid"] = $voiceTaskQueue->sid;_219_219 $fileContent = File::get("resources/workflow.json");_219 $interpolatedContent = sprintfn($fileContent, $params);_219 return $interpolatedContent;_219 }_219_219 /**_219 * Prints the message indicating the workspace was successfully created and_219 * shows the commands to export the workspace variables into the environment._219 *_219 * @param $workspace_219 * @param $workflow_219 */_219 function printSuccessAndInstructions($workspace, $workflow)_219 {_219 $idleActivity = $workspace->findActivityByName("Idle")_219 or die("Somehow the activity 'Idle' was not found.");_219 $successMsg = "Workspace \"{$workspace->friendlyName}\"" ._219 " was created successfully.";_219 $this->printTitle($successMsg);_219 $this->line(_219 "The following variables will be set automatically."_219 );_219 $encondedWorkersPhone = http_build_query($workspace->getWorkerPhones());_219 $envVars = [_219 "WORKFLOW_SID" => $workflow->sid,_219 "POST_WORK_ACTIVITY_SID" => $idleActivity->sid,_219 "WORKSPACE_SID" => $workspace->sid,_219 "PHONE_TO_WORKER" => $encondedWorkersPhone_219 ];_219 updateEnv($envVars);_219 foreach ($envVars as $key => $value) {_219 $this->warn("export $key=$value");_219 }_219 }_219_219 /**_219 * Prints a text separated up and doNwn by a token based line, usually "*"_219 */_219 function printTitle($text)_219 {_219 $lineLength = strlen($text) + 2;_219 $this->line(str_repeat("*", $lineLength));_219 $this->line(" $text ");_219 $this->line(str_repeat("*", $lineLength));_219 }_219}
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.
app/Http/Controllers/IncomingCallController.php
_33<?php_33_33namespace App\Http\Controllers;_33_33use App\Http\Requests;_33use Twilio\Twiml;_33_33/**_33 * Class IncomingCallController_33 *_33 * @package App\Http\Controllers_33 */_33class IncomingCallController extends Controller_33{_33_33 public function respondToUser()_33 {_33 $response = new Twiml();_33_33 $params = array();_33 $params['action'] = '/call/enqueue';_33 $params['numDigits'] = 1;_33 $params['timeout'] = 10;_33 $params['method'] = "POST";_33_33 $params = $response->gather($params);_33 $params->say(_33 'For Programmable SMS, press one. For Voice, press any other key.'_33 );_33_33 return response($response)->header('Content-Type', 'text/xml');_33 }_33}
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 corresponding Task Queue, 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.
app/Http/Controllers/EnqueueCallController.php
_46<?php_46_46namespace App\Http\Controllers;_46_46use Illuminate\Http\Request;_46use Twilio\Twiml;_46_46_46/**_46 * Class EnqueueCallController_46 *_46 * @package App\Http\Controllers_46 */_46class EnqueueCallController extends Controller_46{_46_46 public function enqueueCall(Request $request)_46 {_46 $workflowSid = config('services.twilio')['workflowSid']_46 or die("WORKFLOW_SID is not set in the system environment");_46_46 $selectProductInstruction = new \StdClass();_46 $selectProductInstruction->selected_product_46 = $this->_getSelectedProduct($request);_46_46 $response = new Twiml();_46 $enqueue = $response->enqueue(['workflowSid' => $workflowSid]);_46 $enqueue->task(json_encode($selectProductInstruction));_46_46 return response($response)->header('Content-Type', 'text/xml');_46 }_46_46 /**_46 * Gets the wanted product upon the user's input_46 *_46 * @param $request Request of the user_46 *_46 * @return string selected product: "ProgrammableSMS" or "ProgrammableVoice"_46 */_46 private function _getSelectedProduct($request)_46 {_46 return $request->input("Digits") == 1_46 ? "ProgrammableSMS"_46 : "ProgrammableVoice";_46 }_46}
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:
reserved
.
POST
request is made to the Workflow's AssignmentCallbackURL, which was configured using the Artisan command
workspace:create
. 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/Http/Controllers/CallbackController.php
_117<?php_117_117namespace App\Http\Controllers;_117_117use App\Exceptions\TaskRouterException;_117use App\MissedCall;_117use Illuminate\Http\Request;_117use Illuminate\Support\Facades\Log;_117use Twilio\Rest\Client;_117_117/**_117 * Class CallbackController Handles callbacks_117 *_117 * @package App\Http\Controllers_117 */_117class CallbackController extends Controller_117{_117 /**_117 * Callback endpoint for Task assignments_117 */_117 public function assignTask()_117 {_117 $dequeueInstructionModel = new \stdClass;_117 $dequeueInstructionModel->instruction = "dequeue";_117 $dequeueInstructionModel->post_work_activity_sid_117 = config('services.twilio')['postWorkActivitySid'];_117_117 $dequeueInstructionJson = json_encode($dequeueInstructionModel);_117_117 return response($dequeueInstructionJson)_117 ->header('Content-Type', 'application/json');_117 }_117_117 /**_117 * Events callback for missed calls_117 *_117 * @param $request Request with the input data_117 * @param $twilioClient Client of the Twilio Rest Api_117 */_117 public function handleEvent(Request $request, Client $twilioClient)_117 {_117 $missedCallEvents = config('services.twilio')['missedCallEvents'];_117_117 $eventTypeName = $request->input("EventType");_117_117 if (in_array($eventTypeName, $missedCallEvents)) {_117 $taskAttr = $this->parseAttributes("TaskAttributes", $request);_117 if (!empty($taskAttr)) {_117 $this->addMissingCall($taskAttr);_117_117 $message = config('services.twilio')["leaveMessage"];_117 return $this->redirectToVoiceMail(_117 $twilioClient, $taskAttr->call_sid, $message_117 );_117 }_117 } else if ('worker.activity.update' === $eventTypeName) {_117 $workerActivityName = $request->input("WorkerActivityName");_117 if ($workerActivityName === "Offline") {_117 $workerAttr = $this->parseAttributes("WorkerAttributes", $request);_117 $this->notifyOfflineStatusToWorker(_117 $workerAttr->contact_uri, $twilioClient_117 );_117 }_117 }_117 }_117_117 protected function parseAttributes($name, $request)_117 {_117 $attrJson = $request->input($name);_117 return json_decode($attrJson);_117 }_117_117 protected function addMissingCall($task)_117 {_117 $missedCall = new MissedCall(_117 [_117 "selected_product" => $task->selected_product,_117 "phone_number" => $task->from_117 ]_117 );_117 $missedCall->save();_117 Log::info("New missed call added: $missedCall");_117 }_117_117 protected function redirectToVoiceMail($twilioClient, $callSid, $message)_117 {_117 $missedCallsEmail = config('services.twilio')['missedCallsEmail']_117 or die("MISSED_CALLS_EMAIL_ADDRESS is not set in the environment");_117_117 $call = $twilioClient->calls->getContext($callSid);_117 if (!$call) {_117 throw new TaskRouterException("The specified call does not exist");_117 }_117_117 $encodedMsg = urlencode($message);_117 $twimletUrl = "http://twimlets.com/voicemail?Email=$missedCallsEmail" ._117 "&Message=$encodedMsg";_117 $call->update(["url" => $twimletUrl, "method" => "POST"]);_117 }_117_117 protected function notifyOfflineStatusToWorker($workerPhone, $twilioClient)_117 {_117 $twilioNumber = config('services.twilio')['number']_117 or die("TWILIO_NUMBER is not set in the system environment");_117_117 $params = [_117 "from" => $twilioNumber,_117 "body" => config('services.twilio')["offlineMessage"]_117 ];_117_117 $twilioClient->account->messages->create(_117 $workerPhone,_117 $params_117 );_117 }_117_117}
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.
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/Http/Controllers/CallbackController.php
_117<?php_117_117namespace App\Http\Controllers;_117_117use App\Exceptions\TaskRouterException;_117use App\MissedCall;_117use Illuminate\Http\Request;_117use Illuminate\Support\Facades\Log;_117use Twilio\Rest\Client;_117_117/**_117 * Class CallbackController Handles callbacks_117 *_117 * @package App\Http\Controllers_117 */_117class CallbackController extends Controller_117{_117 /**_117 * Callback endpoint for Task assignments_117 */_117 public function assignTask()_117 {_117 $dequeueInstructionModel = new \stdClass;_117 $dequeueInstructionModel->instruction = "dequeue";_117 $dequeueInstructionModel->post_work_activity_sid_117 = config('services.twilio')['postWorkActivitySid'];_117_117 $dequeueInstructionJson = json_encode($dequeueInstructionModel);_117_117 return response($dequeueInstructionJson)_117 ->header('Content-Type', 'application/json');_117 }_117_117 /**_117 * Events callback for missed calls_117 *_117 * @param $request Request with the input data_117 * @param $twilioClient Client of the Twilio Rest Api_117 */_117 public function handleEvent(Request $request, Client $twilioClient)_117 {_117 $missedCallEvents = config('services.twilio')['missedCallEvents'];_117_117 $eventTypeName = $request->input("EventType");_117_117 if (in_array($eventTypeName, $missedCallEvents)) {_117 $taskAttr = $this->parseAttributes("TaskAttributes", $request);_117 if (!empty($taskAttr)) {_117 $this->addMissingCall($taskAttr);_117_117 $message = config('services.twilio')["leaveMessage"];_117 return $this->redirectToVoiceMail(_117 $twilioClient, $taskAttr->call_sid, $message_117 );_117 }_117 } else if ('worker.activity.update' === $eventTypeName) {_117 $workerActivityName = $request->input("WorkerActivityName");_117 if ($workerActivityName === "Offline") {_117 $workerAttr = $this->parseAttributes("WorkerAttributes", $request);_117 $this->notifyOfflineStatusToWorker(_117 $workerAttr->contact_uri, $twilioClient_117 );_117 }_117 }_117 }_117_117 protected function parseAttributes($name, $request)_117 {_117 $attrJson = $request->input($name);_117 return json_decode($attrJson);_117 }_117_117 protected function addMissingCall($task)_117 {_117 $missedCall = new MissedCall(_117 [_117 "selected_product" => $task->selected_product,_117 "phone_number" => $task->from_117 ]_117 );_117 $missedCall->save();_117 Log::info("New missed call added: $missedCall");_117 }_117_117 protected function redirectToVoiceMail($twilioClient, $callSid, $message)_117 {_117 $missedCallsEmail = config('services.twilio')['missedCallsEmail']_117 or die("MISSED_CALLS_EMAIL_ADDRESS is not set in the environment");_117_117 $call = $twilioClient->calls->getContext($callSid);_117 if (!$call) {_117 throw new TaskRouterException("The specified call does not exist");_117 }_117_117 $encodedMsg = urlencode($message);_117 $twimletUrl = "http://twimlets.com/voicemail?Email=$missedCallsEmail" ._117 "&Message=$encodedMsg";_117 $call->update(["url" => $twimletUrl, "method" => "POST"]);_117 }_117_117 protected function notifyOfflineStatusToWorker($workerPhone, $twilioClient)_117 {_117 $twilioNumber = config('services.twilio')['number']_117 or die("TWILIO_NUMBER is not set in the system environment");_117_117 $params = [_117 "from" => $twilioNumber,_117 "body" => config('services.twilio')["offlineMessage"]_117 ];_117_117 $twilioClient->account->messages->create(_117 $workerPhone,_117 $params_117 );_117 }_117_117}
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.
app/Http/Controllers/MessageController.php
_50<?php_50_50namespace App\Http\Controllers;_50_50use App\Exceptions\TaskRouterException;_50use Illuminate\Http\Request;_50use App\TaskRouter\WorkspaceFacade;_50use Twilio\Twiml;_50_50class MessageController extends Controller_50{_50_50 public function handleIncomingMessage(_50 Request $request, WorkspaceFacade $workspace_50 ) {_50 $cmd = strtolower($request->input("Body"));_50 $fromNumber = $request->input("From");_50 $newWorkerStatus = ($cmd === "off") ? "Offline" : "Idle";_50_50 $response = new Twiml();_50_50 try {_50 $worker = $this->getWorkerByPhone($fromNumber, $workspace);_50 $this->updateWorkerStatus($worker, $newWorkerStatus, $workspace);_50_50 $response->sms("Your status has changed to {$newWorkerStatus}");_50_50 } catch (TaskRouterException $e) {_50 $response->sms($e->getMessage());_50 }_50_50 return response($response)->header('Content-Type', 'text/xml');_50 }_50_50 function updateWorkerStatus($worker, $status, $workspace)_50 {_50 $wantedActivity = $workspace->findActivityByName($status);_50 $workspace->updateWorkerActivity($worker, $wantedActivity->sid);_50 }_50_50 protected function getWorkerByPhone($phone, $workspace)_50 {_50 $phoneToWorkerStr = config('services.twilio')['phoneToWorker'];_50 parse_str($phoneToWorkerStr, $phoneToWorkerArray);_50 if (empty($phoneToWorkerArray[$phone])) {_50 throw new TaskRouterException("You are not a valid worker");_50 }_50 return $workspace->findWorkerBySid($phoneToWorkerArray[$phone]);_50 }_50}
Congratulations! You finished this tutorial. As you can see, using Twilio's TaskRouter is quite simple.
If you're a PHP developer working with Twilio, you might also enjoy these tutorials:
Instantly collect structured data from your users with a survey conducted over a call or SMS text messages. Let's get started!
Learn how to implement ETA Notifications using Laravel and Twilio.
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think!