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.
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.
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.
Let’s dive into the code…
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.
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}"});
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:
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; } }
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)
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>
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>
URL consulted by Twilio after the participant makes a selection.
<apex:page controller="TwilioVoiceCaseConfController" showheader="false" contentType="application/xml"> {!twimlProcessGather} </apex: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>
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.