Monday, 6 July, 2020 UTC


Summary

Last year we announced Flex Salesforce CTI (Computer Telephony Interface) Generally Availability, with support for both Salesforce Classic and Lightning. Flex Salesforce CTI integration enables you to supercharge your Salesforce Sales and Service Cloud with omnichannel customer communications. Once you’ve successfully installed the CTI integration, you will be able to access Flex via Salesforce’s openCTI utility bar component. This enables you to:
  • Perform automatic search and screen pop to present contextual information to agents
  • Keep the Salesforce screen in sync for agents moving between tasks with context switching
  • Create an automatic record of engagements going through Flex
  • Use Single Sign-On (SSO) to authenticate directly to Flex when logging in from Salesforce
  • Use dual monitors to let agents take full advantage of both Flex and Salesforce real estate
  • Click a phone number in Salesforce to perform an outbound call
You can get read more about the native Flex Salesforce Integration here.
This blog post walks you through steps to extend the native Flex Salesforce Integration using Flex's Programmability.
Tutorial prerequisites
Before we can get started building a more advanced integration, you need to make sure you have accounts with Twilio and Salesforce. You will need:
  • A Free Twilio Account. If you sign up through this link, you'll receive an extra $10 if you upgrade
  • A Salesforce Account. You can sign up here for a free developer edition
  • A Flex Project. If you don't have one yet, these quickstarts will help guide you
And with that, you're ready to start.
How it works
Flex Salesforce Integration utilizes Salesforce's openCTI Framework. With Flex's Action Framework and Salesforce's openCTI methods, one could
  • Listen to events on Flex and respond with Salesforce's openCTI methods
  • Listen to events on Salesforce and respond with a Flex Action
Creating the interactions between the two systems forms the Integration. Some examples of the interactions between the two systems are shown below :
Flex-Salesforce Integration Flows
Flex Salesforce
Incoming Call - AcceptTask --> Search And Screen Pop
Incoming Call - SelectTask --> Search And Screen Pop
Make an Outbound Call <-- User Clicks a Phone Number
Incoming Call - AcceptTask --> Log Task to CRM (Task Accepted)
Incoming Call - SelectTask --> Log Task to CRM (Task Viewed)

Why and when would you want to extend your Flex integration

When your organization has specific needs beyond what's available in Flex's Integration with Salesforce, you can simply extend the integration's functionality by adding your required features on top of native integration using Flex Plugins. To do so, you will use the same architecture that Flex's Integration with Salesforce, as described above, uses.
The architecture of extending the Flex Integration with Salesforce would look like this:
Some scenarios when you might want to deploy additional features to your Flex-Salesforce Integration are
  • You want to save information about the Customer in your Salesforce Custom Object
  • You want to create a new case for each incoming request in background (fully automated) and associate that with the Contact record
  • You want to trigger a workflow in Salesforce when Agent finishes a task in Flex
  • ... and so on.

Let's walk through this in detail. Let’s get started
Developer Environment Setup
Let's make sure you have the software you need to extend your Flex-Salesforce Integration. For this, you will need:
  • The Flex-Salesforce Integration
  • node.js and npm
  • The Twilio Plugin Builder
Create your own basic plugin
In order to extend the default Flex-Salesforce integration, you must first scaffold a new plugin by using the Twilio Plugin builder:
npx create-flex-plugin plugin-sfdc cd plugin-sfdc npm install 
To get a fresh start, remove the folders /components and /states and delete all unneeded imports from the file SfdcPlugin.js. Your file should look like this:
import { FlexPlugin } from 'flex-plugin';  const PLUGIN_NAME = 'SfdcPlugin';  export default class SfdcPlugin extends FlexPlugin {  constructor() {  super(PLUGIN_NAME);  }   init(flex, manager) {   } } 
Because we need some helper functions, let us first go to the folder src, create a subfolder called helpers and add two empty files:
  • salesforce.js, which will be used to determine if the plugin runs inside a Salesforce CTI
  • load-script.js, which will be used to load the OpenCTI SDK from Salesforce
When your plugin initializes, you should check if it is running inside a standalone Flex instance or as part of a Flex-Salesforce integration. We simply check this by evaluating wether the location of the window our plugin runs in contains the string force.com. We export a function
export const isSalesForce = baseUrl => !!baseUrl && window.self !== window.top && baseUrl.includes('force.com'); 
from the file /helpers/salesforce.js. Import it in SfdcPlugin.js, your file should look like this:
import { FlexPlugin } from 'flex-plugin'; import { isSalesForce } from './helpers/salesforce';  const PLUGIN_NAME = 'SfdcPlugin';  export default class SfdcPlugin extends FlexPlugin {  constructor() {  super(PLUGIN_NAME);  }   async init(flex, manager) {  const sfdcBaseUrl = window.location.ancestorOrigins[0];   if (!isSalesForce(sfdcBaseUrl)) {  // Continue as usual  console.log('Not initializing Salesforce since this instance has been launched independently.');  return;  }   // do something  } } 
Do you wonder why we declared the init function as async? As a next step, we will load the OpenCTI SDK from Salesforce and continue execution once it is loaded. For that, we export another helper function that injects a script into the DOM
export const loadScript = async url =>  new Promise((resolve, reject) => {  const scriptRef = document.createElement('script');  const tag = document.getElementsByTagName('script')[0];   scriptRef.src = url;  scriptRef.type = 'text/javascript';  scriptRef.async = true;  scriptRef.onerror = reject;  scriptRef.onload = scriptRef.onreadystatechange = res =>  (!res.readyState || res.readyState === 'complete') && resolve(res);   tag.parentNode.insertBefore(scriptRef, tag);  }); 
from the file /helpers/load-script.js.
Depending on wether you are using Salesforce Classic or Salesforce Lightning, you'll need to import a different SDK. You can read more about this in the official Salesforce documentation. The rest of the blog post will discuss development for a Salesforce Lightning org. Some imports and functions can differ for a Salesforce Classic org, but you can easily modify it after following the official documentation.
We then import loadScript:
import { loadScript } from './helpers/load-script'; 
and replace the do something comment in our init function by
const sfApiUrl = `${sfdcBaseUrl}/support/api/44.0/lightning/opencti_min.js`; await loadScript(sfApiUrl);  if (!window.sforce) {  console.log('Saleforce cannot be found');  return; }  // ready for customization 
That's it! So far, we have verified over the window location that our React plugin is indeed loaded as part of Flex within Salesforce. We have then injected the OpenCTI SDK (an individual js package) into the DOM of the Flex UI and waited until it was loaded. Our plugin can now call actions of the OpenCTI Javascript SDK. Hence, we can now use the power of a programmable Flex Contact Center and trigger functionality inside Salesforce - all from our Flex Plugin.
Add custom Screen Pop functionality
Instead of opening a new Case creation wizard, you can fetch a Contact matching the caller's phone number (or create one if the number isn't found). Once you have the Contact, you can create a new case, link it to the Contact, and show it to a representative. When relevant events occur (e.g. the call has ended), you can attach logs to the case for future reporting inside of Salesforce.

Automatic creation of a new case

The OpenCTI SDK cannot fetch data and handle it in another way than screen popping a record, so you'll need to write an Apex class that can read and write data. Click on the cogwheel in the top right corner and select Developer Console. In the Popup, select File > New > Apex Class and name it TwilioHelper.
public class TwilioHelper {   public static Contact fetchContact(String phoneNumber) {  //Fetch information about contact, modify query if needed  if(phoneNumber == null) {  return null;  }  List<Contact> contactList = [  Select Id, Firstname  From Contact  Where MobilePhone =: phoneNumber OR Phone =: phoneNumber  Limit 1  ];  return contactList.size() > 0 ? contactList[0] : createContact(phoneNumber);  }   public static Contact createContact(String phoneNumber) {  Contact cont = new Contact(  LastName = 'Unknown',  Phone = phoneNumber  );  insert cont;  return cont;  }   public static Case createCase(Contact cont) {  Case cas = new Case(  ContactId = cont.Id  );  insert cas;  return cas;  }  } 
This class fetches related records of contacts and cases. Create a new class called TwilioCaseController and fill it with the following code:
public with sharing class TwilioCaseController {   public static Case createRecords(String phone) {  Contact cont = TwilioHelper.fetchContact(phone);  return TwilioHelper.createCase(cont);  }  } 
Together, both classes will create a new case and link it to a contact record. All you need to do is call createRecords(String phone) with the phone number as an identifier. What is now left is triggering this logic when an incoming call is accepted. We can now call the Apex function in the Flex Plugin and screen pop the record:
get sfApi() {  return window.sforce.opencti; }  cases = {};  initRecords(identifier) {  let param = {apexClass: 'TwilioCaseController', methodName: 'createRecords', methodParams: `phone=${identifier}`};  param.callback = (payload) => {  let caseId = payload.returnValue.runApex.Id;  this.cases[identifier] = {caseId: caseId};  this.screenPop(caseId);  }  this.sfApi.runApex(param) }  screenPop(sObjectId) {  this.sfApi.screenPop({  type: this.sfApi.SCREENPOP_TYPE.SOBJECT,  params: { recordId: sObjectId }  }); }  async init(flex, manager) {  ...  // ready for customization  flex.Actions.on('afterAcceptTask', payload => this.initRecords(payload.task.attributes.name)); } 
You can listen to Flex events by making use of the Actions Framework. After a Flex task has been accepted, the function createRecords of Apex class TwilioCaseController will be called, which will create a case and hand back the case id. This id is is then used to screen pop the record. The cases map is just an example for storing a relationship between an identifier and a created case, which is needed for logging in the next step. In production, make sure you reload this data if not available and add proper memory management.
Replacing an action like accepting a task needs careful evaluation of the impact on other plugins. This plugin runs alongside the Salesforce Integration plugin, which utilizes a similar set of actions, which could lead to unforeseen consequences.
In this case, we are reacting to the fact that an event has occured. We strongly recommend to add a listener, instead of completely replacing the action.

Advanced logging

Now that you have a case for each incoming call, let's attach event logs to it. We will create a custom object (Cogwheel > Setup > Object Manager > Create) inside Salesforce in order to separate communications from logging. This object
  • is called TwilioCallLog
  • is a child object of Case
  • has a Status field with the options Reservation accepted , Wrapup and Completed
  • has a Duration field that stores the time in seconds the task was in this state
All we have to do now is to listen to Flex events and trigger the saveLog() function in the OpenCTI SDK!
logEvent(identifier, event, pastEvent) {  let caseInfo = this.cases[identifier];  let now = Math.round((new Date()).getTime() / 1000);  caseInfo[event] = { startTime: now };  let caseId = caseInfo.caseId;   // if there was a logged event before this one, update it with duration  if(pastEvent) {  let params = {  entityApiName: 'TwilioCallLog__c',  Id: caseInfo[pastEvent].logId,  Duration__c: now-caseInfo[pastEvent].startTime  }  this.sfApi.saveLog({value:params});  }   // log current event  let params = {  entityApiName: 'TwilioCallLog__c',  Name: `${identifier} - ${event}`,  Case__c: caseId,  Status__c: event  }  this.sfApi.saveLog({value:params, callback:(payload) => caseInfo[event].logId = payload.returnValue.recordId}); } 
which can now be triggered by
flex.Actions.on('afterAcceptTask', payload => this.initRecords(payload.task.attributes.name)); flex.Actions.on('afterHangupCall', payload => this.logEvent(payload.task.attributes.name, 'Wrapup', 'Reservation accepted')); flex.Actions.on('afterCompleteTask', payload => this.logEvent(payload.task.attributes.name, 'Completed', 'Wrapup')); 
As we can only log the Reservation accepted after a case has been created, call the logging function from the case creation callback
initRecords(identifier) {  let param = {apexClass: 'TwilioCaseController', methodName: 'createRecords', methodParams: `phone=${identifier}`};  param.callback = (payload) => {  let caseId = payload.returnValue.runApex.Id;  this.cases[identifier] = {caseId: caseId};  this.logEvent(identifier, 'Reservation accepted', null);  this.screenPop(caseId);  }  this.sfApi.runApex(param); } 
All that is left now is to deploy your plugin.
You can find the full code in the GitHub repositories for the Flex plugin and the Salesforce Apex classes.
We hope you found this useful and can't wait to see how you extend the Flex-Salesforce Integration for your organization's needs.
Andrej is a Senior Solutions Engineer at Twilio. He's currently helping companies in EMEA design great customer engagement solutions powered by Twilio. He can be reached at asaweljew [at] twilio.com, or you can collaborate with him on GitHub at https://github.com/andrej-s.
Abhijit is a Technical Product Manager at Twilio. During his role in the Flex team, he originally launched the Flex-Salesforce Integration and is excited to collaborate with Andrej on this blog help customer extend the native functionality. He can be reached at amehta[at]twilio.com.