Queues - Automatic Call Distribution

[Ref: Automatic Call Distribution (Queues) , Asterisk Admin Guide 2013, , Asterisk WIKI ]

Table of Contents
  1. Define
  2. Route Calls
  3. Dynamic Members

We use Queues for to help groups manage their incoming calls (distribution etc.) and more recently because we wanted to offer some services under different "labels."

For example, we wanted to have a dedicated phone numbers for our reception, sales department, and Support. The flexibility will allow us to publish a number that clients can dial that not only puts them into the department, but also connect them with the next available person to manage their query..

Unfortunately for us, the groups are not quite distinct and we have a few people serving in multiple groups. The following is a quick review of how we used Asterik's Queues, and messages to enable our desired solution.

Define

Following the above reference documentation we define a simple queue template ServiceQueue from which we derive our queues.

[ServiceQueue](!)
weight=0
wrapuptime=15
autopause=all
maxlen=4
announce-frequency=240
min-announce-frequency=15
relative-periodic-announce=yes
announce-position = no
ringinuse = no
periodic-announce = queues-thank-you-for-waiting-our-operators-are-currently-busy
(queues.conf)

The queue definition has a periodic-announce(ment) thanking callers for waiting in the queue.

An example audio message could be:

  • Thank you for waiting. Our operators are currently buys, and will get to you as soon as possible.

Since we're creating multiple queues, but this message is common to all, then it makes sense to put it in once.

The audio files "queues-thank-you-for-waiting-our-operators-are-currently-busy.XXX" (with extensions .gsm, .alaw, .ulaw etc. ) are stored in the $astdatadir (defined in /etc/asterisk/asterisk.conf) which is usually /usr/local/share/asterisk/

Our queue for Reception will have a unique announce which announces the call to the our staff when picking up a call.

  • "Phone call for XYZ" where XYZ is the company or Queue Grouping (e.g. Reception, Sales, etc.)

We also choose to explicitly set the members of the queue in our configuration file. Later we will show how we can have dynamic members.

Define: Reception Queue:

[Reception](ServiceQueue)
announce = queues-phone-call-for-XYZ-reception
member = SIP/6501
member = SIP/6502

Our queue for Sales and Support will have a different announcement, and a different member list.

Route Calls

To simplify dialplan routing for multiple Queues, we will use a macro for adding (inserting) calls into one of our chosen queues.

Very simply, we are going to take incoming calls to the imaginary phone numbers below, and route them into our new queues.

Incoming Phone NumberQueue
02-1111-6500Reception
02-1111-6510Sales
02-1111-6511Support
exten => _0211116500.,1,Goto(queue-insert,Reception,1)
exten => _0211116510.,1,Goto(queue-insert,Sales,1)
exten => _0211116511.,1,Goto(queue-insert,Support,1)
(extensions.conf)

Dynamic Members

To allow staff to dynamically enter, or exit, from a call queue we need to set up a dialplan. The below is one approach, using contexts defined further below.

exten => *9010, 1, Answer
  same => n, Gosub(queue-login, Sales,)
  same => n, Hangup()

exten => *9011, 1, Answer
  same => n, Goto(queue-logout, Sales)
  same => n, Hangup()

exten => *9012, 1, Answer
  same => n, Goto(queue-pause, Sales)
  same => n, Hangup()

Contexts

To simplify our dialplan, we liberally use [contexts] as functions/subroutines to jump processing to.

The context queue-insert context takes as the 1st argument ${ARG1} the name of the queue we want to insert the call into and then goes through some processing.

  • If we're open, then send the call to the appropriate queue
  • otherwise, process the close
[queue-insert]
exten => Reception,1,GotoIfTime(08:00-17:58,mon-fri,*,*?q-Reception,1)
    same => n,Playback(business-hours-between-0800-1800)
    same => n,Hangup()

The basic processing of a call, within the valid business hours, is to

  • play a message: thank you for calling XYZ ...
  • put the call into the queue
exten => q-Reception,1,Playback(queues-thank-you-for-calling-Company-one-of-our-friendly-staff)
    same => n,Queue(${queue},hHiIRtwkx,,,3000)  ; don't set n option until really needed
    same => n,Gosub(qjoin-${QUEUESTATUS},1)
    same => n,Hangup()

More Information:

[general]
persistentmembers = yes
monitor-type = MixMonitor

[ServiceQueue](!)
weight=0
wrapuptime=15
autopause=all
maxlen=4
announce-frequency=240
min-announce-frequency=15
relative-periodic-announce=yes
announce-position = no
ringinuse = no

[Reception](ServiceQueue)
announce = queues-phone-call-for-Company
periodic-announce = queues-thank-you-for-waiting-our-operators-are-currently-busy
member = SIP/6501
member = SIP/6502

[Sales](ServiceQueue)
announce = queues-phone-call-for-Sales
periodic-announce = queues-thank-you-for-waiting-our-operators-are-currently-busy
member = SIP/6579
member = SIP/6544

[Support](ServiceQueue)
announce = queues-phone-call-for-Support
periodic-announce = queues-thank-you-for-waiting-our-operators-are-currently-busy
member = SIP/6579
member = SIP/6553
(queues.conf)
[queue-insert]

exten => Reception,1,GotoIfTime(08:00-17:58,mon-fri,*,*?q-Reception,1)
    same => n,Goto(closed-${queue},1)
    same => n,Playback(business-hours-between-0800-1800)
    same => n,Hangup()

exten => Sales,1,GotoIfTime(08:00-17:58,mon-fri,*,*?q-Sales,1)
    same => n,Goto(closed-${queue},1)
    same => n,Playback(business-hours-between-0800-1800)
    same => n,Hangup()

exten => Support,1,GotoIfTime(08:00-17:58,mon-fri,*,*?q-Support,1)
    same => n,Goto(closed-${queue},1)
    same => n,Playback(business-hours-between-0800-1800)
    same => n,Hangup()

exten => q-Reception,1,Playback(queues-thank-you-for-calling-Company-one-of-our-friendly-staff)
    same => n,Queue(${queue},hHiIRtwkx,,,3000)  ; don't set n option until really needed
    same => n,Gosub(qinsert-${QUEUESTATUS},1)
    same => n,Hangup()

; ${QUEUESTATUS} Jump based on status (TIMEOUT, FULL, JOINEMPTY, LEAVEEMPTY, JOINUNAVAIL, LEAVEUNAVAIL, CONTINUE)  

exten => q-Sales,1,Playback(queues-thank-you-for-calling-Sales-one-of-our-friendly-staff)
    same => n,Queue(${queue},hHiIRtwkx,,,3000) 
    same => n,Gosub(qinsert-${QUEUESTATUS},1)
    same => n,Hangup()

exten => q-Support,1,Playback(queues-hello-and-thank-you-for-calling-Support-one-of-our-friendly-staff)
    same => n,Queue(${queue},hHiIRtwkx,,,3000)
    same => n,Gosub(qinsert-${QUEUESTATUS},1)
    same => n,Hangup()

exten => qinsert-TIMEOUT,1,Background(Msg Queue Timed Out)
    same => n,VoiceMail(${queue})
    same => n,Hangup()

exten => qinsert-FULL,1,Playback(Msg Queue is Full)
    same => n,Hangup()

exten => qinsert-JOINEMPTY,1,Playback(Msg Queue is closed)
    same => n,Hangup()

exten => qinsert-LEAVEEMPTY,1,Playback(Msg Queue is closed)
    same => n,Hangup()

exten => qinsert-JOINUNAVAIL,1,Playback(Msg Queue is closed)
    same => n,Hangup()

exten => qinsert-LEAVEUNAVAIL,1,Playback(Msg Queue is closed)
    same => n,Hangup()

exten => qinsert-CONTINUE,1,Playback(some_announce_after_leaving_queue)
    same => n,Hangup()

[queue-login]

exten => s,1,Set(queue=${ARG1})
    same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
    same => n,AddQueueMember(${queue},${MemberChannel})
    same => n,Gosub(qlogin-${AQMSTATUS},1)  ; Jump based on status (ADDED, MEMBERALREADY, NOSUCHQUEUE)
    same => n,Hangup()

exten => qlogin-ADDED,1,Background(agent-loginok)
    same => n,Hangup()  ; If they press #, return to start

exten => qlogin-MEMBERALREADY,1,Background(agent-alreadyon)
    same => n,Hangup()  ; If they press #, return to start

exten => qlogin-NOSUCHQUEUE,1,Background(pm-invalid-option)
    same => n,Hangup()

[queue-member-pause]

exten => s,1,Set(queue=${ARG1})
    same => n,NoOp(Queue PAUSE agent ${CHANNEL(peername)} to queue ${queue})
    same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
    same => n,PauseQueueMember(${queue},${MemberChannel})
    same => n,Gosub(qpause-${PQMSTATUS},1)  ; Jump based on status (PAUSED,NOTFOUND)
    same => n,Hangup()

exten => qpause-PAUSED,1,Background(dictate/paused)
    same => n,Hangup()  ; If they press #, return to start

exten => qpause-NOTFOUND,1,Background(pm-invalid-option)
    same => n,Hangup()  ; If they press #, return to start

[queue-member-unpause]

exten => s,1,Set(queue=${ARG1})
    same => n,NoOp(Queue PAUSE agent ${CHANNEL(peername)} to queue ${queue})
    same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
    same => n,UnPauseQueueMember(${queue},${MemberChannel})
    same => n,Gosub(qunpause-${UPQMSTATUS},1)  ; Jump based on status (UNPAUSED,NOTFOUND)
    same => n,Hangup()

exten => qunpause-UNPAUSED,1,Background(agent-loginok)
    same => n,Hangup()  ; If they press #, return to start

exten => qunpause-NOTFOUND,1,Background(pm-invalid-option)
    same => n,Hangup()  ; If they press #, return to start

[queue-member-logout]

exten => s,1,Set(queue=${ARG1})
    same => n,NoOp(Queue login agent ${CHANNEL(peername)} to queue ${queue})
    same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
    same => n,RemoveQueueMember(${queue},${MemberChannel})
    same => n,Gosub(qlogout-${RQMSTATUS},1)  ; Jump based on status (REMOVED, NOTINQUEUE, NOSUCHQUEUE)
    same => n,Hangup()

exten => qlogout-REMOVED,1,Background(agent-loggedoff)
    same => n,Hangup()  ; If they press #, return to start

exten => qlogout-NOTINQUEUE,1,Background(pm-invalid-option)
    same => n,Hangup()  ; If they press #, return to start

exten => qlogout-NOSUCHQUEUE,1,Background(pm-invalid-option)
    same => n,Hangup()
(contexts/queues)
[incoming_ITSP]
; (Sample) ITSP Supplied Number Range: 02-1111-6300 to 02-1111-6399

exten => _0211116300.,1,Goto(queue-insert,${FILTER(0-9,${EXTEN:0:10})},1(Reception))
exten => _0211116310.,1,Goto(queue-insert,${FILTER(0-9,${EXTEN:0:10})},1(Sales))
exten => _0211116311.,1,Goto(queue-insert,${FILTER(0-9,${EXTEN:0:10})},1(Support))

; -- Local Extensions (SIP devices) are accessible extensions 6320 ~ 6399
exten => _02111163XX.,1,Goto(localexten,${FILTER(0-9,${EXTEN:6:4})},1)

; (Sample) ITSP Supplied Number Range: 02-1111-4500 to 02-1111-4599 at friendpbx_1
exten => _02111145XX.,1,Goto(friendpbx_dial,${EXTEN:6:4},1(friendpbx_1,${GLOBAL(CONSOLE)}))

; (Sample) ITSP Supplied Number Range: 02-2222-4500 to 02-2222-4599 at friendpbx_2
;          at extensions 6800 to 6899
exten => _02111145XX.,1,Goto(friendpbx_dial,68${EXTEN:8:2},1(friendpbx_2,${GLOBAL(CONSOLE)}))
(extensions/itsp.source)
[queue-access]
; Agent logins
exten => *90, 1, Answer
  same => n, Set(CHANNEL(musicclass)=default)
  same => n,WaitMusicOnHold(20)
  same => n, Hangup()
  
exten => *9010, 1, Answer
  same => n, Macro(queue-login, Sales)
  same => n, Hangup()
  
exten => *9011, 1, Answer
  same => n, Macro(queue-logout, Sales)
  same => n, Hangup()
  
exten => *9012, 1, Answer
  same => n, Macro(queue-pause, Sales)
  same => n, Hangup()
  
exten => *9013, 1, Answer
  same => n, Macro(queue-unpause, Sales)
  same => n, Hangup()

exten => *9019, 1, Answer
  same => n, Goto(queues-join,${FILTER(0-9,${EXTEN:1:4})},1(Sales))
  same => n, Hangup()
  
exten => *9020, 1, Answer
  same => n, Macro(queue-login, Support)
  same => n, Hangup()
  
exten => *9021, 1, Answer
  same => n, Macro(queue-logout, Support)
  same => n, Hangup()
  
exten => *9022, 1, Answer
  same => n, Macro(queue-pause, Support)
  same => n, Hangup()
  
exten => *9023, 1, Answer
  same => n, Macro(queue-unpause, Support)
  same => n, Hangup()

exten => *9029, 1, Answer
  same => n, Goto(queues-join,${FILTER(0-9,${EXTEN:1:4})},1(Support))
  same => n, Hangup()
(extensions/services.queues)