> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Push delivery versus inbox pull: choosing a delivery surface

> The receiver-side choice every inbound message forces — when to claim a live push slot on the Sync WebSocket and flush the envelope now, when to leave the envelope in the inbox for pull, and when to push to a webhook instead, with the claim lease, flush window, and queue-waterline semantics that govern each choice.

# 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](/concepts/sync-ws-claim-and-flush-window) page covers how a socket claims, flushes, and releases, the [Sync real-time shared-state model](/concepts/sync-realtime-state-model) page covers the object model behind it, the [push channel model](/concepts/push-channel-model) covers fan-out to end-user devices, the [in-app channel model](/concepts/in-app-channel-model) covers the pull feed, and [inbox push-routing](/concepts/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](/concepts/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.

```mermaid theme={null}
flowchart LR
    Producer["Envelope producer<br/>(inbound SMS, email,<br/>chat, voice note)"]
    Worker["Delivery worker<br/>fan-out per tenant"]
    Inbox["Inbox<br/>(durable pull queue)"]
    Presence["Presence<br/>(who is online?)"]
    SyncWS["/api/v1/ws/sync<br/>claim lease + flush window"]
    Webhook["Webhook endpoints<br/>(your HTTPS listeners)"]
    Recipient["Receiver<br/>(agent UI, your backend,<br/>end-user device)"]

    Producer --> Worker
    Worker --> Inbox
    Worker --> SyncWS
    Worker --> Webhook
    Presence --> Worker
    Inbox --> Recipient
    SyncWS --> Recipient
    Webhook --> Recipient
```

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](/concepts/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](/concepts/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](/concepts/sync-ws-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](/concepts/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](/concepts/sync-ws-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](/concepts/omnichannel-capacity-reservation) 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](/concepts/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.

| If the receiver…                                                      | Then…                                                    | Because…                                                                                                                                                                                             |
| --------------------------------------------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Is a signed-in operator working live on this inbox                    | **Push** — hold a Sync claim, rely on presence           | Live flush is instant; the claim is one reusable socket; the inbox absorbs every miss as a fallback that needs no code.                                                                              |
| Is a batch or scheduled reader (a nightly sync, a digest)             | **Pull** — read the inbox or `GET` on a schedule         | A claim held open for work that arrives twice a day wastes a slot under the 50/socket waterline; the pull path never expires the data.                                                               |
| Is another system you own (a CRM, a warehouse, a notification bridge) | **Webhook** — subscribe to the emitted events            | Your listener owns its own retry, ordering, and failover story; the contract is on the [webhook delivery semantics](/concepts/webhook-delivery-semantics) page, and no client claim exists to flush. |
| Is an end user on a device, reachable or not                          | **Push channel** — fan out to the device registry        | The [push channel model](/concepts/push-channel-model) covers APNs / FCM / HMS / Web Push targeting and per-device outcomes; claim/socket semantics do not apply.                                    |
| Is an end user already inside your app and session                    | **In-app pull** — the SDK fetches the feed               | The [in-app channel model](/concepts/in-app-channel-model) computes eligibility at fetch time; the pull is the product, not a degraded push.                                                         |
| Must never miss the envelope, regardless of timing                    | **Inbox always**, plus whatever push lane accelerates it | The inbox is the only surface that is both durable and unconditional; every push lane degrades to it on failure.                                                                                     |

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

* [Sync WebSocket claim, spin-down, and the flush window](/concepts/sync-ws-claim-and-flush-window) — the endpoint contract, heartbeat, idle timeout, and close window this page defers to.
* [Sync real-time shared-state model](/concepts/sync-realtime-state-model) — the object kinds, revision contract, and durable-versus-stream choice behind exactly-once convergence.
* [Message envelope model](/concepts/message-envelope-model) — the durable envelope every surface in this page reads from.
* [Inbox push-routing (auto-assignment)](/concepts/inbox-push-routing) — the optional scheduler that pushes the pull queue to operators on a sweep cadence.
* [Omnichannel capacity reservation model](/concepts/omnichannel-capacity-reservation) — the blended slot ceiling that shares Section 3's second waterline.
* [Push channel model](/concepts/push-channel-model) — the end-user-device fan-out registry, a separate push surface from the Sync claim.
* [In-app channel model](/concepts/in-app-channel-model) — the pull feed for end users, where pull is the product.
* [Webhook delivery semantics](/concepts/webhook-delivery-semantics) — the push-to-your-server lane, with retries and ordering.
