Somleng — An Open Source Twilio Clone
Somleng is an Open Source Twilio Clone written in Ruby. It was originally developed as a drop-in replacement for Twilio for an mHealth project in Cambodia where new mothers receive a weekly call with information and health tips. In this particular project Somleng is connected to RapidPro — a GUI for creating and handing Call Flows. On the backend it’s connected to a local telco for terminating and receiving calls.
This article explains the technical details, design decisions and software components.
Software Components
Somleng consists of four main parts.
- Twilreapi — An Open Source implementation of Twilio’s REST API written in Rails.
- Somleng — An Open Source Adhearsion application which takes enqueued calls and delivers them via SIP.
- Adhearsion-Twilio — An Open Source Adhearsion Plugin which translates TwiML to Adhearsion Commands.
- FreeSWITCH — An Open Source soft switch which handles the low level SIP requests to the connected endpoint. Configuration is provided for connection to Somleng.
Technically FreeSWITCH can be swapped out for Asterisk since Adhearsion supports both.
Outbound Call Flow
Enqueuing Calls
Outbound calls are enqueued by sending requests to Twilreapi. Twilreapi tries to mimic Twilio’s REST API as closely as possible so that Twilio’s Helper Libraries can be used with Somleng with little or no modifications. For example here’s how to request an outbound call through Somleng using CURL as explained in the README.
curl -XPOST https://twilreapi-your-hostname.com/api/2010-04-01/Accounts/{AccountSID}/Calls.json \
-d "Method=GET" \
-d "Url=http://demo.twilio.com/docs/voice.xml" \
-d "To=%2B85512345678" \
-d "From=%2B85512345679" \
-u 'your_account_sid:your_auth_token'
Here’s how you enqueue a call using Twilio’s Python Library
from twilio.rest import TwilioRestClient
account = "ACXXXXXXXXXXXXXXXXX"
token = "YYYYYYYYYYYYYYYYYY"
client = TwilioRestClient(account, token, "https://twilreapi-your-hostname.com/api")
call = client.calls.create(to="9991231234",
from_="9991231234",
url="http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient")
Note that the Twilio Python library allows you to provide a third argument to TwilioRestClient in order to override the default Twilio REST API host.
Call Routing
Since routing calls is typically application specific, you can provide your routing logic by implementing Twilreapi::ActiveCallRouter. By default Twilreapi::ActiveCallRouter::Base#routing_instructions simply returns the source and destination that you initialize it with, however you can inherit from Twilreapi::ActiveCallRouter::Base and return your own routing instructions. These routing instructions will be passed to Somleng. For a concrete example see Twilreapi::ActiveCallRouter::PinCambodia.
Reading calls from the queue
Twilreapi supports multiple queues, including active-elastic-job and sidekiq. Calls are read from the queue by a Ruby worker process and then passed to Somleng via DRb.
Initiating Outbound Calls
After receiving a DRb request, Somleng tries to initiate an outbound call through Adhearsion.
Answered Calls
If the call is answered, Somleng’s CallController is executed. The call controller simply invokes the notify_voice_request_url method of Adhearsion-Twilio. This method makes a synchronous HTTP request to the Voice URL supplied in the original request and executes the returned TwiML. From here on the call is controlled via TwiML responses from your application just like Twilio.
Inbound Call Flow
FreeSWITCH
Inbound calls are first received by FreeSWITCH then passed to Somleng with the default dialplan for Adhearsion.
<!-- conf/dialplan.xml-->
<include>
<context name="default">
<extension name="adhearsion">
<condition>
<action application="rayo"/>
</condition>
</extension>
</context>
</include>
Somleng
Somleng then executes the same CallController that it does for Outbound Calls. The call is once again now controlled by TwiML responses from your application.
Call Data Records
By default, mod-json-cdr is installed as a module in the FreeSWITCH docker image. You can configure mod-json-cdr to POST to https://twilreapi-your-hostname.com/api/admin/call_data_records in order to save CDRs.
Deployment
Twilreapi
Twilreapi can be deployed to an AWS Elastic Beanstalk Rails Application and connected to a Postgresql RDS instance. In our setup we have one small web instance running (with load balancing and auto-scaling) and two nano worker instances. One worker instance is for reading calls from the queue and delivering them to Somleng via DRb. The other worker instance is for processing incoming CDRs and uploading them to S3.
Somleng
Somleng can be deployed to an AWS Elastic Beanstalk Multi-Container Docker Application. In our setup it’s deployed to the on the same VPC as Twilreapi but on private subnets connected to an internal load balancer. The internal load balancer is setup to receive DRb requests on port 9050. There is a Docker port mapping for TCP port 9050 for handling incoming DRb requests.
FreeSWITCH
FreeSWITCH can also be deployed to an AWS Elastic Beanstalk Multi-Container Docker Application. In our setup it’s deployed as a single instance application with an elastic IP address. There are Docker port mappings for TCP port 5222 for handling incoming requests from Rayo (Adhearsion) and UDP port 5060 for handling incoming SIP requests.
Configuration
Twilreapi is configured through environment variables set using the AWS Elastic Beanstalk console.
Somleng is also configured through environment variables, however the environment variables are contained in a file and uploaded to a bucket on S3. When Docker starts the container it pulls the environment variables from S3 and sets them in the container.
Custom FreeSWITCH configuration can also be uploaded to a bucket on S3. When Docker starts the container it will pull the FreeSWITCH configuration from S3 before starting FreeSWITCH.
Disclaimer
Somleng is a work in progress. It currently only supports a fraction of Twilio’s features. Please use with this in mind.