Call Center
Support for defining call center workflows.
Overview
Call Center streamlines the process of defining multi-party call workflows in your application. Particularly, with Twilio in mind.
Twilio provides a two-part API for managing phone calls, and is mostly driven by callbacks. Call Center DRYs up the application logic dealing with a callback driven API so you can focus on the business logic of your call center.
Usage
class Call
include CallCenter
call_flow :state, :intial => :incoming do
actor :customer do |call, event|
"/voice/calls/flow?event=#{event}&actor=customer&call_id=#{call.id}"
end
state :incoming do
response do |x|
x.Gather :numDigits => '1', :action => customer(:wants_voicemail) do
x.Say "Hello World"
x.Play some_nice_music, :loop => 100
end
# <?xml version="1.0" encoding="UTF-8" ?>
# <Response>
# <Gather numDigits="1" action="/voice/calls/flow?event=wants_voicemail&actor=customer&call_id=5000">
# <Say>Hello World</Say>
# <Play loop="100">http://some.nice.music.com/1.mp3</Play>
# </Gather>
# </Response>
end
event :called, :to => :routing, :if => :agents_available?
event :called, :to => :voicemail
event :wants_voicemail, :to => :voicemail
event :customer_hangs_up, :to => :cancelled
end
state :voicemail do
response do |x|
x.Say "Please leave a message"
x.Record(:action => customer(:voicemail_complete))
# <?xml version="1.0" encoding="UTF-8" ?>
# <Response>
# <Say>Please leave a message</Say>
# <Record action="/voice/calls/flow?event=voicemail_complete&actor=customer&call_id=5000"/>
# </Response>
end
event :voicemail_complete, :to => :voicemail_completed
event :customer_hangs_up, :to => :cancelled
end
state :routing do
end
end
end
Benefits of CallCenter is that it's backed by state_machine. Which means you can interact with events the same you do in state_machine
.
@call.called!
@call.wants_voicemail!
@call.routing?
@call.render # See Rendering
Flow
The general application flow for a CallFlow is like this:
- An incoming call is posted to your application
- You create a Call
- You execute an initial event
- You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects
- Something happens and Twilio posts an event to your application
- You find the Call
- You store any new information
- You execute the posted event
- You respond by rendering TwiML. Your TwiML contains callbacks to events or redirects
- Repeat 2.
In order to DRY up the callbacks, it is best to have use standardized callback URLs. A handy concept is the flow_url
, which follows the pattern:
http://domain.com/calls/flow?call_id=?&event=?
By handling this in your controller, you can immediately retrieve the Call from persistence, run an event on the call, and return the rendered TwiML. Here's an example:
def flow
render :xml => @call.run(params[:event])
end
For an in-depth example, take a look at call_roulette.
Rendering
Rendering is your way of interacting with Twilio. Thus, it provides two facilities: access to an XML builder and access to your call.
state :sales do
response do |xml_builder, the_call|
xml_builder.Say "This is #{the_call.agent.name}!" # Or agent.name, you can access he call implicitly
end
end
Renders with @call.render
if the current state is :sales:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>This is Henry!</Say>
</Response>
Callbacks
You have control over what you want to happen before/after state transitions:
state :voicemail do
before(:always) { # Invokes before any transition }
before(:always, :uniq => true) { # Invokes before transitions to a different state }
after(:always) { # Invokes after any transition }
after(:success) { # Invokes after any successful transition }
after(:failure) { # Invokes after any failed transition (those not covered in your call flow) }
after(:always, :uniq => true) { # Invokes after any transition to a different state }
after(:success, :uniq => true) { # Successful unique transitions }
after(:failure, :uniq => true) { # Failed unique transitions }
end
For example,
state :voicemail do
before(:always) { log_start_event }
after(:always) { log_end_event }
after(:failure) { notify_airbrake }
after(:success, :uniq => true) { notify_browser }
after(:failure, :uniq => true) { notify_cleanup_browser }
end
Motivation
Not DRY
Twilio requests your application to return TwiML that describes the call workflow. TwiML contains commands which Twilio then executes. It is essentially an application-to-application API, synonymous to making a REST call.
In the context of "Skinny Controller, Fat Model", outgoing REST calls for the function of business logic are not a view concern but a model concern. Therefore, so is TwiML.
Twilio supports callbacks URLs and redirects to URLs that also render TwiML as a method of modifying live calls. Incoming callbacks are handled by the controller first, but the response is still a model concern.
Terminology
- Call - An application resource of yours that encapsulates a phone call. Phone calls are then acted on: answered, transferred, declined, etc.
- Event - Is something that happens outside or inside your application in relation to a Call. Someone picks up, hangs up, presses a button, etc. But overall, it's anything that can be triggered by Twilio callbacks.
- State - Is the status a Call is in which is descriptive of what's happened so far and what are the next things that should happen. (e.g. a call on hold is waiting for the agent to return)
- CallFlow - Is a definition of the process a Call goes through. Events drive the flow between States. (e.g. a simple workflow is when noone answers the call, send the call to voicemail)
- Render - Is the ability of the CallFlow to return TwiML to bring the call into the State or modify the live call through a Redirect.
- Redirect - Is a way of modifying a live call outside of a TwiML response (e.g. background jobs)
Tools
Drawing
Should you be interested in what your call center workflow looks like, you can draw.
Call.state_machines[:status].draw(:font => 'Helvetica Neue')
# OR
@call.draw_call_flow(:font => 'Helvetica Neue')
Future
- Integrate making new calls into the CallFlow DSL