Building Better Phone Trees With Twilio
Time to read: 10 minutes
When you phone your bank, what combination of keys do you have to press to talk to a human being? If you don’t know, try it out. I am willing to bet that this is the last option presented to you. Your bank probably has any number of self service options to resolve your call as quickly as possible. The actual human call agent is usually the last option because they can perform more complex tasks. This type of system is called Interactive Voice Response (IVR), and the different steps you go through (press 1, press 2) are collectively known as a ‘phone tree’. They are incredibly common and very powerful for providing self service systems.
However some of these phone trees can be confusing, quite a few web sites have been created that provide cheat sheets, or maps of different companies’ phone tree to help callers navigate them. The website pleasepress1.com benefitted form an article on BBC News. Clearly there are some pretty bad phone trees out there. We can use Twilio to resolve this, and some clever ideas common to websites to make that a continuing and measurable improvement. But first, we need to build a phone tree we can perform experiments on. Today we’re just going to build a database backed IVR with simple tracking analytics on caller behaviour. So, let’s walk through this, using Ruby, Sinatra, and Twilio.
Before we start with some code, it is worth talking about terminology. While these systems are almost always called IVRs, there seems to be a lot of different terms for the components. They are composed of Call Trees, Scripts, or Phone Menus, which are made of either Steps or Nodes. I prefer to use IVR, Call Tree, and Steps. So we’ll keep this to be consistent.
So, when a caller first dials our number, this is what they will hear:
“Thank you for calling Twilio Owl Sanctuary.
To hear how many Owls we have, press 1.
To speak to an operator, press 2. ”
(If the caller presses 1):
“Thank you. We have 3 Owls. Three.”
(If the caller presses 2):
“This is a demo, we don’t really have an operator.”
The caller can dial our IVR, listen to the intro, both options, and select option 1 or 2. The user will then hear an appropriate message.
Config.ru
To get started with the coding, we just need to create a config.ru file, and a Gemfile.
This file is just plumbing for deployment. If we want to run the application locally, we can run ‘ruby call_handler.rb’ (we’ll make that file a little later), but this config file will allow us to push the application to Heroku or a similar hosting environment and have it run in the cloud. It simply directs the environment to use the call_handler.rb file as a Sinatra app.
Gemfile
The Gemfile is used to specify the dependencies needed for our app. Most of these are the database we will use. We’re going using Datamapper to store data in a Postgres database, it’s very easy to use so we don’t need to write much code to save our data.
You will need to create a Postgres database on your local system. I favour Postgress.app for my MacBook, which is documented here. But downloads are also available for Windows, and various Linux distributions if you Google around.
Right, let’s get started building the rest of our simple IVR system.
Step.rb
The first thing we need is a representation of each node in the phone tree. This will be some text to say to the caller, and a set of options. We will store the text as one instance of the Step model. We will then store each option in the menu as another instance of the Step model with a parent association to the main text. Our Step model also needs have a variant and if it is a successful ‘goal’, so we can measure the success of each variant.
This gives us a Step Model that looks like this:
To build this class we include ‘DataMapper::Resource’ to give it all the methods it needs to be persisted to the database and declare a some properties. Next we need to create an association ( ‘entity relationship’ as I was taught at programmer school):
The line above means each step has a ‘Parent’, that is also an instance of the Step model, thereby creating the child-to-parent relationship in the phone tree. Each tree represents one collection of ‘Intro’, then ‘Press 1 for X’, ‘Press 2 for Y’. Here the Intro is a parent, and each ‘Press n…’ is a Child.
This line is the inverse of the above, as it allows the Step to have many child steps. This is the parent-to-child part of the relationship. With these two associations, we can traverse the tree in both directions.
This final association is for jumping around the tree. While we have parent-child relationships above for each step and it’s options, this is the result of an option. So that when a user presses the key associated with the step, we know which set of actions to use next.
We will also add a helper method to convert a Step class and its options (through the ‘children’ association) into some TwiML. We can do this with a helper method called ‘to_twiml’:
Notice that in the we’re using Twilio’s new TTS system ‘Alice’, who has a lovely speaking voice!
Finally, to make life easier, I’ve added a helper method to the Step class that can initialize the table with some seed values. In practice we would build an administration interface to do this, but we just want to focus on the core IVR code.
It is worth noting here that we deliberately misspell Twilio, as this helps Alice with pronouncing non-dictionary standard words.
We now have a fully working Step class that can be persisted to a database and generate TwiML. It also has a handy helper method to seed the database with a simple tree. In a real scenario, you will very probably want a step that connects to a customer service/booking/sales agent, but that’s not entirely necessary for this example.
Metric.rb
Having the ability to lead a user through the tree is great. But if we want to lead people through several slightly different trees, then we need a way to count how successful each tree is. We could use a third party API for doing the counting and analytics, but so that we can build on this in future we will roll our own. We will log each call, step, and the state that we receive from Twilio. The code below will suffice.
This code features an associate to a step, as each Metric will be associated with a call, and a step, from which we can put the calls back together and build up a picture of how people use our system.
Call_Handler.rb
Call_Handler.rb is the core of our application. It contains a set of Sinatra routes so that it can respond to HTTP requests. This will allow it to provide both TwiML for Twilio, and HTML so we can see the data generated by our application.
First off, we add the dependencies. Some of these are from our Gemfile, but we also include the Metric and Step classes we created earlier.
The first thing we have to do is configure our database connection. We will get the connection string from an environment variable. As mentioned above, you’ll need to setup a database, but the variable below is compatible with platforms such as Heroku.
The following ‘configure’ block is run once, when the application starts up. First off, we will make sure our model classes (Step and Metric) are correctly represented as database tables. Then, we query the database to see how many Steps we have. If there are none, we can presume this is the first time we’ve run the application or the database was wiped. So we will call the helper method we made in the Step class to seed the database with our simple tree.
The first route we will create is the default ‘/step’ route. This route will respond to the Twilio Voice Web Hook when we first receive an incoming call. We will look for a step that is marked as root (i.e., the very first node in our tree), create a Metric object to log the call, and then ask the Step to generate some TwiML.
This second route is for the ‘action’ attribute on the TwiML verb. We will set the action to be /step/:id of the current step. We can then look at all the children of this step to identify the one matching the Digits parameter from Twilio that contains the key combination the user pressed. Once we have the correct step, we follow the ‘action’ association taking us to the next part of the tree.
The last step for call control is to log the result of all of this. One option is to use the optional Status Callback URL in Twilio, so that when a call finishes we will be sent an additional request. There is no need to return a value from this request.
It’s helpful to create some pages for us to see what is happening. The first one will be the the root (index) page. This will list out each Step in a table so we can make sure everything is as we expect. We’ll instruct the route to render using a template called ‘index’, and a layout called ‘main_layout’.
The /metric route will print out the call logs, and count how many calls get through to a successful goal. This is repeat of the index page above, using a different set of models. This time we use a template called ‘metric’.
ERBs are one of several Ruby template types. We can use these to generate HTML and give our application an interface for people, whereas all the code above that creates TwiML is providing an interface for Twilio.
main_layout.html.erb
The first ERB we will create is the Main Layout. This simply provides some of the boilerplate HTML/Head/Body tags we need. Then we will inject the more specific template we need using the <%= yield %> statement.
index.html.erb
This template is very simple, it generates a table showing each of the Steps we have created. This is very useful for checking that our configure block created the tree as we expected it to.
<% @steps.each do |step| %> <% end %>
ID | Say | Parent | Sequence | Gather | Action | Variant |
---|---|---|---|---|---|---|
<%= step.id %> | <%= step.say %> | <%= step.parent.say if step.parent != nil %> | <%= step.sequence %> | <%= step.gather %> | <%= step.action.say if step.action != nil%> | <%= step.variant %> |
metric.html.erb
The Metric ERB is used to display the number of unique completed calls. It’s a little complicated as we have to query for Metrics that are completed, do not have an associated step, and then make sure we only count them once by Call ID.
Total Calls: <%= @metrics.uniq{|metric| metric.cid}.count %>
Total Completed Calls: <%= @metrics.select{|metric| metric.step != nil && metric.step.goal}.uniq{|metric| metric.cid}.count %>
Call_Handler.rb Utilities
There are a couple of other helpful little routes we can add. These are designed to help with any issues we have with our Model. The first I called /tank as it destroys everything in the database. This can be added to the Call_Handler file so we can delete all the steps and metrics at will.
The major downside of this is we want to restart the application to rebuild the step tree. I added the /build route to do this on demand. If you’re running on an environment like Heroku, this is probably faster than resetting your app.
An IVR System
So, if everything has gone according to plan, we now have an IVR system we can use. Why not try it out? Simply dial one of the numbers below, or download the code from Github and configure your own.
If you prefer to call the UK: +44 1536 609324
…or, if you prefer the US: +1 (864) 761-0312
In my next post, we’ll take this simple IVR system and enhance it with new functionality for Multivariate Testing. Basically, this will allow us to have two very similar phone trees so we can measure which one is more successful and ensure our system is constantly improving! Happy calling.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.