Follow-Me via Local Channels
Follow-Me via Local Channels
When a call goes unanswered at desk phones, you often want to try cell phones next. This snippet uses Local channels to dial external numbers through a separate context, which lets you control the outbound caller ID independently for each leg. The important part is preserving the original inbound DID as the outbound CID so the cell phone user sees who's actually calling.
The Snippet
The subroutine that rings all cell phones simultaneously:
[followme-all]
exten => s,1,NoOp(Follow Me All - Ringing all cell phones)
; Build dial string using Local channels
; Each Local channel routes through followme-external context
same => n,Set(CELL_DIAL_STRING=Local/CELL_100@followme-external&Local/CELL_101@followme-external&Local/CELL_102@followme-external)
same => n,Dial(${CELL_DIAL_STRING},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()
The external dialing context that handles CID and trunk routing:
[followme-external]
; Extension 100's cell phone
exten => CELL_100,1,NoOp(Follow Me to cell - ext 100)
same => n,Set(CALLERID(num)=${IF($["${INCOMING_DID}" != ""]?${INCOMING_DID}:${MAIN_DID})})
same => n,Dial(PJSIP/18025551234@${TRUNK},30,tT)
same => n,Hangup()
; Extension 101's cell phone
exten => CELL_101,1,NoOp(Follow Me to cell - ext 101)
same => n,Set(CALLERID(num)=${IF($["${INCOMING_DID}" != ""]?${INCOMING_DID}:${MAIN_DID})})
same => n,Dial(PJSIP/16035559876@${TRUNK},30,tT)
same => n,Hangup()
; Extension 102's cell phone
exten => CELL_102,1,NoOp(Follow Me to cell - ext 102)
same => n,Set(CALLERID(num)=${IF($["${INCOMING_DID}" != ""]?${INCOMING_DID}:${MAIN_DID})})
same => n,Dial(PJSIP/17815554321@${TRUNK},30,tT)
same => n,Hangup()
The inbound context sets __INCOMING_DID (double underscore) so it inherits through Local channel hops:
[from-trunk]
exten => 16035551000,1,NoOp(Inbound call from ${CALLERID(num)})
same => n,Set(__INCOMING_DID=${EXTEN})
same => n,GotoIfTime(08:00-17:00,mon-fri,*,*?ring-group,s,1)
same => n,Goto(afterhours,s,1)
How It Works
Local channels create a new channel pair inside Asterisk that connects two dialplan contexts. When you Dial(Local/CELL_100@followme-external), Asterisk creates a mini-call that starts executing at CELL_100 in the followme-external context. This gives you a separate dialplan execution environment where you can set CID, apply different logic, or route through a specific trunk.
Double-underscore inheritance (__INCOMING_DID): Variables prefixed with __ inherit across channel boundaries, including Local channel hops. A single underscore _ only inherits one hop deep. Since the call flow is: inbound trunk -> Local channel -> outbound trunk, we need __ (two hops).
CID preservation: The followme-external context sets CALLERID(num) to the original inbound DID. This means when the cell phone rings, the caller ID shows the company's number, not the PBX's trunk number. If no inbound DID is set (e.g., internal call), it falls back to MAIN_DID.
Why Local Channels Instead of app_followme
app_followme (the FollowMe() application with followme.conf) works well for simple cases. Local channels give you more control:
- Per-leg CID: Each cell phone can get a different outbound CID
- Per-leg trunk: You could route different numbers through different trunks
- Simultaneous ring: All cell phones ring at once (app_followme rings them sequentially by default)
- Custom logic: You can add any dialplan logic before the external dial (time checks, DND for cell phones, etc.)
Calling from After-Hours Context
[afterhours]
exten => s,1,Answer()
same => n,Wait(1)
same => n,Playback(after-hours-greeting)
; Try desk phones first
same => n,Dial(PJSIP/100&PJSIP/101&PJSIP/102,20,tTr)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)
; No answer at desk - try cell phones
same => n,Gosub(followme-all,s,1)
same => n,GotoIf($["${GOSUB_RETVAL}" = "ANSWERED"]?done)
; Still no answer - voicemail
same => n,VoiceMail(100@default,u)
same => n(done),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.