Rolling your own cloud phone system
Last editedJun 2024
At GoCardless, we're committed to providing great support to all of our users. This means building great tools, both for our customers and for internal use.
We've previously looked at our Nodephone cloud phone system which uses Twilio's telephony APIs and hooks up our phone support with our user data, as well as various internal tools.
Quite a few people have asked us about open sourcing the application. We might well do this, but my experience shows that different users will have very different requirements, so it makes sense to roll your own. This is a guide that will help you do just that.
Choosing the framework: Ruby on Rails + Backbone
I've previously experimented with Node.js and its Express framework and Ruby on Rails for these kinds of applications. Rails is the stand-out choice for three reasons:
- It's the framework of choice for GoCardless and most other startups too,
meaning the expertise is most likely already there
- Rails provides so much for free, so you spend less time reinventing the wheel
and more time building functionality
- Ruby has an incredibly rich ecosystem of gems,
providing functionality from Twilio API access in twilio-ruby to tagging with just one line of code with acts-as-taggable-on
On the frontend, I opted for Backbone.js. Nodephone was my first time working with a JavaScript framework like this, but I'm absolutely converted. Its models and collections made data super easy to handle, and encapsulating presentation logic in bound views helped avoid inevitable callback hell.
Choosing the platform: Heroku
We're experienced at deploying and scaling Rails apps at GoCardless, but a service like this didn't justify giving our dev ops team another service to manage. Instead, we chose Heroku to give us:
- super simple deployments
- high availability so users can always reach us
Heroku offers a lot of addons as well, making it easy to add extras like log management, a Redis database or exception tracking in a couple of clicks with no billing complications.
Heroku can be a little expensive. If you're looking for a cheaper alternative and more flexible customisation, a great choice would be Amazon Web Services Elastic Beanstalk, especially with the benefits of the AWS free tier.
Working with external services
One of the great things about building a custom phone system is the freedom to integrate with other services you use.
Nodephone pulls in the profiles of callers from GoCardless itself based upon their phone numbers, as well as working with external apps like Salesforce.
When you're working with Twilio, response times of your app are critical to providing a decent experience for callers. Slow external services, and worse still, applications that inevitably go down, threaten that.
The ever-popular background job library Resqueprovides an ideal solution, allowing tasks depending on background services to be performed asynchronously.
When someone calls in, we look up their phone number in GoCardless to see if we know who they are. To do this, we enqueue a Resque job to perform the API request whilst displaying a progress indicator to the client:
Once the job has finished in the background (which usually takes seconds) the app provides a real-time update to clients using Pusher's web sockets:
response = Gocardless::Merchant.find(args["merchant_id"])
Pusher.trigger('calls', 'lookup', { id: args["call_sid"], merchant: response })
If GoCardless is slow to respond or out of action, there will only be a slight degredation of service, rather than failed calls.
Avoiding missed calls
We've all called customer support lines, and one of the biggest frustrations is having to wait for ages to speak to someone who can help.
In order to manage bursts of incoming calls, we introduced a queuing system. Since our average call lasts less than 3 minutes, most callers can get through to someone within a few minutes. However, we also wanted to ensure we didn't miss calls from people getting frustrated by longer waits in the queue.
We achieved this with more Resque magic. Once a caller has chosen an option, they're placed in the queue and agents can pick it up by hitting the "Answer" button on their screen. When a call is answered, we mark it as such in our database.
When the call joins the queue, at the same time we schedule a job for 3 minutes time which will check if the call has been answered:
Resque.enqueue_in(3.minutes, ForwardUnansweredCall, @call.id)
If, 3 minutes from now, it hasn't, it'll ring on all of the phones in the office immediately. This means that every single call is answered within 3 minutes during office hours - but usually much sooner!
Open for business
A particular challenge when building phone systems, as simple as it sounds, is managing opening hours, thanks to complications like time zones. Existing gems that I found like business_time were one step removed from what I needed.
It quickly became clear that I wanted something that didn't already exist, so I built it myself in no time at all. My in_business library lets you set your business hours on a daily basis, and provides simple open?
and closed?
methods to call in your code:
# Configure the gem with hours of your choice, on a day-by-day basis
InBusiness.hours = {
monday: "09:00".."18:00",
tuesday: "10:00".."19:00",
# ...
saturday: "09:00".."12:00"
}
InBusiness.open? # => true [if now is within the set hours]
InBusiness.open? DateTime.parse("9th September 2013 08:00") # => false
Taking inspiration from business_time, I added support for the holidays gem, making it possible to take into account public holidays with just two lines of code:
Holidays.between(Date.civil(2013, 1, 1), 5.years.from_now, :gb).
map{ |holiday| InBusiness.holidays << holiday[:date] }
InBusiness.open? DateTime.parse("25th September 2013 10:00") # => false
An important final addition was adding overrides for the set hours. From time to time, you inevitably want to open and close on an ad-hoc basis. For instance, you might decide to open on a bank holiday when you hadn't planned to.
The end result
Twilio, accompanied by popular tools and frameworks and some ingenious thinking makes it relatively simple to build powerful bespoke phone systems which run entirely in the cloud.