Reusable Dial-User Subroutine

Call Handling Asterisk 20+ -- Last reviewed 2026-03-29 call-handling gosub dnd call-forward follow-me astdb architecture Found this useful? Upvote it. ×

Reusable Dial-User Subroutine

Instead of duplicating DND checks, call forward logic, and voicemail fallback in every extension definition, this pattern centralizes all per-user call handling into a single Gosub. Every extension calls the same subroutine, passing just the extension number and display name.

The Snippet

The subroutine itself:

[dial-user]
exten => s,1,NoOp(Dialing ${ARG1} - ${ARG2})
 ; Check DND (stored in AstDB)
 same => n,Set(DND=${DB(DND/${ARG1})})
 same => n,GotoIf($["${DND}" = "yes"]?dnd)
 ;
 ; Check Call Forward
 same => n,Set(CF=${DB(CF/${ARG1})})
 same => n,GotoIf($["${CF}" != ""]?forward)
 ;
 ; Check Follow Me
 same => n,Set(FM=${DB(FM/${ARG1})})
 same => n,GotoIf($["${FM}" = "yes"]?followme)
 ;
 ; Normal dial
 same => n,Dial(PJSIP/${ARG1},30,tTxX)
 same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
 same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer)
 same => n,GotoIf($["${DIALSTATUS}" = "CHANUNAVAIL"]?unavail)
 same => n,Return()
 ;
 same => n(dnd),VoiceMail(${ARG1}@default,u)
 same => n,Return()
 ;
 same => n(forward),NoOp(Call Forward to ${CF})
 ; Internal extension (pattern match) - dial directly
 same => n,GotoIf($["${CF}" : "^[0-9][0-9][0-9][0-9]?$"]?fwd_internal)
 ; Matches 3-4 digit internal extensions; adjust pattern to match your range
 ; External number - route through trunk
 same => n,Dial(PJSIP/1${CF}@${TRUNK},120,tT)
 same => n,VoiceMail(${ARG1}@default,u)
 same => n,Return()
 same => n(fwd_internal),Dial(PJSIP/${CF},30,tT)
 same => n,VoiceMail(${ARG1}@default,u)
 same => n,Return()
 ;
 same => n(followme),FollowMe(${ARG1},ds)
 same => n,GotoIf($["${FMSTATUS}" = "SUCCESS"]?:noanswer)
 same => n,Return()
 ;
 same => n(busy),VoiceMail(${ARG1}@default,b)
 same => n,Return()
 ;
 same => n(noanswer),VoiceMail(${ARG1}@default,u)
 same => n,Return()
 ;
 same => n(unavail),VoiceMail(${ARG1}@default,u)
 same => n,Return()

Calling the subroutine from your extension definitions:

[dial-extension]
exten => 100,1,NoOp(Dial Alice)
 same => n,Gosub(dial-user,s,1(100,Alice))
 same => n,Hangup()

exten => 101,1,NoOp(Dial Bob)
 same => n,Gosub(dial-user,s,1(101,Bob))
 same => n,Hangup()

exten => 102,1,NoOp(Dial Carol)
 same => n,Gosub(dial-user,s,1(102,Carol))
 same => n,Hangup()

; Fallback pattern for any extension in range
exten => _1XX,1,NoOp(Dial extension ${EXTEN})
 same => n,Dial(PJSIP/${EXTEN},30,tT)
 same => n,VoiceMail(${EXTEN}@default,u)
 same => n,Hangup()

How It Works

The subroutine checks features in priority order:

  1. DND: if DB(DND/100) is yes, go straight to voicemail. No ring, no disturbance.
  2. Call Forward: if DB(CF/100) is set to a number, dial that number instead. Handles both internal (direct PJSIP dial) and external (route through trunk) forwarding.
  3. Follow Me: if DB(FM/100) is yes, use app_followme to try the user's configured follow-me numbers (defined in followme.conf).
  4. Normal Dial: ring the PJSIP endpoint for 30 seconds with transfer enabled (tT) and recording enabled (xX).
  5. DIALSTATUS routing: after Dial(), route to appropriate voicemail greeting (busy vs unavailable).

Why This Pattern Matters

Without this subroutine, every extension in your dialplan would need its own DND check, CF check, FM check, and DIALSTATUS handling. For a 50-extension system, that's thousands of duplicated lines. With this pattern, adding a new extension is one Gosub call.

The feature state lives in AstDB, which means users can toggle features from their phones using feature codes (*78/*79 for DND, *72/*73 for CF, *21/*22 for Follow Me) without any dialplan reload.

Companion Feature Codes

These feature codes set the AstDB values that the subroutine checks:

[features]
; *78/*79 - DND Enable/Disable
exten => *78,1,Set(DB(DND/${CALLERID(num)})=yes)
 same => n,Playback(beep)
 same => n,Hangup()
exten => *79,1,Set(DB_DELETE(DND/${CALLERID(num)})=)
 same => n,Playback(beep)
 same => n,Hangup()

; *72/*73 - Call Forward Enable/Disable
exten => *72,1,Read(FWD_NUM,vm-enter-num-to-call,15)
 same => n,GotoIf($["${FWD_NUM}" = ""]?cancel)
 same => n,Set(DB(CF/${CALLERID(num)})=${FWD_NUM})
 same => n,Playback(beep)
 same => n,SayDigits(${FWD_NUM})
 same => n,Hangup()
 same => n(cancel),Playback(cancelled)
 same => n,Hangup()
exten => *73,1,Set(DB_DELETE(CF/${CALLERID(num)})=)
 same => n,Playback(beep)
 same => n,Hangup()

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