Dynamic Extensions for Customer Support Follow Ups using Return2Me

Chris Foster

Chris Foster::Hacker Extraordinaire

We’ve all be there–Weeding through a labyrinth of menu options before we FINALLY get a support rep on the phone. Often, the feeling is fleeting, as we need to find some paperwork, documentation or some other detail that isn’t immediately on hand, and we’re told that we should call the support line again once the info is ready or the documentation is faxed over. What about a direct line to the agent we ask? I’m sorry sir, our company policy is not to give out our extension information. The vicious cycle starts once again.

What if there was a better way? What if an extension could be dynamically generated for you? Now there is.

Meet Chris Foster of fork(4) whose Return2Me app was the recent winner at API Hack Day Portland 2012. Below Chris shares how you can build your own extensions for customer support using Return2Me.

How to Build Dynamic Extensions for Customer Support with Return2Me

At the 2012 Portland API Hack Day, I had the opportunity to work on an idea I’ve had for while. The concept attempts to solve a common problem in customer service organization – coordinating a followup call from a customer.

Most of us have had this experience. You’re working with some company (likely a telco or financial institution if you’re anything like me) to resolve an issue with your account. You fight your way through the phone tree and the hoards of zombie reps to find the one person who seems to know how to solve your problem. That customer service rep asks you do something after the call. Maybe you need to fax in some documentation or perform a transaction.

After completing the task, you need to followup with the rep to ensure the issue is resolved. You don’t want to fight your way back through the system just to get there. But the rep cannot provide his or her direct extension by company policy.

Using Twilio and Amazon’s elastic beanstalk I created a proof of concept application which allows a dynamic extension to be generated so that a person can return directly to the representative familiar with the case, without exposing the actual direct line for the rep. I’ll be going over how I did it in the next few days, but you can try it out at http://rtmahd.fork4.com/.

Enter a number to be mapped with a dynamic extension as your number and optionally enter a mobile device as the customer’s number. Check “One Time Use Only” if you don’t want the extension stored indefinitely.Upon clicking Create Extension, the mobile device will be sent a text message with the phone number to call and the Return2Me extension. Follow the instructions and listen for your phone to ring.

apihackday_logoAll of the code is publicly available under the MIT license here. You’ll need to download Twilio’s Java helper library for this code to build.

A Web application forms heart of the Return2Me system. With it a representative can generate a temporary extension that maps to their direct number. Twilio then calls into this application when a customer provides an extension. The application looks into the map for a match. If a match occurs, the application responds to Twilio with XML instructions to forward the call to the represenative’s direct line. If no match is found, Twilio the response is crafted to prompt the customer that the extension was not valid. The IVR then gives the customer the opportunity to try again.

An example representative console to create dynamic extensions.

An extension mapping stores the representative and customer phones numbers as well as the extension mapped to the representative. Whether or not this is a one time use only extension is also stored.

[sourcecode]
package com.fork4.return2me.beans;

public class Extension {
final String repPhone;
final String custPhone;
final String extension;
final boolean oneTimeUse;
final boolean hasCustomerPhone;

public Extension(String repPhone, String extension, String custPhone, boolean oneTimeUse) {
super();
this.repPhone = repPhone.trim();
if(custPhone != null && custPhone.trim().length() > 0) {
this.custPhone = custPhone.trim();
this.hasCustomerPhone = true;
} else {
this.custPhone = "N/A";
this.hasCustomerPhone = false;
}
this.extension = extension.trim();
this.oneTimeUse = oneTimeUse;
}

public String getRepPhone() {
return repPhone;
}
public String getCustPhone() {
return custPhone;
}
public String getExtension() {
return extension;
}
public boolean isOneTimeUse() {
return oneTimeUse;
}
public boolean hasCustomerPhone() {
return hasCustomerPhone;
}
}
[/sourcecode]

Generating the extension is pretty straightforward.

[sourcecode]
package com.fork4.return2me;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fork4.return2me.beans.Extension;
import com.fork4.return2me.twilio.SendExtension;

public class GenerateExtension extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
generateExtension(request.getParameter("repPhone"), request.getParameter("custPhone"),
"true".equals(request.getParameter("oneTimeUse")));
response.sendRedirect("index.jsp" );
}

protected void generateExtension(String repPhone, String custPhone, boolean oneTimeUse) {
if(repPhone != null) {
HashMap<String, Extension>exts = getExtensions();
SecureRandom rand = new SecureRandom();
String extension = String.valueOf(rand.nextInt(99999));

// Add the extension
Extension ext = new Extension(repPhone, extension, custPhone, oneTimeUse);

// Send it to the customer
if(ext.hasCustomerPhone()) {
SendExtension.send(extension, custPhone);
}

exts.put(extension, ext);
this.getServletContext().setAttribute("extensions", exts);
}
}

// Extensions are stored in Servlet Context to keep hack simple
@SuppressWarnings("unchecked")
protected HashMap<String, Extension> getExtensions() {
if(this.getServletContext().getAttribute("extensions") != null) {
return (HashMap<String, Extension>)
this.getServletContext().getAttribute("extensions");
}

return new HashMap<String, Extension>();
}
}
[/sourcecode]

If the rep provides the customer’s mobile number, Return2Me uses the Twilio SMS API to send a text message to the customer with the dynamic extension and the Return2Me IVR number.

[sourcecode]
package com.fork4.return2me.twilio;

import java.util.HashMap;
import java.util.Map;

import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.TwilioRestResponse;
import com.twilio.sdk.resource.factory.SmsFactory;
import com.twilio.sdk.resource.instance.Account;
import com.twilio.sdk.resource.instance.Sms;

public class SendExtension {
static final String NUMBER = "[Your Twilio Number]";
static final String ACCOUNTSID = "[Your Account SID]";
static final String AUTHTOKEN = "[Your Authorization Token]";

public static void send(String extension, String customerNumber){

// Instantiate a new Twilio Rest Client
TwilioRestClient client = new TwilioRestClient(ACCOUNTSID, AUTHTOKEN);

// Get the account and call factory class
Account acct = client.getAccount();
SmsFactory smsFactory = acct.getSmsFactory();

// Build map of post parameters
Map<String, String> params = new HashMap<String, String>();
params.put("From", NUMBER);
params.put("To", customerNumber);
params.put("Body", "Your Return2Me extension is " + extension + ". Dial " + NUMBER +
" and enter this extension to be connected with your representative.");
try {
// Send an sms a call
// (This makes a POST request to the SMS/Messages resource)
Sms sms = smsFactory.create(params);
} catch(TwilioRestException e) {
e.printStackTrace();
}
}
}
[/sourcecode]

The entry point for the customer is the IVR. To use an IVR with Twilio, create an XML file which uses special tags to define how Twilio interacts with the caller. Gather tags collect information from the user. The text between the Say tags relayed to the caller via text-to-speech. Refer to Twilio’s most excellent API documentation for more options. Also, there is very good guide on getting started with a Twilio IVR.

The hack uses a simple IVR definition.

[sourcecode]
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather action="http://my.server.com/fetch"
numDigits="5" timeout="10" finishOnKey="#">
<Say>Return 2 Me Demo</Say>
<Say>Enter the extension for your representative. Press pound to continue.</Say>
</Gather>
<!– If customer doesn’t input anything, prompt and try again. –>
<Say>Sorry, I didn’t get your response.</Say>
<Redirect>http://my.server.com/ivr/incoming.xml</Redirect>
</Response>
[sourcecode]

Login into your Twilio account to wire up your number to the Return2Me application. Select the appropriate number and enter the URL for the IVR file as the voice request URL. Optionally, you may create a TwiML app to assign a more meaningful name for the purpose of the number.

When a customer connects to the Twilio number, Twilio calls the URL defined in the application to fetch the IVR. It follows instructions in the XML file to prompt the customer for the extension, pausing for a response. The customer enters their extension, presses #, and if a valid extension is given, the customer is connected again to their representative. If the extension is not found, the customer is prompted to try again.

Look in the extensions list for the supplied extension.

[sourcecode]
package com.fork4.return2me;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fork4.return2me.beans.Extension;
import com.fork4.return2me.twilio.IvrResponse;

public class FetchNumber extends HttpServlet {
private static final String NOT_FOUND_MSG = "Extension not found.";
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processCall(request.getParameter("Digits"), response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processCall(request.getParameter("Digits"), response);
}

protected void processCall(String extension, HttpServletResponse response) throws IOException {
response.setContentType("text/xml");
String number = fetchNumber(extension);
String action;

if(!number.equals(NOT_FOUND_MSG)) {
action = IvrResponse.callRedirect(number);
} else {
action = IvrResponse.badExtension();
}

response.getWriter().write(action);
response.getWriter().flush();
}

protected String fetchNumber(String extension) {
String number = NOT_FOUND_MSG;
if(extension != null) {
HashMap<String, Extension>exts = getExtensions();

if(exts.containsKey(extension)) {
Extension ext = exts.get(extension);
number = ext.getRepPhone();
if(ext.isOneTimeUse()) {
exts.remove(extension);
}

this.getServletContext().setAttribute("extensions", exts);
}
}

return number;
}

// Extensions are stored in Servlet Context to keep hack simple
@SuppressWarnings("unchecked")
protected HashMap<String, Extension> getExtensions() {
if(this.getServletContext().getAttribute("extensions") != null) {
return (HashMap<String, Extension>)
this.getServletContext().getAttribute("extensions");
}

return new HashMap<String, Extension>();
}
}
[/sourcecode]

The webapp dynamically constructs the response based on the result of the lookup.

[sourcecode]
package com.fork4.return2me.twilio;

import com.twilio.sdk.TwilioRestResponse;

public class IvrResponse {
public static String callRedirect(String number) {
StringBuilder response = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");

response.append("<Response>");
response.append("<Say>Connecting you to your representative.</Say>");
response.append("<Dial>+1" + number.replaceAll("-", "") + "</Dial>");
response.append("</Response>");

return response.toString();

}

public static String badExtension() {
StringBuilder response = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");

response.append("<Response>");
response.append("<Gather action=\"http://my.server.com/fetch\" ");
response.append("numDigits=\"5\" timeout=\"10\" finishOnKey=\"#\">");
response.append("<Say>Extension not found.</Say>");
response.append("<Say>Enter the extension for your representative. Press pound to continue.</Say>");
response.append("</Gather>");
response.append("<Say>Sorry, I didn’t get your response.</Say>");
response.append("<Redirect>http://my.server.com/ivr/incoming.xml</Redirect>");
response.append("</Response>");

return response.toString();
}
}
[/sourcecode]

This was a fun project with which to begin learning Twilio. Their API well documented and accessible. The free account is enough to get started with this and other ideas. Be careful, though. Once you get started with Twilio, you’ll be hooked!

Chris Foster is a developer, Linux user, community builder and culture advocate who seeks better ways for people and business to connect.  He loves and is loved by an awesome wife and four great kids.