October 14, 2012

Voicemail-via-email with Twilio and Heroku in 30 minutes.

I recently decided to give my development company Refinry a toll-free number. Really classes the joint up. I decided to use Twilio, since their API is so impressive (and since Google Voice doesn't exist in Canada). And since Twilio makes it just so easy, I went ahead and set up voicemail-to-email, which can be done at no extra charge.

Twilio has got to be one of the most disruptive services out there right now. I remember not long ago when Asterisk was considered the way to go for telephony, and the configuration nightmare that ensued. This is night and day -- for $1/month, plus $0.01 per minute ($2/$0.03 for toll-free) I can develop a totally customized service, incredibly easily. And if you're here, for the rest of the post I'm going to assume that you want to.

The following is a wee twilio endpoint that will do the following

  • Try to forward the call to my cell phone
  • If I don't answer within 10 seconds, prompt the user to record a message
  • Email the message, as an attachment, to my own email address.

You will need

  • A Twilio account
  • A Heroku account
  • Heroku configured on your machine
  • Ruby and Bundler installed on your machine.

Creating the App

First, make a directory for your project, and create a Gemfile for bundler:

1 2 3 4 5 6
#### Gemfile
source "https://rubygems.org"
gem "sinatra"
gem "twilio-ruby"
gem "pony"
gem "thin"

Sinatra is the web framework we'll be using, twilio-ruby is Twilio's ruby library, Pony is a nice email wrapper, and Thin is a snappier webserver than the default WEBrick. Run "bundle install" to install those packages.

Then, create your app.rb file:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
require 'rubygems'
require 'twilio-ruby'
require 'sinatra'
require 'pony'
require 'open-uri'
 
# Config
MY_NUMBER = "+19995551234"
TO_EMAIL = "voicemail@adambard.com"
SMTP_HOST = "<SMTP hostname here>"
SMTP_USER = "<your username here>"
SMTP_PASSWORD = "<your password here>"
 
# Send a message with an mp3 voicemail attachment
def send_message(subject, message, file_url)
Pony.mail({
:to => TO_EMAIL
:body => message,
:via => :smtp,
:via_options => {
:address => SMTP_HOST,
:user_name => SMTP_USER,
:password => SMTP_PASSWORD,
:port => SMTP_PORT,
:authentication => :plain },
:attachments => {"message.mp3" => open(file_url) {|f| f.read }}
})
end
 
# Answer the call and try to call MY_NUMBER
get '/answer' do
Twilio::TwiML::Response.new do |r|
r.Say "Thank you for calling. Please wait while we attempt to connect you."
r.Dial :timeout => "10", :action => "/record-or-hangup", :method => "get" do |d|
r.Number MY_NUMBER
end
end.text
end
 
# If DialCallStatus is "completed", just hang up. Otherwise, record a message
get '/record-or-hangup' do
Twilio::TwiML::Response.new do |r|
if params['DialCallStatus'] == 'completed'
r.Hangup
else
r.Say "Sorry, looks like nobody's around. Please leave a message."
r.Record :action => "/save-recording", :method => "get"
end
end.text
end
 
# Email the message, then thank the caller and hang up
get '/save-recording' do
send_message("New Message on the hotline",
"Message length: #{params['RecordingDuration']}",
params['RecordingUrl'] + '.mp3')
 
Twilio::TwiML::Response.new do |r|
r.Say "Thank you for calling. Bye!"
r.Hangup
end.text
end

I'm not here to explain the details of how Twilio's API or ruby libraries work, but here's the broad strokes:

"/answer" is the endpoint that Twilio will contact first. It plays a nice greeting then tries to connect whoever called with another number (in my case, my cell phone). After that, it heads over to "/record-or-hangup". Notice the ".text" at the end of the Response block? That's so that the response gets rendered as XML, which is what Twilio wants.

"/record-or-hangup" will check the "DialCallStatus" parameter. If it's "completed", it means that I answered the phone and then disconnected. Otherwise, the call timed out. If the call times out, a recording is prompted for and taken, redirecting to "/save-recording" when it's done.

"/save-recording" fetches the .mp3 url of the message, and uses the "send_message" function to attach it to an email that I send to myself. By attaching it, I can let gmail worry about storing it. It also send a Hangup to Twilio just to make sure the call doesn't go long.

I set just a 10-second timeout because my cell phone has its own voicemail, and I want the call to disconnect before that kicks in.

Configuring for Heroku

Like Twilio, Heroku is a product that took an existing system (Ruby hosting) and made it absurdly, comically, unbelievably easy. Run the following commands to create a git repo, add your files to it, and make a heroku instance for it.

$ git init
$ git add .
$ git commit -m "Initial commit"
$ heroku create

Heroku also requires a few config files: config.ru and a Procfile. Here's what to put in them:

1 2 3 4 5 6 7
 
#### config.ru
require './app'
run Sinatra::Application
 
#### Procfile
web: bundle exec thin -R config.ru -p $PORT start

Running the following...

$ foreman start

...will let you test out the basics. Go to http://localhost:5000/answer to check if everything's working.

Deploying to Heroku

$ git push heroku master

Connecting it to Twilio

When you ran heroku create before, it would have told you what your server name is. Something like http://moonlit-night-1234.herokuapp.com/. (I really enjoy their naming scheme)

You made the endpoint "/answer", so go to https://www.twilio.com/user/account/phone-numbers/incoming (log in to Twilio first), select the number you want to hook up, and use a url like ...

http://moonlit-night-1234.herokuapp.com/answer

... as your voice request URL.

That's it! You can test it out right away.