Skip to main content

The voicemail and MWI model

A voicemail is a captured inbound call recording filed into one mailbox. The Message-Waiting Indicator (MWI) is the per-mailbox count of unread versus read messages that a desk phone lamp or the browser softphone envelope badge renders. This page defines the whole model once — capture sources, mailbox identity, the count semantics, and the ownership of the notify path — so the recording library and extensions guides stay thin procedure surfaces. Read this before you wire a queue’s overflow to voicemail or build a client against GET /api/v1/voice/mwi. Voicemail capture is an inbound-record flow. Outbound MT voice and SMS termination stay on the Devotel wholesale softswitch; nothing on this page touches a termination path.

1. What MWI is and when it fires

MWI is the same idea every PBX has shipped for decades: a per-mailbox pair of counters — new (unread) and old (read-but-retained) messages — plus one boolean, Messages-Waiting. In the SIP world that payload is a NOTIFY carrying an application/simple-message-summary body (RFC 3842) for the message-summary event package; a Polycom, Yealink, Grandstream, or Bria client subscribes to it and lights its lamp when the new count is non-zero. The platform derives those counters from the voicemails rows already stored for the tenant — there is no separate MWI table and nothing to provision per mailbox. The derivation is one grouped aggregate per tenant:
  • new = inbox messages with is_read = false
  • old = inbox messages with is_read = true
  • Messages-Waiting = new > 0
Only the active inbox folder counts toward the lamp. A voicemail the spam classifier foldered never flips the lamp and never raises the badge — the unread badge an operator sees tracks messages that actually need attention. MWI fires (is republished) whenever a capture or toggle path changes the counts: a new voicemail lands, or an operator marks one read or unread. Each of those paths publishes a Redis wake-up, and every listener re-reads the full snapshot rather than tracking deltas.

2. Where the MWI hooks sit

Three components share the work. Together they cover the SIP lamp and the browser badge with no duplicated state.
  • Voicemail capture service — persists the voicemails row with its mailbox identity (assigned_user_id or box_id, never both) and the initial is_read = false, then publishes the wake-up. The Jambonz edge dispatch (dispatch_to_voicemail_box for department boxes, the softphone user path for personal boxes) and the IVR/queue fallback verbs all land here.
  • MWI snapshot service — answers GET /api/v1/voice/mwi with the per-mailbox counts and builds the RFC 3842 body the voice-gateway NOTIFY worker stamps onto the SIP lamp notification.
  • MWI SSE stream — GET /api/v1/voice/mwi/stream pushes the snapshot on each Redis wake-up, with a 3-second safety-net tick so a missed pub/sub message still self-heals. The browser softphone never receives a SIP NOTIFY — it renders the envelope badge off this stream instead.
The voice-gateway NOTIFY worker is the SIP half of the same picture: it resolves mailbox → address-of-record → registered contact and emits the NOTIFY with the body the snapshot service built. Until a desk phone subscribes, the web badge path above is the visible surface.

3. From capture to the recording library

Every captured voicemail lands in the recording library the same way every other recording does: the row carries a recording URL, a transcript when the speech pipeline completes, and the mailbox identity that makes it filterable. Two surfaces consume the same row:
  • the voicemail inbox view (the operator-facing list of unheard messages), and
  • the recording library (the QA/review surface, where the same recording joins search and evaluation).
The unread marker is is_read on the row. Marking a message read or unread updates that flag and immediately republishes the MWI counts, so the badge an operator sees and the badge a desk phone lamp would show never disagree. Retention (retention_days / delete_at) removes the row; the counts recompute on the next snapshot because counters are derived, not stored.

4. Who owns the notify, and the tenant-scoped mailbox

A mailbox is either a personal box (one assigned user, keyed by assigned_user_id) or a department box (a named box such as Support or Sales, keyed by box_id). The two are mutually exclusive per row — that is the mailbox model, and MWI is keyed on mailbox identity, not on a SIP credential. The gateway resolves the mailbox to a registered contact at NOTIFY time. Every component in the chain is tenant-scoped. Counts are computed against the caller’s own tenant schema, and the Redis pub/sub channel is namespaced by tenant id, so one tenant’s badge cannot leak another tenant’s counts. That is also what tenant-owned means here: operators of a tenant read, toggle, and clear their own voicemail; the platform never silently opens that surface beyond the tenant. See the compliance tenant-owned model for the general rule.

5. Supervisor badge versus extension owner

The badge is rendered from the same snapshot for everyone, but the mailbox identity decides whose view includes it:
  • An extension owner sees the envelope badge for their personal mailbox — it is their unread count.
  • A supervisor sees the department-box badge for every box they can reach, plus the recording-library row for the same message, because the box is a shared mailbox rather than a private one.
There is no separate supervisor-side count: one snapshot, filtered by the mailbox each viewer is entitled to see.

6. Boundaries — what MWI is not

  • Not a delivery-receipt generator. MWI is an inbound-record signal — the message already arrived. There is no carrier delivery receipt to synthesize, and treating it like a DLR would leak inbound-only data into outbound reporting.
  • Not a CDP event. Voicemail/MWI state does not publish into the customer-data event bus, so segmentation cannot subscribe to “voicemail received.” The event model the CDP consumes lives in the CDP event model; voicemail stays a voice surface.
  • The queue is the inbox-level pointer, not the mailbox. A queue’s overflow action can hand a waiting call to voicemail (voicemail overflow), but the mailbox identity the capture writes is what the MWI counts follow — the queue points into the inbox; it does not become a mailbox itself.
  • Outbound termination is untouched. Capture is inbound; nothing here is a termination decision.

7. Further reading