Outbound routing
When a dialled number is not a local extension, outbound routes decide which trunk carries it and what number is actually sent.
The order
- the calling tenant's own
OutboundRoutedocuments, inpriorityorder; - the
OutboundRoutedocuments with no tenant — the global fallback — inpriorityorder; defaultTrunk, with the number unchanged.
The first pattern that matches wins — within a list and across the fallback. If
nothing matches and there is no defaultTrunk, the call fails.
A route
Each route is a document. Without metadata.tenant it belongs to the global
fallback set:
configVersion: lyno/v1
kind: OutboundRoute
metadata:
name: emergency
spec:
priority: 10
pattern: '^112$'
trunk: carrier
---
configVersion: lyno/v1
kind: OutboundRoute
metadata:
name: national
spec:
priority: 20
pattern: '^0[1-9]\d{8}$'
strip: 1
prepend: "+31"
trunk: carrier
---
configVersion: lyno/v1
kind: OutboundRoute
metadata:
name: international
spec:
priority: 30
pattern: '^\+\d+$'
trunk: carrier
---
configVersion: lyno/v1
kind: System
spec:
defaultTrunk: carrier
| Field | Required | Meaning |
|---|---|---|
metadata.name | yes | Label, used in logs, error messages and to break ties. |
priority | no | Ascending. This is what decides which route is tried first. |
pattern | yes | Go regular expression matched against the dialled number. |
strip | no | Digits removed from the front of the number. |
prepend | no | Text put in front of what remains. |
trunk | yes | The trunk to use. Must exist. |
Manipulation is applied in that order: match, then strip, then prepend. In the
national example, 0201234567 matches, loses its leading 0, and is sent as
+31201234567.
priorityRoutes are first-match-wins in ascending priority, so put the specific ones on
the lower numbers. ^112$ before ^\d+$ is the difference between reaching
emergency services and reaching your carrier's error announcement.
Document order is not consulted — it cannot be, when a route may live in any file. Leave gaps (10, 20, 30) so a route can be inserted later; saving renumbers to exactly that.
Per-tenant routes
A tenant's routes are tried before the global ones, which is how one tenant gets its own carrier for a subset of numbers without affecting anybody else:
configVersion: lyno/v1
kind: OutboundRoute
metadata:
name: acme-mobile
tenant: acme # this is what makes it acme's own
spec:
priority: 10
pattern: '^06\d{8}$'
strip: 1
prepend: "+31"
trunk: acme-mobile-carrier
Everything acme dials that is not a Dutch mobile falls through to the global routes as usual.
Caller ID
The number presented is the calling tenant's callerId, falling back to
the trunk's callerId. That is what allows several tenants to share one
carrier account and still each present their own number.
Calling an external number from a dialplan
Inside a dialplan, an external destination is written external:<number>, and
that number goes through exactly the routing described above:
- id: follow-me
type: dial
target:
kind: followme
stages:
- targets: ["101", "external:0612345678"]
timeout: 25s
or as a direct target:
- id: forward
type: dial
target:
kind: external
number: "+31612345678"
Debugging a route
- Run with
-debug: the matched route name and the resulting number are logged when a call is placed. - A call that leaves on the wrong trunk is almost always an earlier route
matching more broadly than intended — check anchoring (
^and$). - A call that fails with no trunk means nothing matched and
defaultTrunkis unset.