Huddle your global case team in seconds with Twilio conference calling

Introduction:

In my last post, we looked at improving case management response time by sending real-time SMS.  In this post, we will incorporate real-time voice communication with case team members.  Case Management brings together ad hoc teams to assess, diagnose and resolve customer issues.  And, SLAs (Service Level Agreements) drive resolution urgency.  But, what happens if issues aren’t communicated in a timely manner?  What about the liabilities associated with missing your SLAs?

Twilio is a communications API platform company, which is always looking at ways to solve these problems through voice and SMS messaging.  In this post, you’ll learn to seamlessly integrate real-time voice conferencing into Salesforce.com.

 

Situation: A high priority Service Cloud case needs immediate resolution.  Initiate a voice conference call instantaneously and invite case team members to join the discussion.

As soon as your high value customer calls in to report a critical issue, the call center agent creates a case and assigns a case team to work on the issue. The case team manager decides to get the primary team members on a conference call and invite the secondary team members to join the conference in progress by sending details of the conference call via SMS message.

 

Figure 1: Salesforce Case Management Screen:

Clicking on the “Twilio Conference Call” button (Figure1: Annotation 1) triggers a conference call and calls the primary case team member(s) to join the conference in progress.  To make it user friendly, the call prompts the team member to press a key on the phone keypad to actually join to conference.  Additionally, the secondary case team member(s) are notified via SMS with details of the conference call in progress.

 

Figure 2: Received SMS from Salesforce via Twilio:

Figure 3: Conf call recording attached to case after the call:

 

Let’s dive into the code…

Prerequisites

1) To begin, you’ll need an account with Twilio and a Twilio phone number (requires Twilio login).

2) You’ll also need a Salesforce account where you can do the development.

3) Next, you’ll need to install the Twilio Helper Library for Salesforce into your Salesforce org.

4) Finally, we need to create following custom object and fields:

a) Under User Object, create a custom field “Twilio_Conf_Number” of type Phone:

 

Next, Populate the Twilio Conf Number field in the user record (My Personal Information->Personal Information) with the phone number provisioned in step 1 for the current SFDC user.

b) Create a custom object called “Twilio_Case_Conf_Recording”.  The field “Case Number” has a master-detail relationship with the Case object.

 

Step 1:  Creating a button to initiate conference calling on Case Object

Create a button called ‘Twilio Conference Call” on the Case Object (Customize-> Cases-> Buttons, Links and Actions).    Add the following JavaScript code to the Detail Page Button.  This code executes an Apex web service (code follows in Step 2) and passes on parameters from the Case record to initiate the conference calling:

{!REQUIRESCRIPT("/soap/ajax/15.0/connection.js")}
{!REQUIRESCRIPT("/soap/ajax/15.0/apex.js")}
sforce.apex.execute("TwilioVoiceCaseConfController","sendConfInviteCaseTeam",{p_userid:"{!User.Id}", p_caseid: "{!Case.Id}"});

 

Step 2:  Creating the Apex Class to send conference call and SMS invite:

The Apex class has the sendConfInviteCaseTeam method that invokes the Twilio SMS and Voice APIs to send a SMS notification to the Case team members and outbound voice call to join the conference.

The method does the following:

1)    Gets the currently logged in user’s info (Name, Mobile Phone Number) as they will be the moderator of the conference call

2)    Instantiates a connection object to Twilio’s API

3)    Initiates an outbound voice call to the “moderator” of the conference and when answered, puts them into a Twilio conference

4)    Next, for each of the case team members:

  1. Sends a SMS with case details and the conference call phone number to dial-in
  2. For “Primary” case team members, also initiate a voice call and give them an option to join the call (Press 1 to join the call).

 

Note:  Replace variable SFDC_SiteUrl  in the code below with your Site Domain URL (mycompany.force.com)

global class TwilioVoiceCaseConfController {

    // Web Service to initiate Conference Calling
    webservice static void sendConfInviteCaseTeam(String p_userid, String p_caseid) {
        String caseNumber = '';
        String caseContactName = '';
        String caseContactAccountName = '';
        String caseTeamMemberContactId = '';
        String contactName = '';
        String toNumber = '';
        String messageBody = '';
        // Replace SFDC_SiteUrl  with your mycompany.force.com Site Domain URL
        String SFDC_SiteUrl = '<Replace with your force.com site URL>';

        // Get Conference Moderator (User) Info
        String moderatorId = '';
        String moderatorName = '';
        String moderatorPhone = '';
        String moderatorConfNumber = '';
        String moderatorUrl = '';

        // Get params from incoming HTTP Request Object
        String UserId = p_userid;    
        String CaseId = p_caseid;

        // Get the user's info
        for (User u : [SELECT id, name, MobilePhone, twilio_conf_number__c  FROM User WHERE id = :UserId]) {
            moderatorId = u.id;
            moderatorName = EncodingUtil.URLENCODE(u.name, 'UTF-8');
            moderatorPhone = '+' + u.MobilePhone.replaceAll('\\D', '');
            moderatorConfNumber = '+' + u.twilio_conf_number__c.replaceAll('\\D', '');
        }
        moderatorUrl = SFDC_SiteUrl + '/TwilioVoiceCaseConfCallModerator?conferencename=' + moderatorId + '&caseid=' + caseId; 

        // The TwilioAPI helper class looks up your Twilio AccountSid and AuthToken from your current organization, in the TwilioConfig custom setting. 
        // You can configure TwilioConfig by going to Settings->Develop->Custom Settings->Twilio_Config, and your AccountSid and AuthToken 
        // can be found on the Twilio account dashboard
        TwilioRestClient Client = TwilioAPI.getDefaultClient();

        // First, we dial the Moderator Phone and the URL will connect the moderator to the conference
        Map<String,String> voiceparams = new Map<String,String> {
            'To'   => moderatorPhone,
            'From' => moderatorConfNumber,
            'Url' => moderatorUrl
        };
        // Conf call the moderator
        TwilioCall callModerator = Client.getAccount().getCalls().create(voiceparams);

        // Get Case Info
        for (Case c: [SELECT Id, CaseNumber, AccountId, ContactId, Priority FROM Case WHERE id = :CaseId]) {
            caseNumber = c.CaseNumber;
            // Get details about the Case Contact
            for (Contact cc: [SELECT Name FROM Contact WHERE id = :c.ContactId]) {
                caseContactName = cc.Name;
            }  
            for (Account cca: [SELECT Name FROM Account WHERE id = :c.AccountId]) {
                caseContactAccountName = cca.Name;
            }

            String participantUrl = SFDC_SiteUrl + '/TwilioVoiceConfCaseCallParticipantGather?conferencename=' + moderatorId; 
            String participantPersonalizedUrl = '';

            // Create the SMS message body
            messageBody = 'Please join conference in progess at: ' + moderatorConfNumber + '\nCase: ' + caseNumber + ' For ' + caseContactName + '@' + caseContactAccountName;      

            // Iterate through all the Case Team Members and send them a SMS notification and Voice call 
            for (CaseTeamMember ct : [SELECT Id, MemberId, TeamRole.Name FROM CaseTeamMember WHERE ParentId = :c.Id]) {
                caseTeamMemberContactId = ct.MemberId;
                participantPersonalizedUrl = '';

                for (Contact cc: [SELECT name, MobilePhone FROM Contact WHERE id = :caseTeamMemberContactId]) {    
                    contactName = cc.name;  
                    contactName = EncodingUtil.URLENCODE(contactName, 'UTF-8');   
                    // Format (+<Country Code><Number>) the toNumber
                    toNumber = '+' + cc.MobilePhone.replaceAll('\\D', '');

                    // Setup the params for SMS message
                    Map<String,String> SMSparams = new Map<String,String> {
                        'To'   => toNumber,
                        'From' => moderatorConfNumber,
                        'Body' => messageBody
                    };

                    //Send SMS out via Twilio
                    TwilioSMS sms = Client.getAccount().getSmsMessages().create(SMSparams);

                    // Send a voice call to join the conference if the member's role is Primary on the team
                    if (ct.TeamRole.Name == 'Primary'){ 
                        participantPersonalizedUrl = participantUrl + '&participantname=' + contactName + '&moderatorname=' + moderatorName;
                        // Second, we dial the 'Primary' Case Team Members into the conference
                        Map<String,String> voiceparams2 = new Map<String,String> {
                           'To'   => cc.MobilePhone,
                           'From' => moderatorConfNumber,
                           'Url' => participantPersonalizedUrl
                        };
                        // Call the member
                        TwilioCall callParticipant = Client.getAccount().getCalls().create(voiceparams2);
                    }
                }
            } 
        }
    }

    // Process the IVR response (Gather) from the Case Member when called from Twilio
    public String getTwimlProcessGather() {
        // Get the Caller's Phone Number
        String digitSelected = '';
        String conferenceName = '';

        // Get param values from request object
        digitSelected = System.currentPageReference().getParameters().get('Digits');
        conferenceName = System.currentPageReference().getParameters().get('conferencename');
        // Create TwiML response
        TwilioTwiML.Response res = new TwilioTwiML.Response();
        if (digitSelected == '1') {
           res.append(new TwilioTwiML.Say('Connecting you to the conference as a participant'));
           TwilioTwiML.Dial d = new TwilioTwiML.Dial();
           TwilioTwiML.Conference c = new TwilioTwiML.Conference(conferenceName);
           c.setStartConferenceOnEnter(false); 
           d.append(c);
           res.append(d);
           }    
        else {
            res.append(new TwilioTwiML.Say('You will not be connected to the conference.  Have a great day!'));
            res.append(new TwilioTwiML.Hangup());
        }   
        return res.toXML();    
    }

    // Used by the callback visualforce page that is called by Twilio when the conference ends.
    // Save the conference recording link and associate it with the case record.
    public PageReference saveRecording() {   
        // Get Recording related details
        String recordingSid = System.currentPageReference().getParameters().get('Sid');
        String callSid = System.currentPageReference().getParameters().get('CallSid');
        String fromPhoneNumber = System.currentPageReference().getParameters().get('From');
        fromPhoneNumber = fromPhoneNumber.replaceAll('\\D', '');
        String toPhoneNumber = System.currentPageReference().getParameters().get('To');
        toPhoneNumber = toPhoneNumber.replaceAll('\\D', '');
        String recordingURL = System.currentPageReference().getParameters().get('RecordingUrl');
        recordingURL = recordingURL.replaceAll('http', 'https');
        String recordingDuration = System.currentPageReference().getParameters().get('RecordingDuration');
        String caseId = System.currentPageReference().getParameters().get('caseid');

        // Create an instance of  Case Conf Recording object
        Twilio_Case_Conf_Recording__c TwilioRecording = new Twilio_Case_Conf_Recording__c();
        TwilioRecording.Case_Number__c = caseId;     
        TwilioRecording.RecordingSid__c = recordingSid;
        TwilioRecording.CallSid__c = callSid;
        TwilioRecording.Recording_URL__c = recordingURL + '.mp3';
        TwilioRecording.Recording_Duration__c = recordingDuration; 

        // Insert/Update the Twilio Recording record
        upsert TwilioRecording;
        return null;
  }

}

Step 3:  Creating the visualforce pages for Twilio callbacks:

As part of making an outbound voice call and callbacks from Twilio, following visualforce pages are needed.

Note: Please enable access to these pages for your site (App Setup->Develop->Sites)

Step 3.1: TwilioVoiceCaseConfCallModerator Visualforce page:

URL consulted by Twilio when dialing the moderator.

<?xml version="1.0" encoding="UTF-8" ?>
<apex:page sidebar="false" showHeader="false" contentType="application/xml">
    <Response>
        <Say voice="woman">Connecting you to the conference as a moderator</Say>
        <Dial record="true" action="/TwilioVoiceCaseConfAfterRecording?caseid={!$CurrentPage.parameters.caseid}">
            <Conference startConferenceOnEnter="true" endConferenceOnExit="true">{!$CurrentPage.parameters.conferencename}</Conference>
        </Dial>
    </Response>
</apex:page>

Step 3.2:  TwilioVoiceConfCaseCallParticipantGather Visualforce page:

URL consulted by Twilio when dialing the participants.

<?xml version="1.0" encoding="UTF-8" ?>
<apex:page sidebar="false" showHeader="false" contentType="application/xml">
    <Response>
        <Pause length="5" />
        <Gather timeout="10" numDigits="1" action="/TwilioVoiceCaseConfCallPartGatherProc?conferencename={!$CurrentPage.parameters.conferencename}">
            <Say>Hi {!$CurrentPage.parameters.attendeename} you are invited to join a conference started by {!$CurrentPage.parameters.moderatorname}</Say>
            <Say>Press 1 to join the conference.  Press any other key to skip joining the conference</Say>
        </Gather>
        <Say>Have a great day!</Say>
        <Hangup/>
    </Response>
</apex:page>

 

Step 3.4:  TwilioVoiceCaseConfCallPartGatherProc Visualforce page:

URL consulted by Twilio after the participant makes a selection.

<apex:page controller="TwilioVoiceCaseConfController" showheader="false" contentType="application/xml">
{!twimlProcessGather}
</apex:page>

Step 3.5:  TwilioVoiceCaseConfAfterRecording Visualforce page:

URL consulted by Twilio after the recording of the conference is ready.

<apex:page controller="TwilioVoiceCaseConfRecordURLController" action="{!saveRecording}" showHeader="false" contentType="text/xml">
<Response>
</Response>
</apex:page>

Summary

By following the directions above, you should have an integrated conference calling feature within your Service Cloud instance.  If you have any questions or comments, please leave them below.

Twilio platform lets you provision phone numbers in real time, create conference calls, send and receive calls, send and receive SMS, create an IVR (Integrated Voice Response) to collect input, and record conference calls. Since the Twilio REST API helper library is written natively in Force.com, it’s easy to integrate with other Salesforce Objects or Case Management applications.

Ameer Badri is a Solutions Architect at Twilio focused on Fortune 500 companies and channel partner.  He has extensive experience implementing high volume and low latency transactional and analytics applications based on MPP architectures.  He loves disruptive technologies that challenge status quo and enable new business models.  Prior to Twilio, he worked at several startups, Salesforce and Oracle.