After-Hours Escalation Chain

Routing Asterisk 18+ -- Last reviewed 2026-03-29 after-hours escalation follow-me gosub routing Found this useful? Upvote it. ×

After-Hours Escalation Chain

When a call arrives outside business hours, don't just dump it to voicemail. 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

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

  1. 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 dialplan. just drop the file in place.
  2. Tiered fallback: Each tier attempts to reach someone. If DIALSTATUS is ANSWER, we skip straight to done. Otherwise we fall through to the next tier.
  3. Local channels for cell phones: Local/CELL_1001@followme-external creates a "virtual" channel that executes the CELL_1001 extension in the followme-external context. This lets you set outbound caller ID and route through the trunk properly for each cell call.
  4. Caller ID preservation: The followme-external context 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.
  5. GOSUB_RETVAL: The subroutine communicates back to the caller via this special variable, which Gosub() can read after Return().

Tips

User Notes

No notes yet. Be the first to contribute a tip or example.

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.

Moderated before publishing. Email never shown.

Related Snippets