Queues and IVR
Two of the most common PBX features build directly on the dialplan: an IVR (Interactive Voice Response, or "auto-attendant") that greets callers and routes them by keypad input, and queues (ACD — Automatic Call Distribution) that hold callers until an agent is free. This page covers both.
Interactive Voice Response (IVR)
An IVR plays a menu prompt, collects a DTMF digit, and jumps to the matching extension in a context. The building blocks are dialplan applications:
| Application | Role |
|---|---|
Background(file) |
Plays a prompt while listening for a digit; a pressed digit interrupts playback and matches an extension |
WaitExten(sec) |
After the prompt finishes, wait this long for a digit |
Read(var,file,digits) |
Collect a fixed number of digits into a variable (e.g. an account number) |
Playback(file) |
Play a prompt without collecting input |
Goto() / GotoIf() |
Move between menus and options |
A Basic Auto-Attendant
; extensions.conf
[ivr-main]
exten => s,1,Answer()
same => n,Set(TIMEOUT(digit)=3) ; inter-digit timeout
same => n,Set(TIMEOUT(response)=10) ; overall response timeout
same => n(menu),Background(custom/main-menu) ; "Press 1 for sales, 2 for support..."
same => n,WaitExten(10)
; Menu options — each dialed digit matches its own extension
exten => 1,1,Goto(internal,6001,1) ; 1 -> Sales
exten => 2,1,Goto(queue-support,s,1) ; 2 -> Support queue
exten => 3,1,VoiceMailMain(@default) ; 3 -> check voicemail
exten => 0,1,Dial(PJSIP/6000,20) ; 0 -> operator
same => n,Voicemail(6000@default,u)
same => n,Hangup()
; Caller pressed an invalid key
exten => i,1,Playback(pbx-invalid)
same => n,Goto(s,menu)
; Caller didn't press anything in time
exten => t,1,Playback(vm-goodbye)
same => n,Hangup()
Callers reach this by routing an inbound DID (or an internal feature code) to Goto(ivr-main,s,1) — see Endpoints and Trunks.
Nested / Sub-Menus
Give each submenu its own context and route back to the parent with Goto:
[ivr-support]
exten => s,1,Background(custom/support-menu) ; "1 for billing, 2 for technical..."
same => n,WaitExten(10)
exten => 1,1,Goto(queue-billing,s,1)
exten => 2,1,Goto(queue-tech,s,1)
exten => 9,1,Goto(ivr-main,s,1) ; 9 -> back to main menu
exten => i,1,Playback(pbx-invalid)
same => n,Goto(s,1)
exten => t,1,Goto(ivr-main,s,1)
Collecting Input with Read
Use Read when you need several digits (an account or PIN), then act on the value:
[ivr-account]
exten => s,1,Answer()
same => n,Read(ACCOUNT,custom/enter-account,6) ; collect up to 6 digits
same => n,GotoIf($["${ACCOUNT}" = ""]?s,1) ; nothing entered -> retry
same => n,NoOp(Caller entered account ${ACCOUNT})
same => n,AGI(lookup-account.agi,${ACCOUNT}) ; hand off to a script, DB lookup, etc.
same => n,Hangup()
Time-Based Routing (Business Hours)
Route differently by time of day with GotoIfTime (times,weekdays,monthdays,months). Wildcards are *:
[inbound]
exten => s,1,NoOp(Inbound call at ${STRFTIME(${EPOCH},,%H:%M %A)})
; Mon–Fri 09:00–17:00 -> open menu
same => n,GotoIfTime(09:00-17:00,mon-fri,*,*?ivr-main,s,1)
; Otherwise -> after-hours greeting
same => n,Playback(custom/after-hours)
same => n,Voicemail(6000@default,u)
same => n,Hangup()
; Holidays override normal hours (place before the hours check)
exten => s,1,GotoIfTime(*,*,25,dec?closed,1) ; Christmas Day
Tip
Record custom prompts as 8 kHz, mono, signed-linear WAV (or GSM/ulaw) and place them under /var/lib/asterisk/sounds/custom/, referencing them without the extension (custom/main-menu). Keep menus short (3–5 options), state the option before the digit ("for sales, press 1"), and always handle i (invalid) and t (timeout).
Call Queues (ACD)
A queue holds callers and distributes them to members (agents) according to a strategy. Queues are defined in queues.conf and entered from the dialplan with the Queue() application.
queues.conf
; queues.conf
[general]
persistentmembers = yes ; remember dynamic members across restarts
monitor-type = MixMonitor ; enable call recording hooks
[support]
strategy = rrmemory ; round-robin with memory (see table below)
timeout = 20 ; seconds to ring a member before trying the next
retry = 5 ; seconds between ring attempts
wrapuptime = 15 ; cooldown after a call before an agent gets another
musicclass = default ; music-on-hold class while waiting
maxlen = 0 ; 0 = unlimited callers waiting
ringinuse = no ; don't ring agents already on a call
joinempty = no ; don't let callers join if no members are available
leavewhenempty = yes ; eject waiting callers if all members leave
; Announcements to waiting callers
announce-frequency = 30 ; re-announce every 30s
announce-holdtime = yes ; "estimated hold time is..."
announce-position = yes ; "you are caller number..."
periodic-announce = custom/thanks-for-holding
; Static members (always in the queue)
member => PJSIP/6001
member => PJSIP/6002
Distribution Strategies
Set with strategy = in the queue:
| Strategy | Behavior |
|---|---|
ringall |
Ring all available members at once; first to answer wins |
leastrecent |
Ring the member who least recently took a call |
fewestcalls |
Ring the member who has completed the fewest calls |
random |
Ring a random available member |
rrmemory |
Round-robin, remembering where it left off (even distribution) |
rrordered |
Round-robin in the exact member order listed |
linear |
Always try members in listed order, from the top (priority hunt) |
wrandom |
Random, weighted by each member's penalty |
Entering a Queue from the Dialplan
[queue-support]
exten => s,1,Answer()
same => n,Playback(custom/all-agents-busy)
same => n,Queue(support,tT,,,300) ; join "support"; 300s max wait
same => n,Playback(custom/still-busy-voicemail)
same => n,Voicemail(6000@default,u) ; timed out in queue -> voicemail
same => n,Hangup()
Queue(queuename,options,URL,announceoverride,timeout) — common options:
| Option | Effect |
|---|---|
t / T |
Allow the called / calling party to transfer |
h / H |
Allow either party to hang up with a key |
c |
Continue in the dialplan even if the queue was empty |
n |
No retries — try members once then leave the queue |
When Queue() returns (caller was answered elsewhere, timed out, or hung up), execution continues at the next priority — use that to send timed-out callers to voicemail.
Static vs Dynamic Members
- Static members (
member =>inqueues.conf) are always part of the queue. - Dynamic members log in and out at runtime — the basis of "agent login". Manage them from the dialplan:
[agent-features]
; *51 — log this phone into the support queue
exten => *51,1,AddQueueMember(support,PJSIP/${CALLERID(num)})
same => n,Playback(agent-loginok)
same => n,Hangup()
; *52 — log out
exten => *52,1,RemoveQueueMember(support,PJSIP/${CALLERID(num)})
same => n,Playback(agent-loggedoff)
same => n,Hangup()
; *53 / *54 — pause / unpause (e.g. on a break; stays a member but isn't rung)
exten => *53,1,PauseQueueMember(support,PJSIP/${CALLERID(num)})
same => n,Playback(vm-goodbye)
same => n,Hangup()
exten => *54,1,UnpauseQueueMember(support,PJSIP/${CALLERID(num)})
same => n,Hangup()
Note
The old AgentLogin/chan_agent mechanism is deprecated. Use dynamic queue members (AddQueueMember/RemoveQueueMember) with persistentmembers = yes so logins survive restarts. Penalties (member => PJSIP/6001,1) create tiers — lower-penalty members are tried first.
Monitoring Queues
asterisk*CLI> queue show ; all queues, callers, and members
asterisk*CLI> queue show support ; one queue in detail
asterisk*CLI> queue add member PJSIP/6003 to support
asterisk*CLI> queue remove member PJSIP/6003 from support
asterisk*CLI> queue pause member PJSIP/6001 queue support reason lunch
Queue activity is written to the queue_log (/var/log/asterisk/queue_log) — join, connect, complete, abandon, and agent events — which drives reporting/wallboard tools. See Monitoring for logging setup.
Queue Best Practices
- Set
wrapuptimeso agents get a breather between calls; setringinuse = noso busy agents aren't rung. - Announce position and hold time (
announce-position,announce-holdtime) and offer an exit (voicemail or callback) so callers aren't trapped. - Give the
Queue()call a sensibletimeoutand route timed-out callers onward — never leave a bareQueue()with no fallback. - Prefer
rrmemoryfor even distribution, orlinear/penalties for tiered/priority routing. - Enable
persistentmembersso dynamic agent logins survive an Asterisk restart.