Cisco Finesse SMS Chat Powered by Tropo
On Thursday, Cisco announced it was acquiring Tropo, a Twilio-like service allowing developers to make and receive phone calls and SMS texts via a simple cloud API. The Cloverhound team and I are very excited about this. If you’ve read this blog, you know that we’ve always been proponents of cloud-based communications services and we’ve built quite a few things with Twilio in particular. Tropo offers some very unique features not available from competitors, and combined with Cisco I expect awesome things.
Not everyone in the Cisco UC world has drunk the cloud Kool-Aid however, so I think a demonstration is in order. To show off what you can do with cloud communications services and infrastructure — Tropo in particular — I’ve developed a simple Finesse gadget that lets agents SMS chat with a caller. Agents can use it to provide a caller with complex information like street addresses, URLs, email addresses, temporary passwords, ticket numbers, etc.
Here’s the result:
How it works
How did I do it? Well that’s part of the magic of Tropo and one of the key features lacking from competitors. Tropo essentially has PaaS-like capabilities for hosting Tropo applications directly within Tropo, no outside hosting necessary. I’ve combined this with Google Firebase — an awesome cloud data store and real-time messaging service — to connect Tropo to the Finesse gadget. The really cool part? No. Servers. Whatsoever. And that includes your typical IaaS and PaaS services. No Amazon AWS instances, no Digital Ocean droplets, not even a Heroku dyno.
All the resulting code can be found in our public Github repo. Feel free to use or adapt it however you like. If you do make use of it please drop us a line, we love hearing about our work being put to good use, but no pressure :). Now, let’s walk through how the app works.
Overview
When the Finesse gadget loads, it will connect to our Firebase instance. We’ll use the Finesse APIs to listen for call events, and when a call arrives, we’ll use the caller’s ANI to load any chat history from Firebase and start listening for events for that ANI. If the agent wants to send an SMS to the caller, we’ll directly invoke our Tropo app via an AJAX request from our gadget, and also save the message in Firebase so we have a full message history saved . If the caller wants to send an SMS to the agent, our Tropo app will save the incoming message to our Firebase via its HTTP APIs, and Firebase will in turn forward the message to our agent gadget via the real-time feed we subscribed to earlier. The gadget will display incoming and outgoing messages for the agent in a simple chat screen. When the call ends, we’ll clear the SMS chat screen and unsubscribe from the Firebase feed.
Firebase
Firebase provides a NoSQL data store where we can write arbitrary JSON documents. These documents can be stored or retrieved from any arbitrary path within your Firebase. The paths can then be used similar to tables in a SQL database, organizing different types of information.
Its real claim to fame though is its real-time APIs, which allows web (or other) clients to receive asynchronous updates every time a document is added or changed to a path being monitoring. That means we can use this as a real-time messaging service on top of a data store.
For this app, I used a base path of: https://xxxx.firebaseio.com/clients/test (xxx would be replaced with your instance name). Beneath that, the Tropo application uses a separate path for each caller’s phone number, and beneath that I store the list of messages below a /messages path. For example, messages sent to and from caller 15552221111 would be stored under: https://xxxx.firebaseio.com/clients/test/15552221111/messages. Our gadget will subscribe to that path via the Firebase javascript library, and our Tropo app will push any texts from that number to that path. I’ve used the following format for each message, though in general you can use whatever format you can dream up as long as it’s valid JSON:
{ "direction": "in", "message": "hello", "timestamp": ".sv" }
“direction” is set to “in” for texts received from the caller, and “out” for texts sent from an agent to the caller, “message” is the content of the text, and the value “.sv” is a special keyword that tells Firebase to generate a timestamp when the record is written. Here’s a screenshot of some actual data from Firebase’s Dashboard view:
Notice how each message added is automatically assigned a unique key. For more information on Firebase, checkout their many excellent tutorials on the site.
Tropo
Tropo works much like Twilio and its many competitors. You sign up for an account, add phone numbers, and assign applications to trigger when a call or text is received on a number owned by your account. Like Twilio, an application in Tropo can be a URL accessible over the internet pointing to a web application you own. When Tropo receives a call or text, it sends a request to the given URL which would then return specially formatted JSON documents to tell Tropo what to do with the call, like play a message, send a text, listen for keypresses, or transfer the call. In the Tropo world, this is referred to as the WebAPI.
Unlike Twilio, Tropo also lets you host application scripts directly within Tropo itself. These scripts can be written in JavaScript, PHP, Ruby, Python, or Groovy, and can do any of things you can do with the standard WebAPI. Tropo refers to this as the Scripting API. This feature is a pretty big advantage vs. Twilio, especially if you’re prototyping or your not as comfortable standing up web applications. With Twilio and its ilk, your first step in building an application would be to select a PaaS or IaaS hosting provider and what web framework you’ll use to host your app. With Tropo, you just click Create File and start scripting. Pretty powerful stuff.
Since I was trying to keep this application as self-contained as possible, and the scripting requirements are minimal, I used the Scripting API to host the scripting 100% within Tropo.
Inbound texts
When a text is received on the Tropo number, my script needs to upload a JSON document with the message contents to the appropriate Firebase path. Since Tropo doesn’t have any Firebase libraries built into its scripting engine, the only solution is to use Firebase’s REST APIs. This is as simple as POSTing the JSON document to the Firebase path via HTTP. Tropo doesn’t appear to provide any simple HTTP libraries within the Scripting API, but we can take advantage of the fact that all the scripting languages run within a Java environment. The JavaScript engine in particular is based on Rhino, which allows you to import and access anything in the standard Java libraries. We can use these libraries to perform our HTTP request.
Anyone remotely familiar with the standard Java HTTP libraries is probably cringing right now. Yes, they’re ugly, but we’ll have to make do. I translated the standard Java HTTP examples into Rhino’s JavaScript syntax, the result is below (the log
function is provided by the Tropo Scripting API):
importPackage(java.net); importPackage(java.io); importPackage(java.util); function post(urlString, body) { var url = new URL(urlString); log("Opening connection."); var connection = url.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty('Content-Type', 'application/json'); log("Sending output."); var output = new DataOutputStream(connection.getOutputStream()); output.writeBytes(body); output.flush(); output.close(); var responseCode = connection.getResponseCode(); log("Response is: " + responseCode); var scanner = new Scanner(connection.getInputStream(), "UTF-8").useDelimiter("\\A"); var result = scanner.next(); scanner.close(); return [responseCode, result]; }
If you’re thinking that’s a lot of code for an HTTP request, you are correct. That’s Java for you. In fact this is slightly streamlined compared to typical examples, like the ones here. The key difference is I’ve used the Scanner
class, introduced in Java 5, to retrieve the response without a while loop. The reason I did this is Tropo tracks loop iterations and will tend to kill your script if it’s looping too aggressively. This is a good thing. We don’t want errant scripts taking down Tropo’s system after all.
That gives us an easy to use JavaScript function that takes a URL and message, body, and POSTs the body to the URL. To construct the message we’ll need to access information about the incoming text, like what number it’s from and what the message was. In the Tropo Scripting API, this can be done by accessing a global variable named currentCall
, which has callerID
and initialText
properties that get’s us the information we need. The full list of properties in currentCall
can be found here.
Given the above post
function, we can upload incoming texts to Firebase like so:
function receivedSms() { var body = { message: currentCall.initialText, direction: "in", timestamp: { ".sv": "timestamp" } }; var bodyJson = JSON.stringify(body); var firebaseUrl = "https://XXX.firebaseio.com/clients/test/" + currentCall.callerID + "/messages.json"; log("Posting."); var result = post(firebaseUrl, bodyJson); log("HTTP response code: " + result[0]); log("HTTP body: " + result[1]); } receivedSms();
That’s it for inbound texts from the server-side. Now we just combine these and upload them to Tropo as a .js
file (or create the file directly in Tropo’s code editor), and associate the file to our Tropo application as a text script.
Every text we send to the number associated with this Tropo app will now be written to Firebase. You can watch these pop into the database in real-time from the Firebase dashboard, which incidentally is very cool.
Outbound texts
Tropo works a little bit differently when triggering outbound calls and texts. With Twilio, you can simply call a REST API with your destination number and message (or other instructions) directly within the body of the request. With Tropo, the REST API launches the script or URL associated with your application just like it does for an inbound call or text. That means our application script needs to differentiate between inbound and outbound requests and handle both. This threw me off quite bit and feels awkward, but I can see how it might have it’s advantages. Ultimately it would be nice if a simpler option was available, or if you could select separate scripts/URLs for inbound and outbound requests, but it gets the job done.
To detect whether a given request is inbound or outbound, we can check whether the currentCall
global variable is set. If it is, we’re dealing with an inbound text. If not, we’re dealing with and outbound REST request. Using this, we can modify our existing Tropo script to handle both inbound and outbound requests (I’ve left out the post
function for clarity):
function receivedSms() { var body = { message: currentCall.initialText, direction: "in", timestamp: { ".sv": "timestamp" } }; var bodyJson = JSON.stringify(body); var firebaseUrl = "https://XXX.firebaseio.com/clients/test/" + currentCall.callerID + "/messages.json"; log("Posting."); var result = post(firebaseUrl, bodyJson); log("HTTP response code: " + result[0]); log("HTTP body: " + result[1]); } function sendSms() { message(msg, { to:"+" + numberToDial, network:"SMS" }); log("Sent " + msg + " to " + numbertoDial); } if (currentCall) { receivedSms(); } else { sendSms(); }
The message
function is a global function made available by the Tropo Scripting API. You can view the other available functions on Tropo’s Scripting API reference page. The msg
and numberToDial
variables are custom parameters we’ll pass in when we call the REST API from our client. The API allows us to add whatever arbitrary parameters we like to our requests and they are automatically inserted into our script with global scope.
That’s it for the entire server-side of our application. Not bad. Now we need to build the actual client-side gadget.
Finesse
Finesse is Cisco’s web-based agent software for their Contact Center products. It uses the OpenSocial spec to allow developers to embed custom gadgets within the agent’s screen, and interact with agent and phone state via custom APIs. Since this is a Cisco focused blog, I’m going to mostly assume that readers are familiar with Finesse and so won’t go into much detail about it’s APIs. If you’d like to learn more, take a look at Cisco’s Finesse developer site.
To get basic texts working, we need our gadget to do the following at minimum:
- Connect to the Finesse API and listen for new call and end call events to start and end chat sessions.
- When a call starts, subscribe to the Firebase path for the current caller’s ANI to receive incoming messages.
- Send texts from the agent to the caller via the Tropo API.
- Present a simple user interface to display outgoing and incoming texts, and allow the agent to enter texts to send.
As a bonus we’ll also record outbound texts from the agent to the same Firebase URL, and automatically load any recent conversation when a new call starts.
Subscribing to Firebase events
Firebase provides an extremely easy to use JavaScript library which we can use to subscribe to and send changes to Firebase (among other things). It’s as simple as this:
var MAX_HISTORY_COUNT = 10; var number = "15552221111"; // Customer's number var messagesRef = new Firebase("https://XXX.firebaseio.com/clients/test/" + number + "/messages"); var messagesQuery = messagesRef.orderByChild("timestamp").limitToLast(MAX_HISTORY_COUNT); // Retrieves latest messages from Firebase and sets up a listener for new messages. messagesQuery.on("child_added", function(snapshot) { console.log("Adding message id: " + snapshot.key()); displayMessage(snapshot.val()); });
Let’s step through the key lines one-by-one:
1)
var messagesRef = new Firebase("https://XXX.firebaseio.com/clients/test/" + number + "/messages");
Here we create a reference to our Firebase path by using the Firebase constructor provided by its API.
2)
var messagesQuery = messagesRef.orderByChild("timestamp").limitToLast(MAX_HISTORY_COUNT);
This creates a Firebase query that we can use to listen to events on our path. While we’re here, have the query retrieve recent history — ordered by timestamp — via the limitToLast
and orderByChild
functions. Note that at this point, we haven’t made any actual requests to Firebase to retrieve or listen to events. That’ll happen in the next bit.
3)
messagesQuery.on("child_added", function(snapshot) {
Here the API sends a request to Firebase to send our application an event every time a new child item is added to our target path. When this happens the API will call the callback function we provided with a snapshot
object containing the item that was added and it’s identifier (auto-generated in this case). Since our query is requesting the previous 10 records, the callback will also be called once for each of the last 10 items found at our target path, ordered by timestamp (as we defined in the query).
4)
console.log("Adding message id: " + snapshot.key()); displayMessage(snapshot.val());
The snapshot object provides a val()
function which returns the added item directly as a JavaScript object, we don’t have to manually parse it from JSON. The displayMessage
function takes the message content and updates the UI. For brevity I don’t have it shown here but it can be seen in the full code source.
That’s all we need to receive texts. Now how about in the other direction?
Sending texts via Tropo
I couldn’t find an official JavaScript client library for the Tropo REST API, but fortunately it’s simple enough that we don’t really need one. All we need to do is make a GET request to a URL like this:
https://api.tropo.com/1.0/sessions?action=create&token=YOUR_API_TOKEN&msg=MESSAGE_TO_SEND&numberToDial=DESTINATION_NUMBER
Your API token can be found by navigating to your application page in Tropo. The msg
and numberToDial
parameters are the custom variables we defined in our Tropo script above.
Putting this together, we can now create the following function to send an SMS to a caller:
function sendSMS(message, customerNumber) { var messageUrl = "https://api.tropo.com/1.0/sessions?action=create&token=XXXX"; messageUrl += "&numberToDial=" + customerNumber + "&msg=" + message; console.log("Sending: " + messageUrl); $.ajax({ url: messageUrl, success: function (data) { messagesRef.push( { message: message, direction: "out", timestamp: { ".sv": "timestamp" } }); console.log("SMS successfully sent."); console.log("Result: " + data); }, error: function (err) { console.log("Error sending SMS"); } }); }
Notice that once the request is successfully sent, we push the message into our Firebase to make sure the recent conversation function works properly and leave the door open for possible reporting enhancements. We assume messagesRef
is already set in global scope, and use the push
function provided by the Firebase API.
The final gadget
We now have all the building blocks we need to put together our gadget. Building a Finesse gadget is a topic unto itself, so I won’t discuss it here, but it isn’t terribly difficult if you have some basic HTML and JavaScript skills. You can see the end results in our code repo. To use it, upload the contents of the client-finesse folder in the repo to your Finesse install under files/smsagent
, then reference it in your gadget layout specification like so:
/3rdpartygadget/files/smsagent/SMSAgent.xml
Details for uploading third party gadgets can be found in the Finesse developer guide. You can also host it externally, but you’ll need to add:
<Optional feature="content-rewrite"> <Param name="exclude-url">*</Param> </Optional>
within the ModulePrefs
tag in the SMSAgent.xml
gadget specification file to make sure the JavaScript and CSS resources load correctly.
Building your own
There you have it, fully functional SMS chat integrated with Cisco Finesse, built in a couple days and no servers required. You can find 100% of the resulting code in our public Github repository. Feel free to use it for whatever purpose you like. Here are the high level steps you’ll need to get this running on your own:
- Sign up for a Firebase account (free for dev and up to 50 max connections).
- Create a base path within your Firebase instance where you will post messages.
- Sign up for a Tropo account (free for dev, 3 cents per text for production, though prices may change after the Cisco purchase).
- Via the Tropo site, open a support ticket requesting permission to send outbound calls and texts to your test mobile number (Tropo support is very fast, they responded to me within about 15 minutes).
- Edit the
smsagent.js
file with your selected Firebase instance name and base path. - Upload the resulting file to Tropo via their site.
- Create a new Tropo application, and associate the new file to texts for that application.
- On the Tropo application page, select a phone number to provision for your application.
- Edit the
SMSAgent.html
file with your Firebase instance name and path, and your Tropo messaging API key, which can be found on your Tropo application page. - Upload the contents of the client-finesse folder to Finesse (with your edited
SMSAgent.html
file), following the instructions provided by Cisco. Alternatively, host the folder on a web server available to your Finesse server, but add the code snippet mentioned above toSMSAgent.xml
before doing so. - Add a reference to the gadget in your Finesse layout.
- Login an agent and send them a call with your 10 digit test mobile number as the ANI.
- Bask in the glow of SMS chat.
Enjoy!
10 responses to “Cisco Finesse SMS Chat Powered by Tropo”
Awesome. Love it.
This looks pretty slick. Maybe I overlooked in the details of what’s happening, but what happens if that customer texts me say a minute after the call ends or five days? Do they get some kind of auto response from Troppo, does it come through to finesse?
As is it’ll be blindly saved in Firebase, showing up in an agent’s history if they take a call from the user. Next enhancement would be to add autoresponse. Would be relatively simple, need to mark the call as active/inactive in Firebase and branch accordingly in the Tropo script, sending a static message back if the call isn’t active. In fact I may add that this evening.
Nice work!
Excellent. We will add this within our own CC demo labs. Great stuff!
I am trying to use your SMS gadget. This thing looks amazing and really shows the potential of Finesse. I am having a problem with the gadget appearing, though. I have verified all files are loaded into 3rdPartyGadget folder, and referenced in Desktop layout. I also have inbound texts working through Tropo to Filebase successfully. One thing I noticed in the SMSAgent.html file is this code:
if (callData.toAddress &&
(callData.fromAddress.length === 10 || callData.fromAddress.length === 11)) {
var numberToLoad = callData.fromAddress
if (numberToLoad.length === 10) {
numberToLoad = “1” + numberToLoad;
We are using +E.164 number for Calling and Called Party numbers. Could this be causing issue with the gadget showing up blank? I can provide a screenshot of the issue if it would help.
Thanks in advance if you are able to help me out.
Thanks,
BC
You may need to tweak that line to support e.164 if that’s what Finesse is receiving, but it wouldn’t affect the gadget showing up in the first place. What you should see if the gadget loaded is a grayed message box. Try altering the layout URL to reload the gadget, you can do that by adding a nocache query param to the end of the url in your layout config, like so:
/3rdpartygadget/files/smsagent/SMSAgent.xml?nocache=2
Then log out and back in. Anytime you make a change to the gadget you’ll want to increment that nocache param to force Finesse to reload it.
Feel free to send a screenshot if it’s still not working.
Yeah, so this is what I am seeing. Same results with the ?nocache=2 in the string.
https://goo.gl/photos/YJJPwF4PfTuhatxV6
I also noticed that I named my folder SMSAgent, instead of smsagent. Is there anything in the XML/HTML/JS files that requires is to be in smsagent? I didn’t notice any, but I could be missing it.
No the file folder should be fine as long as it matches the URL you entered in the layout. I see you’re using 10.6. Have you tried loading the 10.6 Finesse libraries instead of 10.5? The blank screen suggests the Finesse library may not have initialized properly.
I can’t seem to find any Java Libraries for 10.6. Appears like DevNet latest available is 10.5. https://developer.cisco.com/site/finesse/.
I don’t mean to turn your blog into a support channel. I really appreciate your help and great idea for a gadget. You have opened my eyes a bit to some very cool tools. All my UCCX clusters are 10.6.1, so I will keep hacking away at it and maybe try installing a new 10.5 UCCX cluster.
Thanks again.
BC