Home

Awesome

Twilio

Build Status

This library provides an easy way to work with Twilio cloud telephony service (http://www.twilio.com/), including initiating calls, sending SMSs, generating TwiML, and handling callbacks from the twilio server itself.

If you already have a web server setup and/or your own routing mechanisms you can still use the twilio and twiml modules to access the twilio API and also format twiml responses.

Usage

There are two ways to use twilio_erlang:

Traditional Usage

Core

There are two core API modules:

twilio

Contains functions to access the twilio REST API. For instance, to make a Twilio call you can call the function 'twilio:request()' to make an HTTP request to the twilio API:

twilio:request("ACCOUNT_SID", "AUTH_TOKEN", post, "/2010-04-01/Account/ACCOUNT_SID/Calls.json",
               [{"From", "5551234"}, {"To", "5554321"}, {"Url", "http://YOUR_URL.com/"}])

which will make a call to the specified phone number. API Reference: [http://www.twilio.com/docs/api/rest/making_calls]

You can 'twilio:request()' provides base level access for making HTTP requests to the Twilio API.

Using 'twilio:make_call()' does the same thing but handles most of the details:

twilio:make_call("ACCOUNT_SID", "AUTH_TOKEN", "5551234", "5554321", [{"Url", "http://YOUR_URL.com/"}])

Much better.

twiml

Contains functions to encode a list of twiml erlang records to an XML document.

For instance,

twiml:encode([#say{text="Hello, World!"}, #dial{body="5551234"}])

produces:

<?xml version=\"1.0\"?><Response><Say>Hello, World!</Say><Dial>5551234</Dial></Response>

twilio capabilities

BETA

Twilio Capability tokens tokens are described at [http://www.twilio.com/docs/client/capability-tokens]

AccountSID = "ACXXXXXXXXXXXXXXX",
AuthToken = "secret",
Capabilities = [{client_incoming, "tommy"}, {client_outgoing, "JJJJ"}],
Opts = [{expires_after, 3600}], % 6 minutes
Token = twilio_capabilities:generate(AccountSID, AuthToken, Capabilities,
Opts).
<<"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6WzExNSw5OSwxMTEsMTEyLDEwMSw1OCw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDU4LDEwNSwxMTAsOTksMTExLDEwOSwxMDUsMTEwLDEwMyw2Myw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDc4LDk3LDEwOSwxMDEsNjEsMTE2LDExMSwxMDksMTA5LDEyMSwzMiwxMTUsOTksMTExLDExMiwxMDEsNTgsOTksMTA4LDEwNSwxMDEsMTEwLDExNiw1OCwxMTEsMTE3LDExNiwxMDMsMTExLDEwNSwxMTAsMTAzLDYzLDk3LDExMiwxMTIsODMsMTA1LDEwMCw2MSw3NCw3NCw3NCw3NCwzOCw5OSwxMDgsMTA1LDEwMSwxMTAsMTE2LDc4LDk3LDEwOSwxMDEsNjEsMTE2LDExMSwxMDksMTA5LDEyMV0sImlzcyI6WzY1LDY3LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4LDg4XSwiZXhwIjoxMzI3OTk3NzIxfQ==.l6HkGKuiPahjEJpRrqfG5ZLTdcqwHpfA5UFwKVPEEuE=">>

Look at the documentation and source for more information.

Server

This application optionally starts an HTTP server to handle incoming requests from Twilio. You can start the web server by calling:

twilio_web:start(_Port = 8080)

If you want to handle incoming calls to one of your Twilio phone numbers, you will need to make sure you have the number setup on Twilio to point to your server.

Routing

The web server will automatically route requests to a module based on the incoming request path.

An arbitrary path /ROOT/... will call the function twilio_rt_ROOT:handle_request(RestOfPath, Props) where Props contains the HTTP request parameters.

Here is an example from the machine router twilio_rt_machine:

%% @doc Handle incoming twilio requests on "/machine".
handle_request(["start"], Params) ->
    % these are values sent in from twilio
    City = proplists:get_value("ToCity", Params),
    State = proplists:get_value("ToState", Params),
    [
        #say{text="Hello!  Welcome to the spawnfest call center."},
        #say{text="It appears you are calling from " ++ City ++
            ", " ++ State},
        #redirect{url="options"}
    ];

This handles the route /start and passes the URL query or POST parameters, depending on the HTTP method, as `Params'. The function must return a list of TwiML records.

Utilities

BETA

There are a number of utilities for handling parameters more easily by parsing them into Erlang records:

%% @doc Handle incoming twilio requests on "/machine".
handle_request(["start"], Params) ->
    % these are values sent in from twilio
    Tw = twilio_web_util:process_body(Params),
    twilio_web_util:pretty_print(Tw),

This will parse and print out the incoming record giving something like this in the shell:

***Start of Twilio Record***********************
Account Sid is     "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Application Sid is []
Direction is       "inbound"
Call Status is     "in-progress"
Call Sid is        "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
API Version is     "2010-04-01"
Custom Params is   []

called Number is       "01315101875"
called City is         []
called Zip is          []
called State is        "Edinburgh"

(...)

NOTE This work is not complete. the various Twilio parameters are being implemented gradually. Parameters not currently handled are returned as a list in the field #twilio.custom_params. If you see parameters in that field consider adding clauses to the function twilio_web_util:make_r/7 and adding information to the records in twilio_web.hrl.

Why Extended TwiML?

BETA

This is a major change to the operation of twilio_erlang - it handles incoming calls - but not yet inbound SMS.

TwiML is a set of Twilio facing XML commands that have been implemented in records.

Extended TwiML allows you to mix Erlang facing records with the Twilio-facing ones and makes it trivially easy to build Twilio applications.

Extended TwiML has a number of additions made to it:

The problems that Extended TwiML is solving are:

This is a piece of extended TwiML from the file recipe 1 in twiml_ext_recipies.erl:

    [#say{text = "yowza"}];

The ascii compilation of this is:

1 - SAY "yowza"
2 - HANGUP

It compiles to the following state machine:

[{"1",{{xml,"<Say>yowza</Say>"},next}},
 {"2",{{xml,"<Hangup/>"},exit}}]

Which executes in a finite state machine inside phonecall_srv.erl

This example is not so interesting. Recipe 9 is more interesting:

    SAY1 = #say{text = "My you are looking swish"},
    RESPONSE1 = #response_EXT{title = "Praise", response = "1", body = [SAY1]},

    SAY2 = #say{text = "What you looking at, fannybaws?", language = "en-gb"},
    RESPONSE2 = #response_EXT{title = "Abuse", response = "2", body = [SAY2]},

    SAYD = #say{text = "can you no use a phone, bawbag?", language = "de"},
    DEFAULT = #default_EXT{title = "a slagging", body = [SAYD]},

    % now put them all together
    [#gather{numDigits = 1, autoMenu_EXT = true, after_EXT = [RESPONSE1,
                                                              RESPONSE2,
                                                              DEFAULT]}];

The ascii compilation of this is:

1 - GATHER (request Keypad Input)
    1.1 - SAY "Press 1 for PRAISE. Press 2 for ABUSE. Do nothing for A SLAGGING"
    1.2 - end of Gather (wait for response)
    1.3 - Response 1 : PRAISE
        1.3.1 - SAY "My you are looking swish"
        1.3.2 - HANGUP
    1.4 - Response 2 : ABUSE
        1.4.1 - SAY "What you looking at, fannybaws?"  en-gb
        1.4.2 - HANGUP
    1.5 - Default : A SLAGGING
        1.5.1 - SAY "can you no use a phone, bawbag?"  de
        1.5.2 - HANGUP

Notice that by setting the option autoMenu_EXT to true an auto-menu is generated from the responses you have specified.

and the FSM version is:

[{"1",{{xml,"<Gather numDigits=\"1\">"},into}},
 {"1.1",
  {{xml,"<Say>Press 1 for PRAISE. Press 2 for ABUSE. Do nothing for A SLAGGING</Say>"},
   next}},
 {"1.2",{{xml,"</Gather>"},gather}},
 {"1.3",{{response_EXT,"1","Praise",[]},into}},
 {"1.3.1",{{xml,"<Say>My you are looking swish</Say>"},next}},
 {"1.3.2",{{xml,"<Hangup/>"},exit}},
 {"1.4",{{response_EXT,"2","Abuse",[]},into}},
 {"1.4.1",
  {{xml,"<Say language=\"en-gb\">What you looking at, fannybaws?</Say>"}, next}},
 {"1.4.2",{{xml,"<Hangup/>"},exit}},
 {"1.5",{{default_EXT,"a slagging",[]},next}},
 {"1.5.1",
  {{xml,"<Say language=\"de\">can you no use a phone, bawbag?</Say>"}, next}},
 {"1.5.2",{{xml,"<Hangup/>"},exit}}]

The part that makes it super-interesting is the ability to call out to Erlang fuctions natively. Recipe 12 gives the simplest version of this:

    [#function_EXT{title = "call out to function", module = 'twiml_ext_recipies',
                   fn = 'external_function'}]

This compiles to ascii as:

1 - Call out to CALL OUT TO FUNCTION (twiml_ext_recipies:external_function)
2 - HANGUP

At run time this calls the function twiml_ext_recipies:external_function/1 passing in the #state{} record from phonecall_srv.erl

The signature of the external function is very straightforward:

external_function(_State) ->
    {#twiml{}, [{Type, Fun/2}]}.

where Type is one of the following atoms:

recording
completion

It takes a single parameter and it returns a two part tuple, the most important part is the first element - some valid Extended TwiML.

This is compiled and inserted into the Finite State Machine replacing the call-out entry. The FSM is then revaluated.

The other parameter to be returned is a list of tuples that subscribe functions to callback events. The first paramater is the event that is being subscribed to, the second is a function of arity 2 to be called when the event happens.

Extended TwiML Usage

There are a number of steps to be gone through before using Extended TwiML:

Phonecall_srv.erl

The phone call supervision tree has a phonecall supervisor which spawns a process for all phone calls. This behaves slightly differently for inbound and outbound phone calls.

Inbound phone numbers MUST be set up with the callback status URL enabled in Twilio. This means that when a call is completed the application is notified by Twilio and the phonecall_srv can be sent a call complete message to terminate itself and clean up.

Outbound calls DO NOT receive a call complete or recording message and must clean up after themselves. An outbound call receives allready 'knows' the URL of its recording - so the simplest way to clean up after an outbound call is just to do a call complete call on it immediately after serving up the TwiML that makes the call.

Developers' Notes

Extended TwiML has the following api:

Other Material

Slides of a talk about Extended Twiml at the Erlang User Conference 2012:

http://www.docstoc.com/docs/document-preview.aspx?doc_id=128095466

Presentation at TechMeetup in Glasgow:

http://vimeo.com/47162182

License

Check out LICENSE.

Other

This library was written during Spawnfest, a 48 hour Erlang development competition.