Chapter 13. Automatic Call Distribution (ACD) Queues

An Englishman, even if he is alone, forms an orderly queue of one.

George Mikes

Automatic Call Distribution (ACD), or call queuing, provides a way for a PBX to queue up incoming calls from a group of users: it aggregates multiple calls into a holding pattern and assigns each call a rank that determines the order in which that call should be delivered to an available agent (typically, first in first out). When an agent becomes available, the highest-ranked caller in the queue is delivered to that agent, and everyone else moves up a rank.

If you have ever called an organization and heard “all of our representatives are busy,” you have experienced ACD. The advantage of ACD to the callers is that they don’t have to keep dialing back in an attempt to reach someone, and the advantages to the organizations are that they are able to better service their customers and to temporarily handle situations where there are more callers than there are agents.[126]

Note

There are two types of call centers: inbound and outbound. ACD refers to the technology that handles inbound call centers, whereas the term Predictive Dialer refers to the technology that handles outbound call centers. In this book we will primarily focus on inbound calling.

We’ve all been frustrated by poorly designed and managed queues: enduring hold music from a radio that isn’t in tune, mind-numbing wait times, and pointless messages that tell you every 20 seconds how important your call is, despite that fact that you’ve been waiting for 30 minutes and have heard the message so many times you can quote it from memory. From a customer service perspective, queue design may be one of the most important aspects of your telephone system. As with an automated attendant, what must be kept in mind above all else is that your callers are not interested in holding in a queue. They called because they want to talk to you. All your design decisions must keep this crucial fact front-and-center in your mind: people want to talk to other people; not to your phone system.[127]

The purpose of this chapter is to teach you how to create and design queues that get callers to their intended destinations as quickly and painlessly as possible.

Note

In this chapter, we may flip back and forth between the usage of the terms queue members and agents. Unless we are talking about agents logged in via chan_agent (using AgentLogin()), we’re almost certainly talking about queue members as added via AddQueueMember() or the CLI commands (which we’ll discuss in this chapter). Just know that there is a difference in Asterisk between an agent and a queue member, but that we’ll use the term agent loosely to simply describe an endpoint as called by a Queue().

Creating a Simple ACD Queue

To start with, we’re going to create a simple ACD queue. It will accept callers and attempt to deliver them to a member of the queue.

Note

In Asterisk, the term member refers to a peer assigned to a queue that can be dialed, such as SIP/0000FFFF0001. An agent technically refers to the Agent channel also used for dialing endpoints. Unfortunately, the Agent channel is a deprecated technology in Asterisk, as it is limited in flexibility and can cause unexpected issues that can be hard to diagnose and resolve. We will not be covering the use of chan_agent, so be aware that we will generally use the term member to refer to the telephone device and agent to refer to the person who handles the call. Since one isn’t generally effective without the other, either term may refer to both.

We’ll create the queue(s) in the queues.conf file, and manually add queue members to it through the Asterisk console. In the section Queue Members, we’ll look into how to create a dialplan that allows us to dynamically add and remove queue members (as well as pause and unpause them).

The first step is to create your queues.conf file in the /etc/asterisk configuration directory:

$ cd /etc/asterisk/
$ touch queues.conf

Populate it with the following configuration, which will create two queues named [sales] and [support]. You can name them anything you want, but we will be using these names later in the book, so if you use different queue names from what we’ve recommended here, make note of your choices for future reference:

[general]
autofill=yes             ; distribute all waiting callers to available members
shared_lastcall=yes      ; respect the wrapup time for members logged into more 
                         ; than one queue

[StandardQueue](!)       ; template to provide common features
musicclass=default       ; play [default] music
strategy=rrmemory        ; use the Round Robin Memory strategy
joinempty=no             ; do not join the queue when no members available
leavewhenempty=yes       ; leave the queue when no members available
ringinuse=no             ; don't ring members when already InUse (prevents 
                         ; multiple calls to an agent)

[sales](StandardQueue)   ; create the sales queue using the parameters in the
                         ; StandardQueue template

[support](StandardQueue) ; create the support queue using the parameters in the
                         ; StandardQueue template

The [general] section defines the default behavior and global options. We’ve only specified two options in the [general] section, since the built-in defaults are sufficient for our needs at this point.

The first option is autofill, which tells the queue to distribute all waiting callers to all available members immediately. Previous versions of Asterisk would only distribute one caller at a time, which meant that while Asterisk was signaling an agent, all other calls were held (even if other agents were available) until the first caller in line had been connected to an agent (which obviously led to bottlenecks in older versions of Asterisk where large, busy queues were being used). Unless you have a particular need for backward-compatibility, this option should always be set to yes.

The second option in the [general] section of queues.conf is shared_lastcall. When we enable shared_lastcall, the last call to an agent who is logged into multiple queues will be the call that is counted for wrapup time[128] in order to avoid sending a call to an agent from another queue during the wrap period. If this option is set to no, the wrap timer will only apply to the queue the last call came from, which means an agent who was wrapping up a call from the support queue might still get a call from the sales queue. This option should also always be set to yes (the default).

The next section, [StandardQueue] is the template we’ll apply to our sales and support queues (we declared it a template by adding (!)). We’ve defined the musicclass to be the default music on hold, as configured in the musiconhold.conf file. The strategy we’ll employ is rrmemory, which stands for Round-Robin with Memory. The rrmemory strategy works by rotating through the agents in the queue in sequential order, keeping track of which agent got the last call, and presenting the next call to the next agent. When it gets to the last agent, it goes back to the top (as agents log in, they are added to the end of the list). We’ve set joinempty to no since it is generally bad form to put callers into a queue where there are no agents available to take their calls.

Note

You could set this to yes for ease of testing, but we would not recommend putting it into production unless you are using the queue for some function that is not about getting your callers to your agents. Nobody wants to wait in a line that is not going anywhere.

The leavewhenempty option is used to control whether callers should fall out of the Queue() application and continue on in the dialplan if no members are available to take their calls. We’ve set this to yes because it makes no sense to wait in a line that’s not going anywhere.

Note

From a business perspective, you should be telling your agents to clear all calls out of the queue before logging off for the day. If you find that there are a lot of calls queued up at the end of the day, you might want to consider extending someone’s shift to deal with them. Otherwise, they’ll just add to your stress when they call back the next day, in a worse mood.

The alternative is to use GotoIfTime() near the end of the day to redirect callers to voicemail, or some other appropriate location in your dialplan.

Finally, we’ve set ringinuse to no, which tells Asterisk not to ring members when their devices are already ringing. The purpose of setting ringinuse to no is to avoid multiple calls to the same member from one or more queues.

Note

It should be mentioned that joinempty and leavewhenempty are looking for either no members logged into the queue, or all members unavailable. Agents that are Ringing or InUse are not considered unavailable, so will not block callers from joining the queue or cause them to be kicked out when joinempty=no and/or leavewhenempty=yes.

Once you’ve finished configuring your queues.conf file, you can save it and reload the app_queue.so module from your Asterisk CLI:

$ asterisk -r
*CLI> module reload app_queue.so
   -- Reloading module 'app_queue.so' (True Call Queueing)

Then verify that your queues were loaded into memory:

localhost*CLI> queue show
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   No Members
   No Callers

sales        has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   No Members
   No Callers

Now that you’ve created the queues, you need to configure your dialplan to allow calls to enter the queue.

Add the following dialplan logic to the extensions.conf file:

[Queues]
exten => 7001,1,Verbose(2,${CALLERID(all)} entering the support queue)
same => n,Queue(support)
same => n,Hangup()

exten => 7002,1,Verbose(2,${CALLERID(all)} entering the sales queue)
same => n,Queue(sales)
same => n,Hangup()

[LocalSets]
include => Queues      ; allow phones to call queues

We’ve included the Queues context in the LocalSets context so that our telephones can call the queues we’ve set up. In Chapter 15, we’ll define menu items that go to these queues. Save the changes to your extensions.conf file, and reload the dialplan with the dialplan reload CLI command.

If you dial extension 7001 or 7002 at this point, you will end up with output like the following:

 -- Executing [7001@LocalSets:1] Verbose("SIP/0000FFFF0003-00000001", 
    "2,"Leif Madsen" <100> entering the support queue") in new stack
== "Leif Madsen" <1--> entering the support queue
 -- Executing [7001@LocalSets:2] Queue("SIP/0000FFFF0003-00000001", 
    "support") in new stack
    [2011-02-14 08:59:39] WARNING[13981]: app_queue.c:5738 queue_exec: 
    Unable to join queue 'support'
 -- Executing [7001@LocalSets:3]
    Hangup("SIP/0000FFFF0003-00000001", "") in new stack
 == Spawn extension (LocalSets, 7001, 3) exited non-zero on 
    'SIP/0000FFFF0003-00000001'

You don’t join the queue at this point, as there are no agents in the queue to answer calls. Because we have joinempty=no and leavewhenempty=yes configured in queues.conf, callers will not be placed into the queue. (This would be a good opportunity to experiment with the joinempty and leavewhenempty options in queues.conf to better understand their impact on queues.)

In the next section, we’ll demonstrate how to add members to your queue (as well as other member interactions with the queue, such as pause/unpause).

Queue Members

Queues aren’t very useful without someone to answer the calls that come into them, so we need a method for allowing agents to be logged into the queues to answer calls. There are various ways of going about this, and we’ll show you how to add members to the queue both manually (as an administrator) and dynamically (as the agent). We’ll start with the Asterisk CLI method, which allows you to easily add members to the queue for testing and minimal dialplan changes. We’ll then expand upon that, showing you how to add dialplan logic allowing agents to log themselves into and out of the queues and to pause and unpause themselves in queues they are logged into.

Controlling Queue Members via the CLI

We can add queue members to any available queue through the Asterisk CLI command queue add. The format of the queue add command is (all on one line):

*CLI> queue add member <channel> to <queue> [[[penalty <penalty>] as 
<membername>] state_interface <interface>]

The <channel> is the channel we want to add to the queue, such as SIP/0000FFFF0003, and the <queue> name will be something like support or sales—any queue name that exists in /etc/asterisk/queues.conf. For now we’ll ignore the <penalty> option, but we’ll discuss it in Advanced Queues (penalty is used to control the rank of a member within a queue, which can be important for agents who are logged into multiple queues). We can define the <membername> to provide details to the queue-logging engine. The state_interface option is something that we should delve a bit more into at this junction. Because it is so important for all aspects of queues and their members in Asterisk, we’ve written a little section about it, so go ahead and read An Introduction to Device State. Once you’ve set that up, come back here and continue on. Don’t worry, we’ll wait.

Now that you’ve added callcounter=yes to sip.conf (we’ll be using SIP channels throughout the rest of our examples), let’s see how to add members to our queues from the Asterisk CLI.

Adding a queue member to the support queue can be done with the queue add member command:

*CLI> queue add member SIP/0000FFFF0001 to support
Added interface 'SIP/0000FFFF0001' to queue 'support'

A query of the queue will verify that our new member has been added:

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (Not in use) has taken no calls yet
   No Callers

To remove a queue member, you would use the queue remove member command:

*CLI> queue remove member SIP/0000FFFF0001 from support 
Removed interface 'SIP/0000FFFF0001' from queue 'support'

Of course, you can use the queue show command again to verify that your member has been removed from the queue.

We can also pause and unpause members in a queue from the Asterisk console, with the queue pause member and queue unpause member commands. They take a similar format to the previous commands we’ve been using:

*CLI> queue pause member SIP/0000FFFF0001 queue support reason DoingCallbacks
paused interface 'SIP/0000FFFF0001' in queue 'support' for reason 'DoingCallBacks'

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (paused) (Not in use) has taken no calls yet
   No Callers

By adding a reason for pausing the queue member, such as lunchtime, you ensure that your queue logs will contain some additional information that may be useful. Here’s how to unpause the member:

*CLI> queue unpause member SIP/0000FFFF0001 queue support reason off-break
unpaused interface 'SIP/0000FFFF0001' in queue 'support' for reason 'off-break'

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (Not in use) has taken no calls yet
   No Callers

In a production environment, the CLI would not normally be the best way to control the state of agents in a queue. Instead, there are dialplan applications that allow agents to inform the queue as to their availability.

Controlling Queue Members with Dialplan Logic

In a call center staffed by live agents, it is most common to have the agents themselves log in and log out at the start and end of their shifts (or whenever they go for lunch, or to the bathroom, or are otherwise not available to the queue).

To enable this, we will make use of the following dialplan applications:

  • AddQueueMember()

  • RemoveQueueMember()

While logged into a queue, it may be that an agent needs to put herself into a state where she is temporarily unavailable to take calls. The following applications will allow this:

  • PauseQueueMember()

  • UnpauseQueueMember()

It may be easier to think of these applications in the following manner: the add and remove applications are used to log in and log out, and the pause/unpause pair are used for short periods of agent unavailability. The difference is simply that pause/unpause set the member as unavailable/available without actually removing them from the queue. This is mostly useful for reporting purposes (if a member is paused, the queue supervisor can see that she is logged into the queue, but simply not available to take calls at that moment). If you’re not sure which one to use, we recommend that the agents use add/remove whenever they are not going to be available to take calls.

Let’s build some simple dialplan logic that will allow our agents to indicate their availability to the queue. We are going to use the CUT() dialplan function to extract the name of our channel from our call to the system, so that the queue will know which channel to log into the queue.

We have built this dialplan to show a simple process for logging into and out of a queue, and changing the paused status of a member in a queue. We are doing this only for a single queue that we previously defined in the queues.conf file. The status channel variables that the AddQueueMember(), RemoveQueueMember(), PauseQueueMember(), and UnpauseQueueMember() applications set might be used to Playback() announcements to the queue members after they’ve performed certain functions to let them know whether they have successfully logged in/out or paused/unpaused):

[QueueMemberFunctions]

exten => *54,1,Verbose(2,Logging In Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,AddQueueMember(support,${MemberChannel})

; ${AQMSTATUS}
;   ADDED
;   MEMBERALREADY
;   NOSUCHQUEUE

exten => *56,1,Verbose(2,Logging Out Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,RemoveQueueMember(support,${MemberChannel})

; ${RQMSTATUS}:
;    REMOVED
;    NOTINQUEUE
;    NOSUCHQUEUE

exten => *72,1,Verbose(2,Pause Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,PauseQueueMember(support,${MemberChannel})

; ${PQMSTATUS}:
;     PAUSED
;     NOTFOUND

exten => *87,1,Verbose(2,Unpause Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,UnpauseQueueMember(support,${MemberChannel})

; ${UPQMSTATUS}:
;     UNPAUSED
;     NOTFOUND

Automatically Logging Into and Out of Multiple Queues

It is quite common for an agent to be a member of more than one queue. Rather than having a separate extension for logging into each queue (or demanding information from the agents about which queues they want to log into), this code uses the Asterisk database (astdb) to store queue membership information for each agent, and then loops through each queue the agents are a member of, logging them into each one in turn.

In order to for this code to work, an entry similar to the following will need to be added to the AstDB via the Asterisk CLI. For example, the following would store the member 0000FFFF0001 as being in both the support and sales queues:

*CLI> database put queue_agent 0000FFFF0001/available_queues support^sales

You will need to do this once for each agent, regardless of how many queues they are members of.

If you then query the Asterisk database, you should get a result similar to the following:

pbx*CLI> database show queue_agent
/queue_agent/0000FFFF0001/available_queues        : support^sales

The following dialplan code is an example of how to allow this queue member to be automatically added to both the support and sales queues. We’ve defined a subroutine that is used to set up three channel variables (MemberChannel, MemberChanType, AvailableQueues). These channel variables are then used by the login (*54), logout (*56), pause (*72), and unpause (*87) extensions. Each of the extensions uses the subSetupAvailableQueues subroutine to set these channel variables and to verify that the AstDB contains a list of one or more queues for the device the queue member is calling from:

[subSetupAvailableQueues]
;
; This subroutine is used by the various login/logout/pausing/unpausing routines
; in the [ACD] context. The purpose of the subroutine is centralize the retrieval 
; of information easier.
;
exten => start,1,Verbose(2,Checking for available queues)

; Get the current channel's peer name (0000FFFF0001)
   same => n,Set(MemberChannel=${CHANNEL(peername)})

; Get the current channel's technology type (SIP, IAX, etc)
   same => n,Set(MemberChanType=${CHANNEL(channeltype)})    

; Get the list of queues available for this agent
   same => n,Set(AvailableQueues=${DB(queue_agent/${MemberChannel}/
   available_queues)})
; *** This should all be on a single line

; if there are no queues assigned to this agent we'll handle it in the 
; no_queues_available extension
   same => n,GotoIf($[${ISNULL(${AvailableQueues})}]?no_queues_available,1) 
                                                                            
   same => n,Return()

exten => no_queues_available,1,Verbose(2,No queues available for agent 
   ${MemberChannel})
; *** This should all be on a single line

; playback a message stating the channel has not yet been assigned
   same => n,Playback(silence/1&channel&not-yet-assigned)  
   same => n,Hangup()

[ACD]
;
; Used for logging agents into all configured queues per the AstDB
;
;
; Logging into multiple queues via the AstDB system
exten => *54,1,Verbose(2,Logging into multiple queues per the database values)

; get the available queues for this channel
   same => n,GoSub(subSetupAvailableQueues,start,1())  
   same => n,Set(QueueCounter=1)  ; setup a counter variable

; using CUT(), get the first listed queue returned from the AstDB
   same => n,Set(WorkingQueue=${CUT(AvailableQueues,^,${QueueCounter})})

; While the WorkingQueue channel variable contains a value, loop
   same => n,While($[${EXISTS(${WorkingQueue})}])

; AddQueueMember(queuename[,interface[,penalty[,options[,membername
;  [,stateinterface]]]]])
; Add the channel to a queue, setting the interface for calling 
; and the interface for monitoring of device state
;
; *** This should all be on a single line
   same => n,AddQueueMember(${WorkingQueue},${MemberChanType}/
${MemberChannel},,,${MemberChanType}/${MemberChannel})


   same => n,Set(QueueCounter=$[${QueueCounter} + 1])    ; increase our counter

; get the next available queue; if it is null our loop will end
   same => n,Set(WorkingQueue=${CUT(AvailableQueues,^,${QueueCounter})})

   same => n,EndWhile()

; let the agent know they were logged in okay
   same => n,Playback(silence/1&agent-loginok)
   same => n,Hangup()

exten => no_queues_available,1,Verbose(2,No queues available for ${MemberChannel})
   same => n,Playback(silence/1&channel&not-yet-assigned)
   same => n,Hangup()

; -------------------------

; Used for logging agents out of all configured queues per the AstDB
exten => *56,1,Verbose(2,Logging out of multiple queues)

; Because we reused some code, we've placed the duplicate code into a subroutine
   same => n,GoSub(subSetupAvailableQueues,start,1())   
   same => n,Set(QueueCounter=1)
   same => n,Set(WorkingQueue=${CUT(AvailableQueues,^,${QueueCounter})})
   same => n,While($[${EXISTS(${WorkingQueue})}])
   same => n,RemoveQueueMember(${WorkingQueue},${MemberChanType}/${MemberChannel})
   same => n,Set(QueueCounter=$[${QueueCounter} + 1])
   same => n,Set(WorkingQueue=${CUT(AvailableQueues,^,${QueueCounter})})
   same => n,EndWhile()
   same => n,Playback(silence/1&agent-loggedoff)
   same => n,Hangup()

; -------------------------

; Used for pausing agents in all available queues
exten => *72,1,Verbose(2,Pausing member in all queues)
   same => n,GoSub(subSetupAvailableQueues,start,1())

   ; if we don't define a queue, the member is paused in all queues
   same => n,PauseQueueMember(,${MemberChanType}/${MemberChannel})
   same => n,GotoIf($[${PQMSTATUS} = PAUSED]?agent_paused,1:agent_not_found,1)

exten => agent_paused,1,Verbose(2,Agent paused successfully)
   same => n,Playback(silence/1&unavailable)
   same => n,Hangup()

; -------------------------

; Used for unpausing agents in all available queues
exten => *87,1,Verbose(2,UnPausing member in all queues)
   same => n,GoSub(subSetupAvailableQueues,start,1())

   ; if we don't define a queue, then the member is unpaused from all queues
   same => n,UnPauseQueueMember(,${MemberChanType}/${MemberChannel})
   same => n,GotoIf($[${UPQMSTATUS} = UNPAUSED]?agent_unpaused,1:agent_not_found,1)

exten => agent_unpaused,1,Verbose(2,Agent paused successfully)
   same => n,Playback(silence/1&available)
   same => n,Hangup()

; -------------------------

; Used by both pausing and unpausing dialplan functionality
exten => agent_not_found,1,Verbose(2,Agent was not found)
   same => n,Playback(silence/1&cannot-complete-as-dialed)

You could further refine these login and logout routines to take into account that the AQMSTATUS and RQMSTATUS channel variables are set each time AddQueueMember() and RemoveQueueMember() are used. For example, you could set a flag that lets the queue member know he has not been added to a queue by setting a flag, or even add recordings or text-to-speech systems to play back the particular queue that is producing the problem. Or, if you’re monitoring this via the Asterisk Manager Interface, you could have a screen pop, or use JabberSend() to inform the queue member via instant messaging. (Sorry, sometimes our brains run away with us.)

An Introduction to Device State

Device states in Asterisk are used to inform various applications as to whether your device is currently in use or not. This is especially important for queues, as we don’t want to send callers to an agent who is already on the phone. Device states are controlled by the channel module, and in Asterisk only chan_sip has the appropriate handling. When the queue asks for the state of a device, it first queries the channel driver (e.g., chan_sip). If the channel cannot provide the device state directly (as is the case with chan_iax2), it asks the Asterisk core to determine it, which it does by searching through channels currently in progress.

Unfortunately, simply asking the core to search through active channels isn’t accurate, so getting device state from channels other than chan_sip is less reliable when working with queues. We’ll explore some methods of controlling calls to other channel types in Advanced Queues, but for now we’ll focus on SIP channels, which do not have complex device state requirements. For more information about device states, see Chapter 14.

In order to correctly determine the state of a device in Asterisk, we need to enable call counters in sip.conf. By enabling call counters, we’re telling Asterisk to track the active calls for a device so that this information can be reported back to the channel module and the state can be accurately reflected in our queues. First, let’s see what happens to our queue without the callcounter option:

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (Not in use) has taken no calls yet
   No Callers

Now suppose we have an extension in our dialplan, 555, that calls MusicOnHold(). If we dial that extension without having enabled call counters, a query of the support queue (of which SIP/0000FFFF0001 is a member) from the Asterisk CLI will show something similar to the following:

    -- Executing [555@LocalSets:1] MusicOnHold("SIP/0000FFFF0001-00000000", 
       "") in new stack
    -- Started music on hold, class 'default', on SIP/0000FFFF0001-00000000

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (Not in use) has taken no calls yet
   No Callers

Notice that even though our phone should be marked as In Use because it is on a call, it does not show up that way when we look at the queue status. This is obviously a problem since the queue will consider this device as available, even though it is already on a call.

To correct this problem, we need to add callcounter=yes to the [general] section of our sip.conf file. We can also specifically configure this for any peer (since it is a peer-level configuration option); however, this is really something you’ll want to set for all peers that might ever be part of a queue, so it’s normally going to be best to put this option in the [general] section (it could also be assigned to a template that would be used with all peers in the queue).

Edit your sip.conf file so it looks similar to the following:

[general]
context=unauthenticated         ; default context for incoming calls
allowguest=no                   ; disable unauthenticated calls
srvlookup=yes                   ; enabled DNS SRV record lookup on outbound calls
udpbindaddr=0.0.0.0             ; listen for UDP request on all interfaces
tcpenable=no                    ; disable TCP support
callcounter=yes                 ; enable device states for SIP devices

Then reload the chan_sip module and perform the same test again:

*CLI> sip reload
 Reloading SIP 
  == Parsing '/etc/asterisk/sip.conf':   == Found

The device should now show In use when a call is in progress from that device:

  == Parsing '/etc/asterisk/sip.conf':   == Found
  == Using SIP RTP CoS mark 5
    -- Executing [555@LocalSets:1] MusicOnHold("SIP/0000FFFF0001-00000001", 
       "") in new stack
    -- Started music on hold, class 'default', on SIP/0000FFFF0001-00000001

*CLI> queue show support
support      has 0 calls (max unlimited) in 'rrmemory' strategy 
(0s holdtime, 0s talktime), W:0, C:0, A:0, SL:0.0% within 0s
   Members: 
      SIP/0000FFFF0001 (dynamic) (In use) has taken no calls yet
   No Callers

In short, Queue() needs to know the state of a device in order to properly manage call distribution. The callcounter option in sip.conf is an essential component of a properly functioning queue.

The queues.conf File

We’ve mentioned the queues.conf file already, but there are many options in this file, and we figured it would be right and proper for us to go over some of them with you.

Table 13-1 contains the options available in the [general] section of queues.conf.

Table 13-1. Available options for [general] section of queues.conf

OptionsAvailable valuesDescription
persistentmembersyes, noSet this to yes to store dynamically added members to queues in the AstDB so that they can be re-added upon Asterisk restart.
autofillyes, noWith autofill disabled, the queue application will attempt to deliver calls to agents in a serial manner. This means only one call is attempted to be distributed to agents at a time. Additional callers are not distributed to agents until that caller is connected to an agent. With autofill enabled, callers are distributed to available agents simultaneously.
monitor-typeMixMonitor, <unspecified>If you specify the value MixMonitor the MixMonitor() application will be used for recording calls within the queue. If you do not specify a value or comment the option out, the Monitor() application will be used instead.
updatecdryes, noSet this to yes to populate the dstchannel field of the CDR records with the name of a dynamically added member on answer. The value is set with the AddQueueMember() application. This option is used to mimic the behavior of chan_agent channels.
shared_lastcallyes, noThis value is used for members logged into more than one queue to have their last call be the same across all queues, in order for the queues to respect the wrap up time of other queues.

Table 13-2 describes the options available for configuring queue contexts.

Table 13-2. Available options for defined queues in queues.conf

OptionsAvailable valuesDescription
musicclassMusic class as defined by musiconhold.confSets the music class to be used by a particular queue. You can also override this value with the CHANNEL(musicclass) channel variable.
announceFilename of the announcementUsed for playing an announcement to the agent that answered the call, typically to let him know what queue the caller is coming from. Useful when the agent is in multiple queues, especially when set to auto-answer the queue.
strategyringall, leastrecent, fewestcalls, random, rrmemory, linear, wrandom
  • ringall: rings all available callers (default)

  • leastrecent: rings the interface that least recently received a call

  • fewestcalls: rings the interface that has completed the fewest calls in this queue

  • random: rings a random interface

  • rrmemory: rings members in a round-robin fashion, remembering where we left off last for the next caller

  • linear: rings members in the order specified, always starting at the beginning of the list

  • wrandom: rings a random member, but uses the members’ penalties as a weight.

servicelevelValue in secondsUsed in statistics to determine the service level of the queue (calls answered within the service level time frame).
contextDialplan contextAllows a caller to exit the queue by pressing a single DTMF digit. If a context is specified and the caller enters a number, that digit will attempt to be matched in the context specified, and dialplan execution will continue there.
penaltymemberslimitValue of 0 or greaterUsed to disregard penalty values if the number of members in the queue is lower than the value specified.
timeoutValue in secondsSpecifies the number of seconds to ring a member’s device. Also see timeoutpriority.
retryValue in secondsSpecifies the number of seconds to wait before attempting the next member in the queue if the timeout value is exhausted while attempting to ring a member of the queue.
timeoutpriorityapp, confUsed to control the priority of the two possible timeout options specified for a queue. The Queue() application has a timeout value that can be specified to control the absolute time a caller can be in the queue. The timeout value in queues.conf controls the amount of time (along with retry) to ring a member for. Sometime these values conflict, so you can control which value takes precedence. The default is app, as this is the way it works in previous versions.
weightValue of 0 or higherDefines the weight of a queue. A queue with a higher weight defined will get first priority when members are associated with multiple queues.
wrapuptimeValue in secondsThe number of seconds to keep a member unavailable in a queue after completing a call.
autofillyes, noSame as defined in the [general] section. This value can be defined per queue.
autopauseyes, no, allEnables/disables the automatic pausing of members who fail to answer a call. A value of all causes this member to be paused in all queues she is a member of.
maxlenValue of 0 or higherSpecifies the maximum number of callers allowed to be waiting in a queue. A value of zero means an unlimited number of callers are allowed in the queue.
setinterfacevaryes, noIf set to yes, the following channel variables will be set just prior to connecting the caller with the queue member:
  • MEMBERINTERFACE: the member’s interface, such as Agent/1234

  • MEMBERNAME: the name of the member

  • MEMBERCALLS: the number of calls the interface has taken

  • MEMBERLASTCALL: the last time the member took a call

  • MEMBERPENALTY: the penalty value of the member

  • MEMBERDYNAMIC: indicates whether the member was dynamically added to the queue or not

  • MEMBERREALTIME: indicates whether the member is included from real time or not

setqueueentryvaryes, noIf set to yes, the following channel variables will be set just prior to the call being bridged:
  • QEHOLDTIME: the amount of time the caller was held in the queue

  • QEORIGINALPOS: the position the caller originally entered the queue at

setqueuevaryes, noIf set to yes, the following channel variables will be set just prior to the call being bridged:
  • QUEUENAME: the name of the queue

  • QUEUEMAX: the maximum number of calls allowed in this queue

  • QUEUESTRATEGY: the strategy method defined for the queue

  • QUEUECALLS: the number of calls currently in the queue

  • QUEUEHOLDTIME: the current average hold time of callers in the queue

  • QUEUECOMPLETED: the number of completed calls in this queue

  • QUEUEABANDONED: the number of abandoned calls

  • QUEUESRVLEVEL: the queue service level

  • QUEUESRVLEVELPERF: the queue’s service level performance

membermacroName of a macro defined in the dialplanDefines a macro to be executed just prior to bridging the caller and the queue member.
announce-frequencyValue in secondsDefines how often we should announce the caller’s position and/or estimated hold time in the queue. Set this value to zero to disable.
min-announce-frequencyValue in secondsSpecifies the minimum amount of time that must pass before we announce the caller’s position in the queue again. This is used when the caller’s position may change frequently, to prevent the caller hearing multiple updates in a short period of time.
periodic-announce-frequencyValue in secondsIndicates how often we should make periodic announcements to the caller.
random-periodic-announceyes, noIf set to yes, will play the defined periodic announcements in a random order. See periodic-announce.
relative-periodic-announceyes, noIf set to yes, the periodic-announce-frequency timer will start from when the end of the file being played back is reached, instead of from the beginning. Defaults to no.
announce-holdtimeyes, no, onceDefines whether the estimated hold time should be played along with the periodic announcements. Can be set to yes, no, or only once.
announce-positionyes, no, limit, moreDefines whether the caller’s position in the queue should be announced to her. If set to no, the position will never be announced. If set to yes, the caller’s position will always be announced. If the value is set to limit, the caller will hear her position in the queue only if it is within the limit defined by announce-position-limit. If the value is set to more, the caller will hear her position if it is beyond the number defined by announce-position-limit.
announce-position-limitNumber of zero or greaterUsed if you’ve defined announce-position as either limit or more.
announce-round-secondsValue in secondsIf this value is nonzero, we’ll announce the number of seconds as well, and round them to the value defined.
queue-thankyouFilename of prompt to playIf not defined, will play the default value (“Thank you for your patience”). If set to an empty value, the prompt will not be played at all.
queue-youarenextFilename of prompt to playIf not defined, will play the default value (“You are now first in line”). If set to an empty value, the prompt will not be played at all.
queue-thereareFilename of prompt to playIf not defined, will play the default value (“There are”). If set to an empty value, the prompt will not be played at all.
queue-callswaitingFilename of prompt to playIf not defined, will play the default value (“calls waiting”). If set to an empty value, the prompt will not be played at all.
queue-holdtimeFilename of prompt to playIf not defined, will play the default value (“The current estimated hold time is”). If set to an empty value, the prompt will not be played at all.
queue-minutesFilename of prompt to playIf not defined, will play the default value (“minutes”). If set to an empty value, the prompt will not be played at all.
queue-secondsFilename of prompt to playIf not defined, will play the default value (“seconds”). If set to an empty value, the prompt will not be played at all.
queue-reportholdFilename of prompt to playIf not defined, will play the default value (“Hold time”). If set to an empty value, the prompt will not be played at all.
periodic-announceA set of periodic announcements to be played, separated by commasPrompts are played in the order they are defined. Defaults to queue-periodic-announce (“All representatives are currently busy assisting other callers. Please wait for the next available representative”).
monitor-formatgsm, wav, wav49, <any valid file format>Specifies the file format to use when recording. If monitor-format is commented out, calls will not be recorded.
monitor-typeMixMonitor, <unspecified>Same as monitor-type as defined in the [general] section, but on a per-queue basis.
joinemptypaused, penalty, inuse, ringing, unavailable, invalid, unknown, wrapupControls whether a caller is added to the queue when no members are available. Comma-separated options can be included to define how this option determines whether members are available. The definitions for the values are:
  • paused: members are considered unavailable if they are paused.

  • penalty: members are considered unavailable if their penalties are less than QUEUE_MAX_PENALTY.

  • inuse: members are considered unavailable if their device status is In Use.

  • ringing: members are considered unavailable if their device status is Ringing.

  • unavailable: applies primarily to agent channels; if the agent is not logged in but is a member of the queue, it is considered unavailable.

  • invalid: members are considered unavailable if their device status is Invalid. This is typically an error condition.

  • unknown: members are considered unavailable if device status is unknown.

  • wrapup: members are considered unavailable if they are currently in the wrapup time after the completion of a call.

leavewhenemptypaused, penalty, inuse, ringing, unavailable, invalid, unknown, wrapupUsed to control whether callers are kicked out of the queue when members are no longer available to take calls. See joinempty for more information on the assignable values.
eventwhencalledyes, no, varsIf set to yes, the following manager events will be sent to the Asterisk Manager Interface (AMI):
  • AgentCalled

  • AgentDump

  • AgentConnect

  • AgentComplete

If set to vars, all channel variables associated with the agent will also be sent to the AMI.

eventmemberstatusyes, noIf set to yes, the QueueMemberStatus event will be sent to AMI. Note that this may generate a lot of manager events.
reportholdtimeyes, noEnables reporting of the caller’s hold time to the queue member prior to bridging.
ringinuseyes, noUsed to avoid sending calls to members whose status is In Use. Recall from our discussion in the preceding section that only the SIP channel driver is currently able to accurately report this status.
memberdelayValue in secondsUsed if you want there to be a delay prior to the caller and queue member being connected to each other.
timeoutrestartyes, noIf set to yes, resets the timeout for an agent to answer if either a BUSY or CONGESTION status is received from the channel. This can be useful if the agent is allowed to reject or cancel a call.
defaultruleRule as defined in queuerules.confAssociates a queue rule as defined in queuerules.conf to this queue, which is used to dynamically change the minimum and maximum penalties, which are then used to select an available agent. See Changing Penalties Dynamically (queuerules.conf).
memberDeviceUsed to define static members in a queue. To define a static member, you supply its Technology/Device_ID (e.g., Agent/1234, SIP/0000FFFF0001, DAHDI/g0/14165551212).

The agents.conf File

If you’ve browsed through the samples in the ~/src/asterisk-complete/1.8/configs/ directory, you may have noticed the agents.conf file. It may seem tempting, and it has its places, but overall the best way to implement queues is through the use of SIP channels. There are two reasons for this. The first is that SIP channels are the only type that provide true device state information. The other reason is that agents are always logged in when using the agent channel, and if you’re using remote agents, the bandwidth requirements may be greater than you wish. However, in busy call centers it may be desirable to force agents to answer calls immediately rather than having them press the answer button on the phone.

The agents.conf file is use to define agents for queues using the agents channel. This channel is similar in nature to the other channel types in Asterisk (local, SIP, IAX2, etc.), but it is more of a pseudo-channel in that it is used to connect callers to agents who have logged into the system using other types of transport channel. For example, suppose we use our SIP-enabled phone to log in to Asterisk using the AgentLogin() dialplan application. Once we’re logged in, the channel remains online the entire time it is available (logged on), and calls are then passed to it through the agent channel.

Let’s take a look at the various options available to us in the agents.conf file to get a better idea of what it provides us. Table 13-3 shows the single option available in the [general] section of agents.conf. Table 13-4 shows the available options under the [agents] header.

Table 13-3. Options available under the [general] header in agents.conf

OptionsAvailable valuesDescription
multipleloginyes, noIf set to yes, a single line on a device can log in as multiple agents. Defaults to yes.

Table 13-4. Options available under the [agents] header in agents.conf

OptionsAvailable valuesDescription
maxloginretriesInteger valueSpecifies the maximum number of tries an agent has to log in before the system considers it a failed attempt and ends the call. Defaults to 3.
autologoffValue in secondsSpecifies the number of seconds for which an agent’s device should ring before the agent is automatically logged off.
autologoffunavailyes, noIf set to yes, the agent is automatically logged off when the device being called returns a status of CHANUNAVAIL.
ackcallyes, noIf set to yes, the agent must enter a single DTMF digit to accept the call. To be used in conjunction with acceptdtmf. Defaults to no.
acceptdtmfSingle DTMF characterUsed in conjunction with ackcall, this option defines the DTMF character to be used to accept a call. Defaults to #.
endcallyes, noIf set to yes, allows an agent to end a call with a single DTMF digit. To be used in conjunction with enddtmf. Defaults to yes.
enddtmfSingle DTMF characterUsed in conjunction with endcall, this option defines the DTMF character to be used to end a call. Defaults to *.
wrapuptimeValue in millisecondsSpecifies the amount of time after disconnection of a caller from an agent for which the agent will not be available to accept another call. Used in situations where agents must perform a function after each call (such as entering call details into a log).
musiconholdMusic class as defined in musiconhold.confDefines the default music class agents listen to when logged in.
goodbyeName of file (relative to /var/lib/asterisk/sounds/<lang>/)Defines the default goodbye sound played to agents. Defaults to vm-goodbye.
updatecdryes, noUsed in call detail records to change the source channel field to the agent/agent_id.
groupInteger valueAllows you to define groups for sets of agents. The use of agent groups is essentially deprecated functionality that we do not recommend you use. If you define group1, you can use Agent/@1 in queues.conf to call that group of agents. The call will be connected arbitrarily to one of those agents. If no agents are available, it will return back to the queue like any other unanswered call. If you use Agent/:1, it will wait for a member of the group to become available. The use of strategies has no effect on agent groups. Do not use these.
recordagentcallsyes, noEnables/disables the recording of agent calls. Disabled by default.
recordformatFile format (gsm, wav, etc.)Defines the format to be used when recording agent calls. Default is wav.
urlprefixString (URL)Accepts a string as its argument. The string can be formed as a URL and is appended to the start of the text to be added to the name of the recording.
savecallsinFilesystem path (e.g., /var/calls/)Accepts a filesystem path as its argument. Allows you to override the default path of /var/spool/asterisk/monitor/ with one of your choosing.[a]
custom_beepName of file (relative to /var/lib/asterisk/sounds/<lang>/)Accepts a filename as its argument. Can be used to define a custom notification tone to signal to an always-connected agent that there is an incoming call.
agentAgent definition (see description)Defines an agent for use by Queue() and AgentLogin(). These are agents that will log in and stay connected to the system, waiting for calls to be delivered by the Queue() dialplan application. Agents are defined like so:
agent => agent_id,agent_password,name

An example of a defined agent would be:

agent => 1000,1234,Danielle Roberts

[a] Since the storage of calls will require a large amount of hard drive space, you will want to define a strategy to handle storing and managing these recordings. This location should probably reside on a separate volume, one with very high performance characteristics.

Advanced Queues

In this section we’ll take a look at some of the finer-grained queue controls, such as options for controlling announcements and when callers should be placed into (or removed from) the queue. We’ll also look at penalties and priorities, exploring how we can control the agents in our queue by giving preference to a pool of agents to answer the call and increase that pool dynamically based on the wait times in the queue. Finally, we’ll look at using Local channels as queue members, which gives us the ability to perform dialplan functionality prior to connecting the caller to an agent.

Priority Queue (Queue Weighting)

Sometimes you need to add people to a queue at a higher priority than that given to other callers. Perhaps the caller has already spent time waiting in a queue, and an agent has taken some information but realized the caller needed to be transferred to another queue. In this case, to minimize the caller’s overall wait time, it might be desirable to transfer the call to a priority queue that has a higher weight (and thus a higher preference), so it will be answered quickly.

Setting a higher priority on a queue is done with the weight option. If you have two queues with differing weights (e.g., support and support-priority), agents assigned to both queues will be passed calls from the higher-priority queue in preference to calls from the lower-priority queue. Those agents will not take any calls from the lower-priority queue until the higher-priority queue is cleared. (Normally, there will be some agents who are assigned only to the lower-priority queue, to ensure that those calls are dealt with in a timely manner.) For example, if we place queue member James Shaw into both the support and support-priority queues, callers in the support-priority queue will have a preferred standing with James over callers in the support queue.

Let’s take a look at how we could make this work. First, we need to create two queues that are identical except for the weight option. We can use a template for this to ensure that the two queues remain identical if anything should need to change in the future:

[support_template](!)
musicclass=default
strategy=rrmemory
joinempty=no
leavewhenempty=yes
ringinuse=no

[support](support_template)
weight=0

[support-priority](support_template)
weight=10

With our queues configured (and subsequently reloaded using module reload app_queue.so from the Asterisk console), we can now create two extensions to transfer callers to. This can be done wherever you would normally place your dialplan logic to perform transfers. We’re going to use the LocalSets context, which we’ve previously enabled as the starting context for our devices:

[LocalSets]
include => Queue  ; allow direct transfer of calls to queues

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Queue(support)             ; standard support queue available
                                        ; at extension 7000
   same => n,VoiceMail(7000@queues,u)   ; if there are no members in the queue, 
                                        ; we exit and send the caller to voicemail
   same => n,Hangup()

exten => 8000,1,Verbose(2,Entering the priority support queue)
   same => n,Queue(support-priority)    ; priority queue available at 
                                        ; extension 8000
   same => n,VoiceMail(7000@queues,u)   ; if there are no members in the queue, 
                                        ; we exit and send the caller to voicemail
   same => n,Hangup()

There you have it: two queues defined with different weights. We’ve configured our standard queues to start at extension 7000, and our priority queues to start at 8000. We can mirror this for several queues by simply matching between the 7XXX and 8XXX ranges. So, for example, if we have our sales queue at extension 7004, our priority-sales queue (for returning customers, perhaps?) could be placed in the mirrored queue at 8004, which has a higher weight.

The only other configuration left to do is to make sure some or all of your queue members are placed in both queues. If you have more callers in your 7XXXX queues, you may want to have more queue members logged into that queue, with a percentage of your queue members logged into both queues. Exactly how you wish to configure your queues will depend on your local policy and circumstances.

Queue Member Priority

Within a queue, we can penalize members in order to lower their preference for being called when there are people waiting in a particular queue. For example, we may penalize queue members when we want them to be a member of a queue, but to be used only when the queue gets full enough that all our preferred agents are unavailable. This means we can have three queues (say, support, sales, and billing), each containing the same three queue members: James Shaw, Kay Madsen, and Danielle Roberts.

Suppose, however, that we want James Shaw to be the preferred contact in the support queue, Kay Madsen preferred in sales, and Danielle Roberts preferred in billing. By penalizing Kay Madsen and Danielle Roberts in support, we ensure that James Shaw will be the preferred queue member called. Similarly, we can penalize James Shaw and Danielle Roberts in the sales queue so Kay Madsen is preferred, and penalize James Shaw and Kay Madsen in the billing queue so Danielle Roberts is preferred.

Penalizing queue members can be done either in the queues.conf file, if you’re specifying queue members statically, or through the AddQueueMember() dialplan application. Let’s look at how our queues would be set up with static members in queues.conf. We’ll be using the StandardQueue template we defined earlier in this chapter:

[support](StandardQueue)
member => SIP/0000FFFF0001,0,James Shaw         ; preferred
member => SIP/0000FFFF0002,10,Kay Madsen        ; second preferred
member => SIP/0000FFFF0003,20,Danielle Roberts  ; least preferred

[sales](StandardQueue)
member => SIP/0000FFFF0002,0,Kay Madsen
member => SIP/0000FFFF0003,10,Danielle Roberts
member => SIP/0000FFFF0001,20,James Shaw

[billing](StandardQueue)
member => SIP/0000FFFF0003,0,Danielle Roberts
member => SIP/0000FFFF0001,10,James Shaw
member => SIP/0000FFFF0002,20,Kay Madsen

By defining different penalties for each member of the queue, we can help control the preference for where callers are delivered, but still ensure that other queue members will be available to answer calls if the preferred member is unavailable. Penalties can also be defined using AddQueueMember(), as the following example demonstrates:

exten => *54,1,Verbose(2,Logging In Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})

; *CLI> database put queue support/0000FFFF0001/penalty 0
   same => n,Set(QueuePenalty=${DB(queue/support/${CHANNEL(peername)}/penalty)})

; *CLI> database put queue support/0000FFFF0001/membername "James Shaw"
   same => n,Set(MemberName=${DB(queue/support/${CHANNEL(peername)}/membername)})

; AddQueueMember(queuename[,interface[,penalty[,options[,membername
; [,stateinterface]]]]])
   same => n,AddQueueMember(support,${MemberChannel},${QueuePenalty},,${MemberName})

Using AddQueueMember(), we’ve shown how you could retrieve the penalty associated with a given member name for a particular queue and assign that value to the member when she logs into the queue. Some additional abstraction would need to be done to make this work for multiple queues; for more information see Automatically Logging Into and Out of Multiple Queues.

Changing Penalties Dynamically (queuerules.conf)

Using the queuerules.conf file, it is possible to specify rules to change the values of the QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY channel variables. The QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY channel variables are used to control which members of a queue are to be used for servicing callers. Let’s say we have a queue called support, and we have five queue members with various penalties ranging from 1 through 5. If prior to a caller entering the queue the QUEUE_MIN_PENALTY channel variable is set to a value of 2 and the QUEUE_MAX_PENALTY is set to a value of 4, only queue members whose penalties are set to values ranging from 2 through 4 will be considered available to answer that call:

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Set(QUEUE_MIN_PENALTY=2)   ; set minimum queue member penalty to be used
   same => n,Set(QUEUE_MAX_PENALTY=4)   ; set maximum queue member penalty we'll use
   same => n,Queue(support)             ; entering the queue with minimum and maximum 
                                        ; member penalties to be used

What’s more, during the caller’s stay in the queue, we can dynamically change the values of QUEUE_MIN_PENALTY and QUEUE_MAX_PENALTY for that caller. This allows either more or a different set of queue members to be used, depending on how long the caller waits in the queue. For instance, in the previous example, we could modify the minimum penalty to 1 and the maximum penalty to 5 if the caller has to wait more than 60 seconds in the queue.

The rules are defined using the queuerules.conf file. Multiple rules can be created in order to facilitate different penalty changes throughout the call. Let’s take a look at how we’d define the changes described in the previous paragraph:

[more_members]
penaltychange => 60,5,1

Note

If you make changes to the queuerules.conf file and reload app_queue.so, the new rules will affect only new callers in the queue, not existing callers.

We’ve defined the rule more_members in queuerules.conf and passed the following values to penaltychange: 60 is the number of seconds to wait before changing the penalty values, 5 is the new QUEUE_MAX_PENALTY, and 1 is the new QUEUE_MIN_PENALTY. With our new rule defined, we must reload app_queue.so to make it available to us for use:

*CLI> module reload app_queue.so
    -- Reloading module 'app_queue.so' (True Call Queueing)
  == Parsing '/etc/asterisk/queuerules.conf':   == Found

We can also verify our rules at the console with queue show rules:

*CLI> queue show rules
Rule: more_members
   After 60 seconds, adjust QUEUE_MAX_PENALTY to 5 and adjust QUEUE_MIN_PENALTY to 1

With our rule now loaded into memory, we can modify our dialplan to make use of it. Just modify the Queue() line to include the new rule, like so:

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Set(QUEUE_MIN_PENALTY=2)   ; set minimum queue member penalty
   same => n,Set(QUEUE_MAX_PENALTY=4)   ; set maximum queue member penalty

; Queue(queuename[,options[,URL[,announceoverride[,timeout[,AGI[,macro
; [,gosub[,rule[,position]]]]]]]]])
   same => n,Queue(support,,,,,,,,more_members)  ; entering queue with minimum and 
                                                 ; maximum member penalties

The queuerules.conf file is quite flexible. We can define our rule using relative instead of absolute penalty values, and we can define multiple rules:

[more_members]
penaltychange => 30,+1
penaltychange => 45,,-1
penaltychange => 60,+1
penaltychange => 120,+2

Here, we’ve modified our more_members rule to use relative values. After 30 seconds, we increase the maximum penalty by 1 (which would take us to 5 using our sample dialplan). After 45 seconds, we decrease the minimum penalty by 1, and so on. We can verify our new rule changes after a module reload app_queue.so at the Asterisk console:

*CLI> queue show rules
Rule: more_members
  After 30 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 45 seconds, adjust QUEUE_MAX_PENALTY by 0 and adjust QUEUE_MIN_PENALTY by -1
  After 60 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 120 seconds, adjust QUEUE_MAX_PENALTY by 2 and adjust QUEUE_MIN_PENALTY by 0

Announcement Control

Asterisk has the ability to play several announcements to callers waiting in the queue. For example, you might want to announce the caller’s position in the queue, the average wait time, or make periodic announcements thanking your callers for waiting (or whatever your audio files say). It’s important to tune the values that control when these announcements are played to the callers, because announcing their position, thanking them for waiting, and telling them the average hold time too often may annoy them, causing them to either hang up or take it out on your agents.

There are several options in the queues.conf file that you can use to fine-tune what and when announcements are played to your callers. The full list of queue options is available in The queues.conf File, but we’ll review the relevant ones here.

Table 13-5 lists the options you can use to control when announcements are played to the caller.

Table 13-5. Options related to prompt control timing within a queue

OptionsAvailable valuesDescription
announce-frequencyValue in secondsDefines how often we should announce the caller’s position and/or estimated hold time in the queue. Set this value to zero to disable.
min-announce-frequencyValue in secondsIndicates the minimum amount of time that must pass before we announce the caller’s position in the queue again. This is used when the caller’s position may change frequently, to prevent the caller hearing multiple updates in a short period of time.
periodic-announce-frequencyValue in secondsSpecifies how often we should make periodic announcements to the caller.
random-periodic-announceyes, noIf set to yes, will play the defined periodic announcements in a random order. See periodic-announce.
relative-periodic-announceyes, noIf set to yes, the periodic-announce-frequency timer will start from when the end of the file being played back is reached, instead of from the beginning. Defaults to no.
announce-holdtimeyes, no, onceDefines whether the estimated hold time should be played along with the periodic announcements. Can be set to yes, no, or only once.
announce-positionyes, no, limit, moreDefines whether the caller’s position in the queue should be announced to her. If set to no, the position will never be announced. If set to yes, the caller’s position will always be announced. If the value is set to limit, the caller will hear her position in the queue only if it is within the limit defined by announce-position-limit. If the value is set to more, the caller will hear her position only if it is beyond the number defined by announce-position-limit.
announce-position-limitNumber of zero or greaterUsed if you’ve defined announce-position as either limit or more.
announce-round-secondsValue in secondsIf this value is nonzero, we’ll announce the number of seconds as well, and round them to the value defined.

Table 13-6 shows what files will be used when announcements are played to the caller.

Table 13-6. Options for controlling the playback of prompts within a queue

OptionsAvailable valuesDescription
musicclassMusic class as defined by musiconhold.confSets the music class to be used by a particular queue. You can also override this value with the CHANNEL(musicclass) channel variable.
queue-thankyouFilename of prompt to playIf not defined, will play the default value (“Thank you for your patience”). If set to an empty value, the prompt will not be played at all.
queue-youarenextFilename of prompt to playIf not defined, will play the default value (“You are now first in line”). If set to an empty value, the prompt will not be played at all.
queue-thereareFilename of prompt to playIf not defined, will play the default value (“There are”). If set to an empty value, the prompt will not be played at all.
queue-callswaitingFilename of prompt to playIf not defined, will play the default value (“calls waiting”). If set to an empty value, the prompt will not be played at all.
queue-holdtimeFilename of prompt to playIf not defined, will play the default value (“The current estimated hold time is”). If set to an empty value, the prompt will not be played at all.
queue-minutesFilename of prompt to playIf not defined, will play the default value (“minutes”). If set to an empty value, the prompt will not be played at all.
queue-secondsFilename of prompt to playIf not defined, will play the default value (“seconds”). If set to an empty value, the prompt will not be played at all.
queue-reportholdFilename of prompt to playIf not defined, will play the default value (“Hold time”). If set to an empty value, the prompt will not be played at all.
periodic-announceA set of periodic announcements to be played, separated by commasPrompts are played in the order they are defined. Defaults to queue-periodic-announce (“All representatives are currently busy assisting other callers. Please wait for the next available representative”).

If the number of options devoted to playing announcements to callers is any indication of their importance, it’s probably in our best interest to use them to their fullest potential. The options in Table 13-5 help us define when we’ll play announcements to callers, and the options in Table 13-6 help us control what we play to our callers. With those tables in hand, let’s take a look at an example queue where we’ve defined some values. We’ll use our basic queue template as a starting point:

[general]
autofill=yes             ; distribute all waiting callers to available members
shared_lastcall=yes      ; respect the wrapup time for members logged into more 
                         ; than one queue

[StandardQueue](!)       ; template to provide common features
musicclass=default       ; play [default] music
strategy=rrmemory        ; use the Round Robin Memory strategy
joinempty=yes            ; do not join the queue when no members available
leavewhenempty=no        ; leave the queue when no members available
ringinuse=no             ; don't ring members when already InUse (prevents 
                         ; multiple calls to an agent)

[sales](StandardQueue)   ; create the sales queue using the parameters in the
                         ; StandardQueue template

[support](StandardQueue) ; create the support queue using the parameters in the
                         ; StandardQueue template

We’ll now modify the StardardQueue template to control our announcements:

[StandardQueue](!)       ; template to provide common features
musicclass=default       ; play [default] music
strategy=rrmemory        ; use the Round Robin Memory strategy
joinempty=yes            ; do not join the queue when no members available
leavewhenempty=no        ; leave the queue when no members available
ringinuse=no             ; don't ring members when already InUse (prevents 
                         ; multiple calls to an agent)

; -------- Announcement Control --------
announce-frequency=30           ; announces caller's hold time and position every 30
                                ; seconds
min-announce-frequency=30       ; minimum amount of time that must pass before the
                                ; caller's position is announced 
periodic-announce-frequency=45  ; defines how often to play a periodic announcement to
                                ; caller
random-periodic-announce=no     ; defines whether to play periodic announcements in 
                                ; a random order, or serially
relative-periodic-announce=yes  ; defines whether the timer starts at the end of
                                ; file playback (yes) or the beginning (no)
announce-holdtime=once          ; defines whether the estimated hold time should be 
                                ; played along with the periodic announcement
announce-position=limit         ; defines if we should announce the caller's position
                                ; in the queue
announce-position-limit=10      ; defines the limit value where we announce the
                                ; caller's position (when announce-position is set to 
                                ; limit or more)
announce-round-seconds=30       ; rounds the hold time announcement to the nearest 
                                ; 30-second value

Let’s describe what we’ve just set in our StandardQueue template.

We’ll announce the caller’s hold time and position every 30 seconds (announce-frequency),[129] and make sure the minimum amount of time that passes before we announce it again is at least 30 seconds (min-announce-frequency). We do this to limit how often our announcements are played to the callers, in order to avoid the updates becoming annoying. Periodically, we’ll play an announcement to the callers that thanks them for holding and assures them that an agent will be with them shortly. (The announcement is defined by the periodic-announcement setting. We’re using the default announcement, but you can define one or more announcements yourself using periodic-announce.)

These periodic announcements will be played every 45 seconds (periodic-announce-frequency), in the order they were defined (random-period-announce). To determine when the periodic-announce-frequency timer should start, we use relative-periodic-announce. The yes setting means the timer will start after the announcement has finished playing, rather than when it starts to play. The problem you could run into if you set this to no is that if your periodic announcement runs for any significant length of time (lets say 30 seconds), it will appear as if it is being played every 15 seconds, rather than every 45 seconds as may be intended.

How many times we announce the hold time to the caller is controlled via the announce-holdtime option, which we’ve set to once. Setting the value to yes will announce it every time, and setting to no will disable it.

We configure how and when we announce the caller’s estimated remaining hold time via announce-position, which we’ve set to limit. Using the value of limit for announce-position lets us announce the caller’s position only if it is within the limit defined by announce-position-limit. So, in this case we’re only announcing the callers’ positions if they are in the first 10 positions of the queue. We could also use yes to announce the position every time the periodic announcement is played, set it to no to never announce it, or use the value more if we want to announce the position only when it is greater than the value set for announce-position-limit.

Our last option, announce-round-seconds, controls the value to round to when we announce the caller’s hold time. In this case, instead of saying “1 minute and 23 seconds,” the value would be rounded to the nearest 30-second value, which would result in a prompt of “1 minute and 30 seconds.”

Overflow

Overflowing out of the queue is done either with a timeout value, or when no queue members are available (as defined by joinempty or leavewhenempty). In this section we’ll discuss how to control when overflow happens.

Controlling timeouts

The Queue() application supports two kinds of timeout: one is for the maximum period of time a caller stays in the queue, and the other is how long to ring a device when attempting to connect a caller to a queue member. We’ll be talking about the maximum period of time a caller stays in the queue before the call overflows to another location, such as VoiceMail(). Once the call has fallen out of the queue, it can go anywhere that a call could normally go when controlled by the dialplan.

The timeouts are specified in two locations. The timeout that indicates how long to ring queue members for is specified in the queues.conf file. The absolute timeout (how long the caller stays in the queue) is controlled via the Queue() application. To set a maximum amount of time for callers to stay in a queue, simply specify it after the queue name in the Queue() application:

[Queues]
exten => 7000,1,Verbose(2,Joining the support queue for a maximum of 2 minutes)
   same => n,Queue(support,120)
   same => n,VoiceMail(support@queues,u)
   same => n,Hangup()

Of course, we could define a different destination, but the VoiceMail() application is as good as any. Just make sure that if you’re going to send callers to voicemail someone checks it regularly and calls your customers back.

Now let’s say we have the scenario where we have set our absolute timeout to 10 seconds, our timeout value for ringing queue members to 5 seconds, and our retry timeout value to 4 seconds. In this scenario, we would ring the queue member for 5 seconds, then wait 4 seconds before attempting another queue member. That brings us up to 9 seconds of our absolute timeout of 10 seconds. At this point, should we ring the second queue member for 1 second and then exit the queue, or should we ring this member for the full 5 seconds before exiting?

We control which timeout value has priority with the timeoutpriority option in queues.conf. The available values are app and conf. If we want the application timeout (the absolute timeout) to take priority, which would cause our caller to be kicked out after exactly 10 seconds, we should set the timeoutpriority value to app. If we want the configuration file timeout to take priority and finish ringing the queue member, which will cause the caller to stay in the queue a little longer, we should set timeoutpriority to conf. The default value is app (which is the default behavior in previous versions of Asterisk).

Controlling when to join and leave a queue

Asterisk provides two options that control when callers can join and are forced to leave queues, based on the statuses of the queue members. The first option, joinempty, is used to control whether callers can enter a queue. The leavewhenempty option is used to control when callers already in a queue should be removed from that queue (i.e., if all of the queue members become unavailable). Both options take a comma-separated list of values that control this behavior. The factors are listed in Table 13-7.

Table 13-7. Options that can be set for joinempty or leavewhenempty

ValueDescription
pausedMembers are considered unavailable if they are paused.
penaltyMembers are considered unavailable if their penalties are less than QUEUE_MAX_PENALTY.
inuseMembers are considered unavailable if their device status is In Use.
ringingMembers are considered unavailable if their device status is Ringing.
unavailableApplies primarily to agent channels; if the agent is not logged in but is a member of the queue it is considered unavailable.
invalidMembers are considered unavailable if their device status is Invalid. This is typically an error condition.
unknownMembers are considered unavailable if device status is unknown.
wrapupMembers are considered unavailable if they are currently in the wrapup time after the completion of a call.

For joinempty, prior to placing a caller into the queue, all the members are checked for availability using the factors you list as criteria. If all members are deemed to be unavailable, the caller will not be permitted to enter the queue, and dialplan execution will continue at the next priority.[130] For the leavewhempty option, the members’ statuses are checked periodically against the listed conditions; if it is determined that no members are available to take calls, the caller is removed from the queue, with dialplan execution continuing at the next priority.

An example use of joinempty could be:

joinempty=paused,inuse,invalid

With this configuration, prior to a caller entering the queue the statuses of all queue members will be checked, and the caller will not be permitted to enter the queue unless at least one queue member is found to have a status that is not paused, inuse, or invalid.

The leavewhenempty example could be something like:

leavewhenempty=inuse,ringing

In this case, the queue members’ statuses will be checked periodically, and callers will be removed from the queue if no queue members can be found who do not have a status of either inuse or ringing.

Previous versions of Asterisk used the values yes, no, strict, and loose as the available values to be assigned. The mapping of those values is shown in Table 13-8.

Table 13-8. Mapping between old and new values for controlling when callers join and leave queues

ValueMapping (joinempty)Mapping (leavewhenempty)
yes(empty)penalty,paused,invalid
nopenalty,paused,invalid(empty)
strictpenalty,paused,invalid,unavailablepenalty,paused,invalid,unavailable
loosepenalty,invalidpenalty,invalid

Using Local Channels

The use of Local channels as queue members is a popular way of executing parts of the dialplan and performing checks prior to dialing the actual agent’s device. For example, it allows us to do things like start recording the call, set up channel variables, write to a log file, set a limit on the call length (e.g., if it is a paid service), or do any of the other things we might need to do once we know which location we’re going to call.

When using Local channels for queues, they are added just like any other channels. In the queues.conf file, adding a Local channel would look like this:

; queues.conf
[support](StandardQueue)
member => Local/SIP-0000FFFF0001@MemberConnector  ; pass the technology to dial over
                                                  ; and the device identifier,
                                                  ; separated by a hyphen. We'll
                                                  ; break it apart inside the 
                                                  ; MemberConnector context.

Tip

Notice how we passed the type of technology we want to call along with the device identifier to the MemberConnector context. We’ve simply used a hyphen (although we could have used nearly anything as a separator argument) as the field marker. We’ll use the CUT() function inside the MemberConnector context and assign the first field (SIP) to one channel variable and the second field (0000FFFF0001) to another channel variable, which will then be used to call the endpoint.

Passing information to be later “exploded” in the context used by the Local channel is a common and useful technique (kind of like the explode() function in PHP).

Of course, we’ll need the MemberConnector context to actually connect the caller to the agent:

[MemberConnector]
exten => _[A-Za-z0-9].,1,Verbose(2,Connecting ${CALLERID(all)} to Agent at ${EXTEN})

   ; filter out any bad characters, allowing alphanumeric characters and the hyphen
   same => n,Set(QueueMember=${FILTER(A-Za-z0-9-,${EXTEN})

   ; assign the first field of QueueMember to Technology using the hyphen separator
   same => n,Set(Technology=${CUT(QueueMember,-,1)})

   ; assign the second field of QueueMember to Device using the hyphen separator
   same => n,Set(Device=${CUT(QueueMember,-,2)})

   ; dial the agent
   same => n,Dial(${Technology}/${Device})
   same => n,Hangup()

So, now we’ve passed our queue member to the context, and we can dial the device. However, because we’re using the Local channel as the queue member, the Queue() won’t necessarily know the state the call is in, especially when the Local channel is optimized out of the path (see https://wiki.asterisk.org/wiki/display/AST/Local+Channel+Modifiers for information about the /n modifier, which causes the Local channel to not be optimized out of the path). The queue will be monitoring the state of the Local channel, and not that of the device we really want to monitor.

Luckily, we can give the Queue() the actual device to monitor and associate that with the Local channel, so that the Local channel’s state is always that of the device we’ll end up calling. Our queue member would be modified in the queues.conf file like so:

; queues.conf
[support](StandardQueue)
member => Local/SIP-0000FFFF0001@MemberConnector,,,SIP/0000FFFF0001

Warning

Only SIP channels are capable of sending back reliable device state information, so it is highly recommended that you use only these channels when using Local channels as queue members.

You can also use the AddQueueMember() and RemoveQueueMember() applications to add members to and remove members from a queue, just like with any other channel. AddQueueMember() also has the ability to set the state interface, which we defined statically in the queues.conf file. An example of how you might do this follows:

[QueueMemberLogin]
exten => 500,1,Verbose(2,Logging in device ${CHANNEL(peername)} into the support queue)

   ; Save the device's technology to the MemberTech channel variable
   same => n,Set(MemberTech=${CHANNEL(channeltype)})
 
   ; Save the device's identifier to the MemberIdent channel variable
   same => n,Set(MemberIdent=${CHANNEL(peername)})

   ; Build up the interface name and assign it to the Interface channel variable
   same => n,Set(Interface=${MemberTech}/${MemberIdent})

   ; Add the member to the support queue using a Local channel. We're using the same
   ; format as before, separating the technology and the device indentifier with
   ; a hyphen and passing that information to the MemberConnector context. We then
   ; use the IF() function to determine if the member's technology is SIP and, if so,
   ; to pass back the contents of the Interface channel variable as the value to the
   ; state interface field of the AddQueueMember() application.
   ;
   ; *** This line should not have any line breaks
   same => n,AddQueueMember(support,Local/${MemberTech}-${MemberIdent}
@MemberConnector,,,${IF($[${MemberTech} = SIP]?${Interface})})
   same => n,Playback(silence/1)

   ; Play back either the agent-loginok or agent-incorrect file, depending on what
   ; the AQMSTATUS variable is set to.
   same => n,Playback(${IF($[${AQMSTATUS} = ADDED]?agent-loginok:agent-incorrect)})
   same => n,Hangup()

Now that we can add devices to the queue using Local channels, let’s look at how we might control the number of calls to either non-SIP channels or devices with more than one line on them. We can make use of the GROUP() and GROUP_COUNT() functions to track call counts to an endpoint. We’ll modify our MemberConnector context to take this into account:

[MemberConnector]
exten => _[A-Za-z0-9].,1,Verbose(2,Connecting ${CALLERID(all)} to Agent at ${EXTEN})

   ; filter out any bad characters, allowing alphanumeric characters and the hyphen
   same => n,Set(QueueMember=${FILTER(A-Za-z0-9-,${EXTEN})

   ; assign the first field of QueueMember to Technology using the hyphen separator
   same => n,Set(Technology=${CUT(QueueMember,-,1)})

   ; assign the second field of QueueMember to Device using the hyphen separator
   same => n,Set(Device=${CUT(QueueMember,-,2)})

   ; Increase the value of the group inside the queue_members category by one
   same => n,Set(GROUP(queue_members)=${Technology}-${Device})

   ; Check if the group@category is greater than 1, and, if so, return Congestion()
   ; (too many channels)
   ;
   ; *** This line should not have any line breaks
   same => n,ExecIf($[${GROUP_COUNT(${Technology}-${Device}@queue_members)} > 1]
?Congestion())

   ; dial the agent
   same => n,Dial(${Technology}/${Device})
   same => n,Hangup()

The passing back of Congestion() will cause the caller to be returned to the queue (while this is happening, the caller gets no indication that anything is amiss and keeps hearing music until we actually connect to the device). While this is not an ideal situation because the queue will keep trying the member over and over again (or at least include it in the cycle of agents, depending on how many members you have and their current statuses), it is better than an agent getting multiple calls at the same time.

We’ve also used this same method to create a type of reservation process. If you want to call an agent directly (for example, if the caller needs to follow up with a particular agent), you could reserve that agent by using the GROUP() and GROUP_COUNT() functions to essentially pause the agent in the queue until the caller can be connected. This is particularly useful in situations where you need to play some announcements to the caller prior to connecting her with the agent, but you don’t want the agent to get connected to another caller while the announcements are being played.

Queue Statistics: The queue_log File

The queue_log file located in /var/log/asterisk/ contains information about the queues defined in your system (when a queue is reloaded, when queue members are added or removed, etc.) and about calls into the queues (e.g., their status and what channels the callers were connected to). The queue log is enabled by default, but can be controlled via the logger.conf file. There are three options related to the queue_log file specifically:

queue_log

Controls whether the queue log is enabled or not. Valid values are yes or no (defaults to yes).

queue_log_to_file

Controls whether the queue log should be written to a file even when a real time backend is present. Valid values are yes or no (defaults to no).

queue_log_name

Controls the name of the queue log. The default is queue_log.

The queue log is a pipe-separated list of events. The fields in the queue_log file are as follows:

  • Epoch timestamp of the event

  • Unique ID of the call

  • Name of the queue

  • Name of bridged channel

  • Type of event

  • Zero or more event parameters

The information contained in the event parameters depends on the type of event. A sample queue_log file might look something like the following:

1292281046|psy1-1292281041.87|7100|NONE|ENTERQUEUE||4165551212|1
1292281046|psy1-1292281041.87|7100|Local/9996@MemberConnector|RINGNOANSWER|0
1292281048|psy1-1292281041.87|7100|Local/9990@MemberConnector|CONNECT|2
|psy1-1292281046.90|0

1292284121|psy1-1292281041.87|7100|Local/9990@MemberConnector|COMPLETECALLER|2|3073|1
1292284222|MANAGER|7100|Local/9990@MemberConnector|REMOVEMEMBER|
1292284222|MANAGER|7200|Local/9990@MemberConnector|REMOVEMEMBER|
1292284491|MANAGER|7100|Local/9990@MemberConnector|ADDMEMBER|
1292284491|MANAGER|7200|Local/9990@MemberConnector|ADDMEMBER|
1292284519|psy1-1292284515.93|7100|NONE|ENTERQUEUE||4165551212|1
1292284519|psy1-1292284515.93|7100|Local/9996@MemberConnector|RINGNOANSWER|0
1292284521|psy1-1292284515.93|7100|Local/9990@MemberConnector|CONNECT|2
|psy1-1292284519.96|0

1292284552|MANAGER|7100|Local/9990@MemberConnector|REMOVEMEMBER|
1292284552|MANAGER|7200|Local/9990@MemberConnector|REMOVEMEMBER|
1292284562|psy1-1292284515.93|7100|Local/9990@MemberConnector|COMPLETECALLER|2|41|1

As you can see from this example, there might not always be a unique ID for the event. In some cases external services, such as the Asterisk Manager Interface (AMI), perform actions on the queue; in this case you’ll see something like MANAGER in the Unique ID field.

The available events and the information they provide are described in Table 13-9.

Table 13-9. Events in the Asterisk queue log

EventInformation provided
ABANDONWritten when a caller in a queue hangs up before his call is answered by an agent. Three parameters are provided for ABANDON: the position of the caller at hangup, the original position of the caller when entering the queue, and the amount of time the caller waited prior to hanging up.
ADDMEMBERWritten when a member is added to the queue. The bridged channel name will be populated with the name of the channel added to the queue.
AGENTDUMPIndicates that the agent hung up on the caller while the queue announcement was being played, prior to them being bridged together.
AGENTLOGINRecorded when an agent logs in. The bridged channel field will contain something like Agent/9994 if logging in with chan_agent, and the first parameter field will contain the channel logging in (e.g., SIP/0000FFFF0001).
AGENTLOGOFFLogged when an agent logs off, along with a parameter indicating how long the agent was logged in for.
COMPLETEAGENTRecorded when a call is bridged to an agent and the agent hangs up, along with parameters indicating the amount of time the caller was held in the queue, the length of the call with the agent, and the original position at which the caller entered the queue.
COMPLETECALLERSame as COMPLETEAGENT, except the caller hung up and not the agent.
CONFIGRELOADIndicates that the queue configuration was reloaded (e.g., via module reload app_queue.so).
CONNECTWritten when the caller and the agent are bridged together. Three parameters are also written: the amount of time the caller waited in the queue, the unique ID of the queue member’s channel to which the caller was bridged, and the amount of time the queue member’s phone rang prior to being answered.
ENTERQUEUEWritten when a caller enters the queue. Two parameters are also written: the URL (if specified) and the caller ID of the caller.
EXITEMPTYWritten when the caller is removed from the queue due to a lack of agents available to answer the call (as specified by the leavewhenempty parameter). Three parameters are also written: the position of the caller in the queue, the original position at which the caller entered the queue, and the amount of time the caller was held in the queue.
EXITWITHKEYWritten when the caller exits the queue by pressing a single DTMF key on his phone to exit the queue and continue in the dialplan (as enabled by the context parameter in queues.conf). Four parameters are recorded: the key used to exit the queue, the position of the caller in the queue upon exit, the original position the caller entered the queue at, and the amount of time the caller was waiting in the queue.
EXITWITHTIMEOUTWritten when the caller is removed from the queue due to timeout (as specified by the timeout parameter to Queue()). Three parameters are also recorded: the position the caller was in when exiting the queue, the original position of the caller when entering the queue, and the amount of time the caller waited in the queue.
PAUSEWritten when a queue member is paused.
PAUSEALLWritten when all members of a queue are paused.
UNPAUSEWritten when a queue member is unpaused.
UNPAUSEALLWritten when all members of a queue are unpaused.
PENALTYWritten when a member’s penalty is modified. The penalty can be changed through several means, such as the QUEUE_MEMBER_PENALTY() function, through using Asterisk Manager Interface, or the Asterisk CLI commands.
REMOVEMEMBERWritten when a queue member is removed from the queue. The bridge channel field will contain the name of the member removed from the queue.
RINGNOANSWERLogged when a queue member is rung for a period of time, and the timeout value for ringing the queue member is exceeded. A single parameter will also be written indicating the amount of time the member’s extension rang.
TRANSFERWritten when a caller is transferred to another extension. Additional parameters are also written, which include: the extension and context the caller was transferred to, the hold time of the caller in the queue, the amount of time the caller was speaking to a member of the queue, and the original position of the caller when he entered the queue.[a]
SYSCOMPATRecorded if an agent attempts to answer a call, but the call cannot be set up due to incompatibilities in the media setup.

[a] Please note that when the caller is transferred using SIP transfers (rather than the built-in transfers triggered by DTMF and configured in features.conf), the TRANSFER event may not be reliable.

Conclusion

We started this chapter with a look at basic call queues, discussing what they are, how they work, and when you might want to use one. After building a simple queue, we explored how to control queue members through various means (including the use of Local channels, which provide the ability to perform some dialplan logic just prior to connecting to a queue member). We also explored all the options available to us in the queues.conf, agents.conf, and queuerules.conf files, which offer us fine-grained control over any queues we configure. Of course, we need the ability to monitor what our queues are doing, so we looked finally at the queue log and the myriad of events and event parameters written when various things happen in our queues.

With the knowledge provided in this chapter, you should be well on your way to implementing a successful set of queues for your company.



[126] It is a common misconception that a queue can allow you to handle more calls. This is not strictly true, in that your callers will still want to speak to a live person, and they will only be willing to wait for so long. In other words, if you are short-staffed, your queue could end up being nothing more than an obstacle to your callers. The ideal queue is invisible to the callers, since their calls get answered immediately without them having to hold.

[127] There are several books available that discuss call center metrics and available queuing strategies, such as James C. Abbott’s The Executive Guide to Call Center Metrics (Robert Houston Smith).

[128] Wrapup time is used for agents who may need to perform some sort of logging or other function once a call is done. It gives them a grace period of several seconds in order to perform this task before taking another call.

[129] Callers’ positions and hold times are only announced if more than one person is holding in the queue.

[130] If the priority n+1 from where the Queue() application was called is not defined, the call will be hung up.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset