Building A Salesforce Powered Call Center with Twilio TaskRouter

April 21, 2015
Written by

thinkvoice-640×265

ThinkVoice gives businesses the communications tools they need to create exceptional customer experiences. ThinkVoice CEO Brian Coyle knows that something as simple and powerful as making a phone ring can be difficult to do at a large scale. That’s where ThinkVoice comes in. To ensure they’re giving their customers reliable and scalable communication solutions, ThinkVoice uses Twilio.

After the launch of Twilio’s TaskRouter, ThinkVoice added a new tool to their tool belt that enables them to manage traditionally difficult aspects of call center data like agent state, queue information, and more.

In the following post, Bryan Coyle shows how to build a Salesforce powered call center with Twilio’s TaskRouter. 

 

Building A Salesforce Powered Call Center

This is a beginner’s guide to implementing an omnichannel call center directly into your Salesforce CRM using Salesforce Open CTI, Twilio TaskRouter and Twilio client.  The source code for the solution is available here. The tutorial is aimed at developers looking to build a call center within Salesforce or just looking to learn more about TaskRouter.  If you just want to check out the end product you can deploy it to Heroku straight away. 

If you want to bring phone calls into your Salesforce CRM you are, in the best case scenario, integrating a stand alone cloud based call center product with prebuilt connectors, or at worst integrating an on premise legacy PBX through multiple layers of integration.  In either case you are managing your call center across multiple systems leaving you with a management headache that is extremely costly and error prone.  The following tutorial aims to solve this headache by reducing dependence on third party solutions and leaving as much configuration in Salesforce as possible.

Omnichannel

The demo demonstrates the ability to handle multiple channels of communication using Twilio TaskRouter to manage a single queue of communications coming from different channels.  This has been a real struggle as point solutions are only able to handle one or two communication channels and few support SMS as a channel.

Brief Overview the Technologies Used

The tutorial is built on the fantastic demo of a Salesforce embeddable ACD using Twilio Client by Charles Oppenheimer of Twilio.  Many thanks to Charles for sharing that demo.  We have simply taken that demo and inserted TaskRouter to handle the distribution of calls and addition of text messages.

  • Salesforce Open CTI –Salesforce Open CTI is an open API to allow third party CTI vendors to connect telephony channels into the Salesforce CRM interface.  In our demo we use Open CTI to house our soft phone and drive the click to dial/text functionality.  The demo requires no plugins or installed software thanks to the design of Open CTI. For more info see the developer guide.
  • Twilio ClientTwilio Client is a WebRTC interface to Twilio.  In our demo we are using the javascript library which gives us an API and connection to Twilio to receive the call within our Salesforce browser delivering the call via WebRTC.  Twilio client also gives us the ability to control the call via our soft phone.
  • Twilio TaskRouter TaskRouter is the queue and state machine that we have our Salesforce users connect to through the Open CTI soft phone.  We route all of our calls and texts through TaskRouter which manages the queueing of those  communications.  TaskRouter also monitors the state of our Salesforce users sending them the calls and texts as they become available.  What differentiates TaskRouter from traditional cloud or on premise phone systems is it’s completely API driven so we can place all administrative functions within Salesforce.

Getting Started – Part 1 – Phone Calls

First I highly recommend going through the original client-acd project from Charles as he covers all the basic setup including Twilio setup.  Our README covers the additional setup steps of configuring the necessary TaskRouter components.  At a high level we have replaced any code that was managing agent state or call queueing with TaskRouter code including removing all the Mongo DB code.  Mind the twilio-gem version.  The TaskRouter stuff is in after 3.15.  The rest of the post is going to cover the implementation of TaskRouter.

 Call Routing With TaskRouter

Lets start with routing incoming calls to TaskRouter.  In our Sinatra app we are going to change the /voice action to send the call into our workflow via Twiml.  This will immediately route our incoming call into the queue that is now managed by TaskRouter and the workflow we defined.  With that we now have a call in queue waiting for agent.  Very simple!

post '/voice' do
  response = Twilio::TwiML::Response.new do |r|  
    r.Say("Please wait for the next availible agent ")
    r.Enqueue workflowSid: workflow_id
  end
  response.text
end

Lets now hook up the agent state.

Salesforce Agent State Handling

Thanks to the work done in the original project it was really easy to plug the agent into TaskRouter.  As the softphone loads up within the Salesforce browser the softphone.js code was already pulling the agent id from the Salesforce API and registering as a Twilio Client.  Once we have a Twilio Client established we then go to register the agent with TasksRouter.  Much like registering a Twilio Client we need to get a token from the server with the proper permissions. In our code we pass the Salesforce user id in an ajax call so we can use that to match to the correct worker defined with TaskRouter.

$.get("/workertoken", {"client":SP.username}, function (workerToken) {
    SP.worker = new Twilio.TaskRouter.Worker(workerToken);
    SP.functions.registerTaskRouterCallbacks();
    var activitySids = {};
 
    // For this demo we assume basic states to map to the existing Ready/Not Ready so we will
    // take 1 ready and 1 not ready event
    // In real app we would make an interface with dynamic state drop down and map them here
    SP.worker.fetchActivityList(function(error, activityList) {
      var activities = activityList.activities;
      var i = activities.length;
      while (i--) {
        if (activities[i].available){
          SP.actvities.ready = activities[i].sid
        } else {
          SP.actvities.notReady = activities[i].sid
        }      
      }
    });
});

On the server we use the Twilio REST API to create a client to TaskRouter. With the REST API we can loop through all the workers that are defined for our Workspace, find the worker that corresponds to our salesforce agent and generate a token for that worker. We return that token back to the client side where our javascript function instantiates a worker via the worker.js library.

## If a workers is defined with the client name it returns a token for the TaskRouter worker
get '/workertoken' do
  client_name = params[:client]
  if client_name
    @trclient = Twilio::REST::TaskRouterClient.new(account_sid, auth_token, workspace_id)
    workertoken = nil
    @trclient.workers.list.each do |worker|
      logger.info worker.friendly_name
      if worker.friendly_name == client_name
        worker_capability = Twilio::TaskRouter::Capability.new account_sid, auth_token, workspace_id, worker.sid
        worker_capability.allow_worker_fetch_attributes
        worker_capability.allow_worker_activity_updates
        workertoken = worker_capability.generate_token
      end
    end
  end
  return workertoken || ""
end

Worker.js is the javascript SDK that gives us the ability to receive work from the queue and gives us the ability to manage agent state. In this example we register for the activity.update callback which will let our client know anytime this agent changes state and when it does we update the UI. Worker.js also allows us to retrieve a list of activities or states that an agent can be set to. Each activity (or state) is either Available or Unavailable meaning they can take work or not. In this demo we assume one activity is available and will store the sid of one Available activity and one Unavailable activity. We use these sids to set the agent state later.

// callback for TaskRouter to tell use when agent state has changed
SP.functions.registerTaskRouterCallbacks = function(){
  console.dir("Register callbacks");
  SP.worker.on('activity.update', function(worker) {
    SP.functions.updateStatus("", worker);
    console.log("Worker activity changed to: " + worker.activity_name);
  });
}

So we now have a call in queue, and a salesforce agent able to register to the queue and manage their state. Lets see how work gets distributed to our agent.

Routing Calls to the Salesforce Agent

Back when we setup our inbound call handling in client-acd.rb /voice we simply redirected the call to the TaskRouter Workflow with Twiml. Because we did this there is a little magic baked into TaskRouter we can take advantage of. First lets understand how TaskRouter routes work. The call came into Twilio and was directed to a workflow. The workflow is aware of workers (agents) who have attributes that the workflow can use to determine who should take that work. In this demo the workflow is acting as a simple queue knowing who has been idle for the longest. When we log into salesforce and click the Ready button Worker.js tells TaskRouter that our worker/agent is available. TaskRouter assigns the activity (the call in this case) to the agent. The workflow is configured to POST that assignment to our Sinatra app at /assignment. When the app receives that POST we simply tell TaskRouter to dequeue the call to the agent’s Twilio Client ID which is configured on the worker’s attribute {“contact_uri”: “client:salesforce_login_id”}…magic!

#######  This is called when agents is selected by TaskRouter to send the task ###############
## We will use the dequeue method of handling the assignment
### https://www.twilio.com/docs/taskrouter/handling-assignment-callbacks#dequeue-call
post '/assignment' do
  raw_attributes = params[:TaskAttributes]
  attributes = JSON.parse raw_attributes
  logger.info attributes
  from = attributes["from"]
  logger.info from
  assignment_instruction = {
    instruction: 'dequeue',
    from: from
  }
  content_type :json
  assignment_instruction.to_json
 
end

 

Wrapping Up

TaskRouter has really simplified our code as we don’t need to manage agent state. We still keep a websocket connection and polling to push real time stats up to our soft phone giving our agent the number of logged in agents and number of items in the queue. We use the TaskRouter REST API to get that info.

## Thread that polls to get current queue size, and updates websocket clients with new info
## We now use the TaskRouter rest client to query workers and agents
Thread.new do
   while true do
     sleep(1)
     stats = @trclient.task_queue_statistics(task_queue_id).realtime
     qsize = stats["tasks_by_status"]["pending"]
     readycount = stats["total_available_workers"]
 
      settings.sockets.each{|s|
        msg =  { :queuesize => qsize, :readyagents => readycount}.to_json
        logger.debug("Sending webocket #{msg}");
        s.send(msg)
      }
     logger.debug("run = #{$sum} #{Time.now} qsize = #{qsize} readyagents = #{readycount}")
  end
end

In the next part of this demo we will add text message routing to our Salesforce Call Center. Stay tuned.