After-Hours Escalation Chain
After-Hours Escalation Chain
When a call arrives outside business hours, don't just dump it to voicemailAsterisk's built-in voice messaging system (app_voicemail), configured in voicemail.conf with mailboxes per user.. This pattern plays a greeting, tries desk phones (someone might be working late), escalates to cell phones, and only goes to voicemail as a last resort.
Requirements
- A
[followme-external]contextA named section of the dialplan that groups extensions. Calls enter a specific context and can only reach extensions visible from it, making contexts the basic unit of call routing and security. mapping each cell extensionA dialplan entry that matches a dialed number or pattern within a context and triggers a sequence of prioritized steps. to its outbound number (see the Cell Phone Subroutine section below) - Custom greeting audio files (with automatic fallback to built-in prompts)
func_stat.somodule loaded (for STAT() file existence checks)MAIN_DIDset as a channelA single call leg passing through Asterisk. Channels represent connections to endpoints and are what dialplan applications act on. variable in your inbound context (used as the outbound caller IDThe calling party number (and optionally name) presented on an outbound call, set from the endpoint or manipulated in the dialplan. fallback whenINCOMING_DIDis not available)
After-Hours Context
[afterhours-sales]
exten => s,1,NoOp(After Hours - Sales)
same => n,Answer()
same => n,Wait(1)
; Play custom greeting if the file exists, otherwise use built-in prompts
same => n,Set(AH_GREETING=/var/lib/asterisk/sounds/custom/afterhours-sales)
same => n,GotoIf($[${STAT(e,${AH_GREETING}.wav)} || ${STAT(e,${AH_GREETING}.ulaw)}]?play_custom)
; Fallback: generic after-hours message from built-in sounds
same => n,Playback(thank-you-for-calling)
same => n,Playback(hours)
same => n,Playback(vm-runout)
same => n,Goto(try_desk)
same => n(play_custom),Playback(${AH_GREETING})
; Tier 1: Ring desk phones
same => n(try_desk),NoOp(Ringing desk phones)
same => n,Dial(PJSIP/1001&PJSIP/1002&PJSIP/1003,20,tTr)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)
; Tier 2: Escalate to cell phones via subroutine
same => n,NoOp(No answer at desk, trying cell phones)
same => n,Gosub(followme-all,s,1)
same => n,GotoIf($["${GOSUB_RETVAL}" = "ANSWERED"]?done)
; Tier 3: Voicemail
same => n,NoOp(No answer anywhere, going to voicemail)
same => n,VoiceMail(sales@default,u)
same => n(done),Hangup()
Cell Phone Subroutine
[followme-all]
exten => s,1,NoOp(Ringing all cell phones simultaneously)
; Use Local channels so each cell call routes through the external
; dialing context (which sets proper outbound caller ID)
same => n,Set(CELLS=Local/CELL_1001@followme-external&Local/CELL_1002@followme-external&Local/CELL_1003@followme-external)
same => n,Dial(${CELLS},30,tT)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?answered)
same => n,Set(GOSUB_RETVAL=NOANSWER)
same => n,Return()
same => n(answered),Set(GOSUB_RETVAL=ANSWERED)
same => n,Return()
[followme-external]
; Each CELL_XXXX extension dials out through the trunk
exten => CELL_1001,1,NoOp(Follow Me to 1001 cell)
same => n,Set(CALLERID(num)=${IF($["${INCOMING_DID}" != ""]?${INCOMING_DID}:${MAIN_DID})})
same => n,Dial(PJSIP/18025551001@${TRUNK},30,tT)
same => n,Hangup()
exten => CELL_1002,1,NoOp(Follow Me to 1002 cell)
same => n,Set(CALLERID(num)=${IF($["${INCOMING_DID}" != ""]?${INCOMING_DID}:${MAIN_DID})})
same => n,Dial(PJSIP/18025551002@${TRUNK},30,tT)
same => n,Hangup()
How it works
- STAT(e, path): Checks if a file exists. Returns 1 if it exists, 0 otherwise. This lets you deploy custom greetings per department without changing the dialplanThe core call-routing configuration of Asterisk, written mostly in extensions.conf as contexts, extensions, and priorities that decide how every call is handled. Full definition →, just drop the file in place.
- Tiered fallback: Each tier attempts to reach someone. If
DIALSTATUSisANSWER, we skip straight todone. Otherwise we fall through to the next tier. - Local channels for cell phones:
Local/CELL_1001@followme-externalcreates a "virtual" channel that executes theCELL_1001extension in thefollowme-externalcontext. This lets you set outbound caller ID and route through the trunkA connection between Asterisk and another PBX or an ITSP/carrier, used to send and receive external calls. properly for each cell call. - Caller ID preservation: The
followme-externalcontext sets the caller ID to the original inbound DID (stored earlier as__INCOMING_DID). The double underscore (__) makes the variable inherit across channels, including Local channel hops.MAIN_DIDis used as a fallback whenINCOMING_DIDis not set. - GOSUB_RETVAL: The subroutine communicates back to the caller via this special variable, which
Gosub()can read afterReturn().
Tips
- Set both
__INCOMING_DIDand__MAIN_DID(double underscore) early in your inbound context so they propagate through Local channels. - The Wait(1) after Answer() prevents audio clipping: some carriers need a moment before audio is ready.
- Test the STAT() check by temporarily renaming the greeting file and confirming the fallback plays.
- For a more sophisticated setup, use
followme.confwith sequential number entries instead of the manual[followme-external]context.
User Notes
Know a tip or gotcha for this topic? Share it below and help others.
Contribute a note
Share a tip, gotcha, or practical example. Keep it under 2000 characters. No questions (use the Asterisk community forums for support). Wrap code in backticks.