Skip to main content

Push delivery versus inbox pull

Every inbound message lands twice: once as a durable envelope that is always safe, and once on whatever delivery surface reaches the receiver fastest. The surfaces are the Sync WebSocket (a claimed, pushed slot), the inbox (a durable pull queue), and webhooks (a pushed HTTP callback into your own system). The neighboring pages cover each surface mechanically — the Sync WebSocket claim and flush window page covers how a socket claims, flushes, and releases, the Sync real-time shared-state model page covers the object model behind it, the push channel model covers fan-out to end-user devices, the in-app channel model covers the pull feed, and inbox push-routing covers auto-assignment of the operator backlog. This page is the receiver-side decision layer on top of all of them: when holding a claim is cheap and correct, when it is waste, and exactly what happens when the claim or the flush goes wrong. Devotel Orbit ships every inbound envelope to the durable inbox first — push surfaces are accelerants on top of that guarantee, never replacements for it. Choosing a surface is therefore a choice about who moves to the envelope now, not about where the envelope lives.

Section 1 — The components in one diagram

One inbound message travels through a single fan-out, and each surface below is a separate page in this tree. The durable lane is the message envelope model plus the inbox; the push lanes are the Sync WebSocket and your webhooks; the presence lane decides which push lane is worth holding open. The names on the diagram are the pages this page deliberately does not re-cover:
  • Envelope producer and delivery worker — the write path that commits an inbound message and publishes its change event in the same moment; the message envelope model owns that contract.
  • Inbox — the durable pull queue every envelope lands in regardless. An operator pulls it open with GET on the conversations surface; nothing a push lane does can lose it.
  • Presence — the online/offline signal that decides whether pushing is worth the slot at all. Presence that says “nobody is watching” is the whole argument for leaving work in the inbox; inbox push-routing consumes the same signal on 30-second sweeps to auto-assign pull-queued conversations.
  • Sync WS endpoint, claim lease, flush window — one row of the diagram, three concepts, both owned by the Sync WebSocket claim and flush window page. This page uses their outcomes (hold, flush, release, fallback) without restating the endpoint contract.
  • Webhooks — the push-to-your-server lane, with its own delivery semantics, retries, and ordering on the webhook delivery semantics page.
This page sits between those pages and the reader choosing a surface. Read it first when the question is “push or pull?”; read the neighbors when the question is “how exactly does the lane I picked behave?”

Section 2 — Why presence beats polling

A claim is the lease a client holds to receive pushed events instead of polling for them. On the Sync WebSocket the claim is literal: opening the upgrade on /api/v1/ws/sync binds a socket slot to your tenant, and holding it open is what turns “an envelope arrived” into “your client hears about it the instant the write commits.” Presence earns its cost because the alternative — a receiver with no claim — has to poll to learn what it already could have been told. Poll loops trade latency for CPU on both sides, and they scale badly: every client polls at its own cadence, so a thousand receivers generate a thousand overlapping clocks for the same answer. The claim is bounded, which is the price of the guarantee. A tenant holds at most 50 concurrent Sync sockets, and one socket holds at most 200 subscriptions — the 51st claim returns too_many_connections. That waterline is generous for presence-driven clients (agents signed into one workspace each) and hostile to wasteful ones (a client opening a socket per object). Long-lived claims reused across many subscriptions are the intended shape; claim churn is what the cap exists to refuse. The flush is the moment the claimed lane actually hands the envelope over. Two distinct flushes exist, and they belong to the two faces of a push client:
  • The delivery flush — the delivery worker publishes the change event the instant the inbound write commits. With an active claim, that flush is immediate: the socket relays the frame, and the receiver’s next conscious act is a rehydrating GET for the current state. With no claim, the same envelope still flushes — into the inbox, where it waits for a pull with no freshness loss at all.
  • The close flush window — a polite client ending a claim sends its unsubscribe frames, waits for the unsubscribed acknowledgements, and then closes. That ordered drain, bounded by the heartbeat window, is what releases the lease cleanly so the slot is reusable on the next claim. Closing without the flush releases the slot too — just later, on the idle timer.
Presence beats polling because the durable inbox absorbs every failure mode of the push lane. Push when a receiver is present and the claim is cheap; when presence says nobody is there, the inbox is not a fallback so much as the correct primary surface — and checking it on a pull cadence is the only cost you actually have.

Section 3 — The queue waterline

Claims and slots fight each other across surfaces, and the queue waterline is the line below which a contested slot goes to the receiver who needs it live. Two waterlines exist, and conflating them is the classic mistake:
  • The Sync waterline (client-side) — 50 sockets per tenant, 200 subscriptions per socket. Below it, claims are essentially free and presence decides everything. Above it, a client that refuses to reuse sockets starts handing too_many_connections back to receivers who genuinely needed one. The full lifecycle of claiming and releasing this lane is on the claim and flush window page.
  • The capacity waterline (operator-side) — an agent who answers voice calls and digital conversations on the same shift has a blended slot ceiling (default 3 concurrent slots, tenant-tunable). Voice dispatch and digital inbox routing both reserve against that ceiling before assigning, so a push toward an operator respects headroom the operator genuinely has. The omnichannel capacity reservation model owns this second waterline.
The two meet at the assignment edge. When an inbound conversation routes toward an operator whose blended slots are full — a lobbyed call plus two open chats, one reservation already held — the reservation is refused and the work falls back to the durable pull surface: the next eligible operator, an overflow queue, or the unassigned inbox. A refused reservation never loses the envelope; it changes who pulls it. That is the max_per_agent ceiling on the inbox push-routing sweeps and the reservation gate above sharing one vocabulary: push is attempted against a waterline, and anything the waterline refuses stays safe in the pull queue until capacity opens. Design receivers around the waterline you actually have. An agent UI that reuses one claim for its 200 subscriptions stays under it indefinitely; a client that opens a socket per inbox view crosses it on a busy Monday.

Section 4 — Choosing push versus inbox versus webhook

Choose the surface per receiver, not per message. The same envelope can push to an agent over the Sync socket, wait in the inbox for a supervisor, and fire a webhook into your analytics pipeline simultaneously. Two readings of the table cover almost every real system: an agent-facing product pushes to whoever presence marks alive and pulls for everyone else, while a system-facing product subscribes a webhook and treats the inbox as its durability backstop.

Section 5 — Failure semantics

The four failure clauses are what make the choice in Section 4 safe. Each is tenant-visible behavior, and each falls back toward the durable inbox rather than toward silent loss:
  • No flush → the inbox fallback still navigates. If a client never receives a flush — socket down, subscription never opened, frame lost between commit and relay — the envelope is not lost; it converges the moment the client issues its catch-up GET on the object, or the operator opens the inbox. Durable object kinds (Documents, Maps, Lists) carry a revision the client compares against its base, so a missed frame can never apply twice or apply stale. The sole exception is the ephemeral Stream, which stores nothing the pull path can recover — choosing a durable kind over a stream is the receiver-side form of the same fallback.
  • Claim timeout → the message is reassignable. A claim that stops answering heartbeats releases on the idle timeout, and the slot returns to the tenant’s 50-socket waterline without an operator cleanup pass. Meanwhile the envelope the claim would have flushed is unaffected: for operator work, an assignment whose operator goes absent is pulled back into the assignable pool by the same presence signal the sweeps and reservation gate consume, and for client claims the envelope simply re-flushes to whichever claim arrives next. Nothing about the claim lease ever owns the envelope itself.
  • Register versus unregister. A receiver joins a push lane by registering a claim or a device and leaves it by unregistering — the deliberate, acknowledged release (unsubscribe then close on the socket; device-token delete on the device registry). Unregistering releases the slot immediately and removes the receiver from targeting; merely abandoning the client does the same thing one heartbeat later, at the TTL’s pace instead of the client’s. Register fresh on every session start in clients whose identity changes (shared devices, per-user installs), because a stale registration keeps receiving flushes meant for the previous identity until it unregisters or dies.
  • Fallback is navigable, not hidden. Every degradation on this page — no flush, timed-out claim, refused reservation — degrades to a surface a human or a rehydrating client can open and inspect. The inbox, the catch-up GET, and the webhook delivery log are all read surfaces; a receiver recovering from any of the three failures lands on one of them with the full envelope intact.
The invariant under all four: push failures move envelopes toward pull surfaces; they never move them toward deletion. A receiver that understands that one rule can choose any surface in Section 4 and stay safe.

Where this fits