Reusable Dial-User Subroutine
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:
- DND: if
DB(DND/100)isyes, go straight to voicemail. No ring, no disturbance. - 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. - Follow Me: if
DB(FM/100)isyes, useapp_followmeto try the user's configured follow-me numbers (defined infollowme.conf). - Normal Dial: ring the PJSIP endpoint for 30 seconds with transfer enabled (
tT) and recording enabled (xX). - 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.