The Call Tool is implemented as a Page plugin (Plugins::CallTool
). Every plugin instance has the
following fields:
targets
: json array that holds the targets information (the json contents are automatically mapped toCallTool::Target
instances)sound_clip
: paperclip attachment that holds a recorded audio file that should include a message and instructions from the campaigner about the campaign.menu_sound_clip
: paperclip attachment that holds a recorded audio file to be used as a menu during a call (Press 1 to connect, press 2 to hear the campaigner's message again)
To make phone calls we use an external service provided by Twilio. In order to know how to handle a particular call, Twilio makes request to our server and expects to return a Twiml file (xml). The returned Twiml file will hold the instructions that tell Twilio how to proceed with the call.
This is a simplified overview of the flow of a call:
-
A member visits a page that has the call tool plugin. He's presented with a form that will make a
POST /api/call
request to the backend sending these two params:target_id
andmember_phone_number
. -
The backend creates an instance of
Call
and makes a request to Twilio to initiate a call sending the member's phone number and the Twiml callback URL with instructions on how to handle the call. -
Twilio makes a call to the member and then immediately makes a request to
twilio/calls/:id/start
. Thestart
instructions (seeCallTool::TwimlGenerator::Start
) tell Twilio to play the sound clip with the campaigner's message and then is redirected totwilio/calls/:id/menu
. -
When Twilio makes a request to
twilio/calls/:id:/menu
the menu is played, and the key presses are captured. If the member presses 1 it's redirected totwilio/calls/:id/start
, and if the member presses 2 it's redirected to the next step attwilio/calls/:id/connect
. SeeCallTool::TwimlGenerator::Menu
. -
twilio/calls/:id/connect
tells twilio to make a call to the target's phone number and bridge it with the in-progress call to the member.
The status of a call is quite complex, since in the simplified flow I just described at any step something can go wrong in a couple of different ways.
The full state of a call is stored in three different places, and it's the combination of three different state machines.
- AR Call status: This the main status of the call, it's stored in
Call#status
as an enum. The possible statuses are:
:failed
: A request was made to Twilio to initiate a call and it responded with an error message. This usually means the number is invalid.:unstarted
: This means that the call was made to the member but either the line was busy, never picked up or the phone was disconnected.:started
: This means the member picked up the phone and started hearing the campaignerer's message:connected
: This means the member pressed 1 and got connected to the target.
-
Member call status: This holds the details of the actual phone call made to the member by Twilio. Twilio sends Champaign events when the status of a call changes, and they are stored in:
Call#member_call_events
. So in order to know the last state of a call we need to docall.member_call_events.last['CallStatus']
. The possible statuses are:completed
,answered
,busy
,no-answer
,failed
,canceled
andunknown
. To understand what each status means please check out Twilio's docs. I added an extra statusunknown
to describe the case when we don't receive a callback from Twilio. -
Target call status: This holds the details of the actual phone call made to the target by Twilio. This is sent in a callback request and stored in
Call#target_call_info['DialCallStatus']
. To know the possible states and their meanings please see Twilio's docs