> ## 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.

# Inbox conversation push-routing (auto-assignment)

> How the optional push-routing scheduler moves pending, unassigned inbox conversations out of the pull queue — the opt-in gate, the 30-second sweep, the skill-and-affinity match with a round-robin fallback, the atomic claim guarantees, and how it compares to the voice ACD queue.

# Inbox conversation push-routing (auto-assignment)

By default the Inbox is a **pull queue**: a conversation a routing rule did not directly assign sits unassigned until an operator opens it and clicks **Claim**. Push-routing is an optional per-organization scheduler that flips that queue to **push** — it watches the unassigned backlog and hands the oldest waiting conversations to the best available operator, so a routed-to-nobody conversation still reaches a human without anyone clicking anything.

This page explains the model: what the unassigned queue is, how to enable the scheduler, how it picks an operator, what its atomicity guarantees are, and how it compares to the voice ACD queue and to manual claiming. The step-by-step Inbox setup lives in the [Inbox setup guide](/guides/inbox-setup); the ownership model the scheduler writes to (`assigned_to`) is described on the [conversations concept page](/concepts/conversations).

Push-routing assigns only **inbound** conversations waiting in the Inbox. It never sends messages and never touches outbound routing — outbound messaging and voice exit only through the Devotel softswitch, and every control on this page is tenant-owned configuration.

## The default: an unassigned pull queue

A conversation enters the Inbox either pre-assigned (a routing rule matched an `assign_user`, `assign_team`, `assign_round_robin`, or `assign_skill_based` action) or **unassigned** — status `pending`, no owner. Unassigned work is visible to the whole team, and an operator takes it by clicking **Claim** on the conversation row. Until someone does, it waits.

Pull semantics work when a team triages deliberately — a supervisor dividing the morning backlog, or agents picking work that matches their shift. It breaks when nobody remembers to claim: first-response SLA clocks keep running against an ownerless conversation, and the breach surface blames no one. Push-routing exists to close that gap for teams that want the backlog drained automatically while keeping the pull model for everything else.

## The opt-in gate

Push-routing is disabled until the organization turns it on. The gate lives in the organization settings object at **`settings.conversation_push_routing`**:

```json theme={null}
{
  "conversation_push_routing": {
    "enabled": true,
    "max_per_tick": 25,
    "max_per_agent": 5,
    "skip_channels": ["sms"]
  }
}
```

| Field           | Default | Effect                                                                                                                                                                        |
| --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`       | `false` | Master switch. Anything other than `true` means the scheduler skips the organization entirely.                                                                                |
| `max_per_tick`  | `25`    | Most conversations assigned for your organization in one sweep. Hard-capped at 100 no matter what you set.                                                                    |
| `max_per_agent` | `5`     | Per-operator ceiling on open conversations (`open`, `pending`, `active`); an operator at the ceiling stops receiving pushes until a conversation closes. Accepted range 1–50. |
| `skip_channels` | `[]`    | Channels that stay on the pull model even with push-routing on — for example `"sms"` if the team prefers to claim SMS work by hand.                                           |

Only `enabled: true` is required; every other field falls back to its default. The scheduler re-reads opted-in organizations within minutes of a change, so turning it on takes effect on the next sweep without a deploy.

## The 30-second sweep

Every 30 seconds the scheduler runs one pass over the opted-in organizations. For each, it:

1. **Snapshots the routable operator slate** — organization members who are not marked offline, do-not-disturb, or away, with their current open-conversation count attached. A member whose presence is offline, DND, or away never receives a push.
2. **Claims the oldest waiting conversations** — pending and unassigned, oldest first, bounded by `max_per_tick`. The claim uses row-locking that skips rows another worker replica already holds, so horizontally scaled replicas never double-process the same conversation.
3. **Applies the `skip_channels` carve-out** and the capacity gate — conversations on a skipped channel stay unassigned, and once every routable operator is at the `max_per_agent` ceiling the pass stops (the remainder is revisited on the next sweep, capacity permitting).
4. **Assigns each conversation to the best operator** (next section), stamps the conversation `open`, publishes the same real-time conversation-update event the inbox consumes, and fires the normal notification — exactly what a manual assignment produces.

The pass is tenant-scoped end to end: candidates and assignments are read and written inside the organization's own tenant schema.

## How an operator is picked: skill match, then round-robin

For each claimed conversation the scheduler first looks for a **required-skills hint** — the tags on the contact the conversation is with. It then ranks the routable operators with the same skill-affinity matcher the routing-rule engine's `assign_skill_based` action uses:

* **Affinity** is the fraction of required skills the operator also carries on their own skill list. The operator with the highest affinity wins.
* **Ties** break toward the more specialized operator (smaller total skill list), then alphabetically, so the outcome is deterministic.
* **No required skills, or no overlap** — when the contact has no usable tags, or no routable operator carries any of them, the scheduler falls back to **round-robin**: the operator with the fewest open conversations wins, ties broken alphabetically. A conversation with no skill hint still gets assigned whenever at least one non-offline operator exists; it never waits for a match that cannot exist.

An operator with an empty skill list still participates in the round-robin fallback — an empty list means "no recorded specialty," not "ineligible." Skills on this page are the same per-operator tags the [skill catalog](/concepts/skill-catalog) and the routing rules' `assign_skill_based` action use, so one skill vocabulary drives the voice queue, the routing rules, and the push-routing scheduler.

## Atomicity guarantees

Two operators — or an operator and the scheduler — can target the same unassigned conversation at the same moment. The scheduler defers to whoever gets there first, with two guards:

* **Skip-locked candidate claim.** The scheduler locks the candidate rows it is about to consider in a way that lets concurrent replicas skip each other's rows. Two scheduler replicas never evaluate the same conversation, so no duplicate assignment work.
* **Race-safe assignment.** The assignment write succeeds **only if the conversation is still unassigned and still pending**. If an operator clicked **Claim** — or a bulk-assign ran — in the milliseconds between the scheduler's read and its write, the write matches zero rows and the scheduler skips the conversation. A manual claim always wins; push-routing never clobbers an operator's own pickup, and it never overwrites an existing owner.

The same "only-if-unassigned" predicate is what the manual Claim endpoint uses, so the scheduler and the UI race on identical, mutually exclusive terms.

## Worked example: two operators, one skilled

An organization enables push-routing with defaults (`max_per_tick: 25`, `max_per_agent: 5`). Two operators are signed in:

* **Amara** — skills `["billing", "english"]`, 1 open conversation
* **Theo** — skills `[]`, 0 open conversations

Three conversations arrive unassigned:

1. **A WhatsApp conversation** with a contact tagged `billing`. Amara's affinity is 1 (she carries `billing`); Theo's is 0. The scheduler assigns it to **Amara**; her open count rises to 2.
2. **An email conversation** whose contact has no tags. No skill hint, so round-robin applies: Theo has fewer open conversations and receives it. **Theo**, open count now 1.
3. **An SMS conversation**, but the organization set `skip_channels: ["sms"]`. It stays unassigned in the pull queue for the team to claim manually.

If Amara hits 5 open conversations mid-sweep, the capacity gate removes her from the slate and remaining pushes go to Theo — or wait, if Theo is also at the ceiling.

## Push-routing versus the alternatives

|                       | Manual claim (default)            | Push-routing scheduler                                     | Voice ACD queue                                                      |
| --------------------- | --------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------- |
| Who acts              | The operator clicks **Claim**     | The scheduler assigns every 30s                            | The queue dispatcher rings an agent in real time                     |
| When work waits       | Until someone claims              | At most one sweep interval when a routable operator exists | Until an agent is `available`                                        |
| Operator choice       | Whoever claims first              | Skill-affinity match, round-robin fallback                 | Per-queue routing strategy (skill-weighted, round-robin, and others) |
| Capacity gate         | None — an operator can over-claim | `max_per_agent` open-conversation ceiling                  | Presence gate plus blended omnichannel load                          |
| Per-channel carve-out | n/a (everything is pull)          | `skip_channels` keeps chosen channels on pull              | Per-queue configuration                                              |

The closest sibling is the [ACD queue model](/concepts/acd-queue-model): the voice queue pushes call legs to `available` agents in real time, while push-routing brings the same push behaviour to the inbox backlog on a sweep cadence — same skill vocabulary, same "assignment never clobbers" discipline, one fifth of the machinery (no ringing, no no-answer timeout, no overflow action). If a waiting conversation must land on the *right* agent immediately rather than the *best available* agent within one sweep, route it explicitly with an `assign_skill_based` or `assign_user` routing rule; push-routing is the backstop for everything the rules leave unassigned.

## Related reading

* [Inbox setup](/guides/inbox-setup) — channels, routing rules, macros, SLA, and AI drafts end to end.
* [Conversations concept](/concepts/conversations) — the thread model and the ownership this scheduler writes.
* [ACD queue model](/concepts/acd-queue-model) — the voice-side push machinery this page compares against.
* [Skill catalog](/concepts/skill-catalog) — the per-operator skills the match consumes.
* [Contact reasons](/guides/contact-reasons) — tagging contacts so the skill match has something to match.
