This Number Rocks, Rocking Out with Elixir Lang

March 16, 2017
Written by


Henri Morlaye is a developer in Leon, France. He works for a small company there that builds tools for professionals to facilitate innovative instructive and training environments at their companies.

Until recently, Henri and his team relied on a landline as the main phone system for their company. While searching for a way to modernize, they decided to build a solution themselves with Twilio that would allow for both more flexibility and control.

The team took the simple concept of a Twimlet but decided to give it their own flair. Using Elixir with the Twilio REST API, they were quickly up and calling!

They built the CRUD app with the Phoenix Framework and deployed on Heroku. A Postgresql database table keeps track of the Twilio numbers and phone numbers they point to, stored as embedded structs using an array of maps and Elixir’s Ecto embedded schema.

All Twilio calls are routed to two functions which determine what to do if there is an incoming SMS or a call.  The function for SMS sends an email and the function for a call will try in turn each “real” phone number associated with the “virtual” Twilio number.

A call comes in:

def incoming(conn, %{"To" => to, "From" => from}) do 
  try_real_number conn, to, from, 0

The try_real_number private function looks for the called number in the database, tries to dial a “real” number – Henri’s phone for example – and sets an HTTP callback to Twilio keeping track of the current trial index. If no one answers, or a call comes in outside of open hours, a TwiML <Record> instruction is sent back to Twilio.

defp try_real_number(conn, to, from, real_number_index) do
  case get_number(to) do
    nil ->
      answer conn, ~s{<Say language="en">Unknown number</Say>}
    number ->
      if Enum.count(number.real_numbers) < (real_number_index + 1) do
        answer conn, ~s{
          <Record timeout="10"/>
        real_number = <a href=""></a>(number.real_numbers, real_number_index)
        if <a href=""></a>_opened?(real_number) do 
          callback = @root_url <> twilio_path(conn, :callback, real_number_index)
          answer conn, ~s{
            <Dial timeout="10" action="#{callback}" method="POST">
          try_real_number conn, to, from, real_number_index + 1

The callback action URL provided in the <Dial> instruction uses destructuring to run different routines according to the type of action:

  • Voicemail recording notifications are forwarded to the other user by email.
  • Completed calls are finished.
  • Unanswered call attempts trigger a new call to try_real_number with the next “real” number in the list.

def callback(conn, %{"real_number_index" => real_number_index, "From" => from, "To" => to, "RecordingUrl" => url, "RecordingDuration" => duration}) do    
  number = get_number(to)    
  if number, do: UserMailer.voicemail(number, from, duration, url)  
  answer conn, ~s{<Hangup/>}
def callback(conn, %{"real_number_index" => real_number_index, "From" => from, "To" => to, "DialCallStatus" => status}) do    
  case status do
    "completed" ->
      answer conn, ~s{<Hangup/>}
      try_real_number conn, to, from, String.to_integer(real_number_index) + 1

The answer function just wraps an XML snippet in a Twilio response:

defp answer(conn, xml) do
  response = """
  <?xml version="1.0" encoding="UTF-8"?>
  |> put_resp_header("Content-Type", "text/xml")
  |> resp(:ok, response)

Without a helper library for Elixir, Henri said he relied heavily on the logging system in the Console. “It was really a piece of cake!”

This Number Rocks

Henri created the solution with their own use in mind. “It did not require a lot of work. It was perfect,” he said. But after sharing it with friends in the local startup community, people loved it and wanted to start using it too. “Having a Twilio number could bring a lot of benefits to them as it does for us.” They set up to make it so others, even without technical knowledge, could also have a fancy call forwarding system.



Whether you’re inspired to start building your own solution from scratch or following the tutorial to get started on is more your style, we’re always happy to hear what you’re up to.

Feel free to drop a line to: