Monday, November 22, 2010

The cool thing aboutTwillio andMailgun is that developer can concentrate on building great applications and focus on the feature set instead of fighting the email and voice complexities what can steal hundreds of working hours.

One of such applications revealing the power of both platforms is an email-to-voice gateway using Twillio and Mailgun. Here's the toolset used in the project:

1. Python - IMHO the best language for rapid prototyping.

2.Flask - relatively new kid on the block, cool and small web framework loved by Ev, that's why I've decided to check it out

3. Twillio Python library.

## Deciding what to do. ##

Our gateway should do very simple thing, when someone mails to some address, like callme+1234556@example.com, Twillio should call to +123456 and read message sender, subject and body to the user on the phone.

## Configuring Mailgun. ##

The first thing we need to do is to accept messages like call+phone@mailgun.net. All we need to to do is to create a route like

call(.+)@mailgun.net -> http://example.com/messages

Once the message will arrive to our address, http callback will be triggered and all what we need to do is to create a handler in our Flask app:



#!python
@app.route('/messages/', methods=['GET', 'POST'])
def new_message():
message_id = request.form['Message-Id']
recipient = request.form['recipient']
number = extract_phone_number(recipient)
if not number:
abort(500, "Invalid recipient format: {0}".format(number))
else:
app.logger.debug("Call to: {0}, message id: {0}".format(number, message_id))
call_to(number, message_id)
return 'Thanks'



Ok, now when we have all information about the message it's a good time to call Twillio API's.

## Calling using Twillio ##

To ask Twillio to make a call for you one needs to call Twillio API's, the call would look like that:


#!python
def make_call(to, url, from_ = CALLER_ID):
return account.request(
'/{0}/Accounts/{1}/Calls'.format(API_VERSION, ACCOUNT_SID),
'POST', { 'From' : CALLER_ID, 'To' : to, 'Url' : url })


Once Twillio succeeds at calling it calls the url you've supplied and you need to generate some TwiML so that interpreter would know what to do with this call. All we need is to generate something like this:


#!xml




You've got new message.
Message from: {{ from_ }}, subject: {{ subject }}, body: {{body[:300]}}.





The code above is actuallyJinja template that Flask is using, these guys at pocoo.org are creating cool libs with strange names, check outWerkzeug for example :)

## Gluing it all together ##

As you've noticed, there are two separate http calls, one is for mailgun callback with the message and the other one is for twillio callback for TwiML. We need to store the message between calls. We can easily achieve that by setting up Mailgun to store this message for us instead of writing custom storage. I've just created a mailbox storage@mailgun.net and set up one more route:

call(.+)@mailgun.net -> storage@mailgun.net

Once message matching the pattern arrives 2 routes will now hit and message can be accessed via IMAP or POP3. Don't be scared, in python all the IMAP code to search message by id takes 10 lines:


#!python
def get_message(message_id):
mailbox = IMAP4_SSL('mailgun.net')
mailbox.login('storage@mailgun.net', 'abc123')
mailbox.select()
typ, data = mailbox.search(None, '(HEADER "Message-Id" "{0}")'.format(message_id))
for num in data[0].split():
typ, data = mailbox.fetch(num, '(RFC822)')
return Parser().parsestr(data[0][1], headersonly = False)
return None



Having this code our callback for twillio will look like:


#!python
@app.route('/read_message/', methods=['GET', 'POST'])
def read_message(message_id):
app.logger.debug("Request to read message by id {0}".format(message_id))
message = get_message(message_id)

subject = message.get('subject', '')
body = message.get_payload()
from_ = message.get('From')

if message:
return render_template('read_message.xml', from_ = from_, body = body, subject = subject)
else:
return render_template('error.xml')



That's it! To get our test server running, we need two additional lines in our gateway.py file:


#!python
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3210, debug = True)


That's it! To launch the app we need to type:

python gateway.py and get it all working!

50 lines of code got our server up and running, ha!

Of course this is a test code and we need to do slightly more to have it ready for production, but basically all main and hard parts are ready, we've successfully set up the 3 hardest parts as email processing, message storage and outbound calls.