Skip to main content

Tenants

A Tenant document describes one customer. The minimum is a domain; everything else has a global fallback or a default.

configVersion: lyno/v1
kind: Tenant
metadata:
name: acme # the identifier; it namespaces everything below
spec:
description: Acme Industries
domains:
- acme.pbx.example.com
- voice.acme.example

dids:
numbers: ["+31201234567", "+31201234568"]
patterns: ['^\+312012345\d{2}$']

callerId:
number: "+31201234567"
name: Acme

timezone: Europe/Amsterdam

moh:
classes:
jazz: ../sounds/moh/jazz.wav

interTenant: [globex]

Domains

domains: [acme.pbx.example.com]

The SIP domains this tenant's phones register with. They select the tenant for any endpoint request and become the digest realm for that tenant's authentication.

  • Domains must be unique across tenants; validation refuses a duplicate.
  • A tenant may have several domains, which is useful during a migration.
  • Phones must use the domain as their SIP domain and the PBX address as their outbound proxy.
  • At least one is required. A tenant with none is a validation error, because nothing could ever be attributed to it.

DIDs

dids:
numbers: ["+31201234567"]
patterns: ['^\+312012345\d{2}$']

The numbers this tenant owns on the shared trunks. numbers are exact claims, patterns are Go regular expressions matched against the called number.

Exact claims are checked before patterns, and a number claimed by two tenants is a validation error. An InboundOverride document beats both — see Inbound routing.

Anchor your patterns

^ and $ are not implied. '^\+312012345\d{2}$' claims a block of one hundred numbers; '\+312012345' would match any number containing that string.

Two patterns that overlap cannot be decided statically, so validation lets them through and the runtime resolves them by tenant name order. Exact-against-pattern clashes are caught.

Caller ID

callerId:
number: "+31201234567"
name: Acme

Presented on outgoing calls, overriding the trunk's own caller ID. That is what lets several tenants share one carrier account and still each present their own number.

The resolution order is: the tenant's callerId, then the trunk's callerId, then nothing.

Timezone

timezone: Europe/Amsterdam

Used by this tenant's time conditions, overriding the global timezone. An individual condition can override it again with its own timezone: key. Names come from the IANA database.

Music on hold

moh:
default: ../sounds/moh/acme.wav
classes:
jazz: ../sounds/moh/jazz.wav

The tenant's classes are layered on top of the global ones, key by key: a class name defined in both resolves to the tenant's file. A dial, moh or queue step then refers to a class by name.

Paths resolve against the configuration root, like every other path.

Name a class after its file

golden-hold: ../sounds/moh/golden-hold.wav reads better in a dialplan than class2: does, and a step naming it then says what the caller will hear.

What lives inside a tenant

Accounts, dialplans, ring groups, queues, menus, mailboxes and routes are documents of their own, tied to this tenant by metadata.tenant. They are not keys inside the tenant:

---
configVersion: lyno/v1
kind: Queue
metadata:
name: support # the name a dialplan refers to
tenant: acme # the namespace it means something in
spec:
strategy: fewestrecent
agents: ["100", "101"]
---
configVersion: lyno/v1
kind: Mailbox
metadata:
name: "100" # the account extension whose messages it holds
tenant: acme
spec:
displayName: Alice
pin: "4821"
email: alice@example.com
Kindmetadata.name isPage
Accountthe SIP usernameAccounts and extensions
Extensionthe number the plan answers onDialplans
RingGroupa name you chooseRing groups
Queuea name you chooseCall queues
IVRMenua name you chooseIVR menus
Mailboxthe account extension whose messages it holdsVoicemail
InboundRoutea label for the routeInbound routing
OutboundRoutea label for the routeOutbound routing

A queue, a menu or a mailbox is a document rather than a field on the tenant for the same reason a ring group is: several dialplans can share one, and a name means nothing outside the tenant that defined it.

Overriding the global blocks

A tenant may override recording, storage, voicemail, smtp and security field by field — a key it does not mention keeps the global value:

kind: Tenant
metadata: { name: acme }
spec:
domains: [acme.pbx.example.com]
voicemail:
maxMessage: 5m # this tenant only
smtp:
from: Acme Phones <phones@acme.example>

Not all of them take effect. storage and security.acl are merged and then ignored, and only enabled and mode are honoured from recording. The reference has the table.

A tenant can also take a dataDir of its own instead of a subdirectory of the global one — see The data directory.

Other keys

KeyPurpose
descriptionFree text, shown in the editor.
interTenantTenants this one may dial directly. Needs the global allowInterTenant.
featureCodesValidated, and not dispatched on — see Feature codes.

The full key-by-key listing is in the tenant configuration reference.