Skip to main content

Pending replies queue — the supervisor workflow from login to decision

Inbox → Pending replies is the single queue every gated reply funnels through. When the reply-approval gate is on for an agent, that agent’s composed reply is held here instead of sending, and a supervisor — anyone in the owner, admin, or supervisor seat — turns each held reply into a decision: approve it and it dispatches, or reject it and it goes back to the agent with feedback. This page walks the queue from login to decision; the gate mechanics and API surface live in Reply approvals, and the operator walkthrough of the same surface lives in Pending replies guide.

What the pending-replies queue is

The queue is the supervisor view of the reply-approval gate. An agent whose gate is on submits a reply as usual; Orbit holds the proposed body as a pending row and returns gated: true to the composer. Every gated reply across the workspace lands in this one shared list, so a supervisor never hunts per-agent queues — the whole backlog of sign-off decisions is one page. The queue is deliberately narrow in scope. It decides outbound text, nothing else: conversation assignment, SLA clocks, and routing live in the standard Inbox list. What the queue adds on top of that list is the gated sign-off — a second named person between the composed reply and the customer. Open it from the dashboard sidebar under Inbox → Pending replies. The page is restricted to owners, admins, and supervisors; an agent without one of those seats never reaches it, and the server enforces the same role set on every call.

Sort and filtering — oldest first, per agent, per channel

The queue reads oldest first. The server returns pending rows in submission order, so the row at the top of the page is always the reply that has been waiting longest — work the queue top-down and the backlog drains in arrival order.
Pending replies queue header with rows sorted oldest first, each row naming its requester, channel badge, proposed body, and age

The queue header above the pending list; rows are ordered oldest first, so the longest-waiting reply sits on top.

Per agent. The queue is shared across all gated agents, and each row names its requester by display name resolved from the team roster. When several agents are gated, scan the requester column and work a specific agent’s submissions as a group — clearing one junior agent’s queue end-to-end is how a coaching session concludes. To narrow the backlog to a single conversation instead, the API lists pending rows filtered by conversation_id (see the Reply approvals API). Per channel. Every row carries a channel badge — SMS, MMS, WhatsApp, Email, RCS, Viber, Instagram, Messenger, LINE, Telegram, Apple Messages, or fax — so the shaping decision (“is this phrasing right for SMS length”) is visible before you read the body. The badge reads off the channel the reply will dispatch on, which is the same channel an approval sends through. Long queues page honestly: the server returns up to 50 pending rows, the page mounts the first 10, and Load more grows the window with a “Showing N of M” counter.
Load more button below the pending list with the Showing 10 of 23 visible-row counter

The Load more control under the mounted rows, with the Showing-N-of-M counter above it.

The API caps the pending list at 50 rows per call, and the page mounts the first 10; Load more grows the mounted window without re-fetching, and the counter always names both numbers.

Per-row actions — approve and reject, and what each writes

Each row carries two actions, and both write a permanent decision record. Approve & send. One click, no dialog — the decision gate was reading the body. The exact queued body dispatches through the same channel path a direct reply would take: an SMS goes out as SMS, an email goes out with the subject and cc/bcc the agent entered, a WhatsApp reply posts to the thread. The row is marked approved, stamped with the deciding user, the decision timestamp, and the id of the dispatched message — the audit trail points from the decision to the message the customer actually received.
Pending reply row with the Approve and send button next to the Reject action

A pending row with its Approve & send action on the right, beside Reject.

Approve takes one click with no confirmation dialog; the exact queued body dispatches. If dispatch itself fails — the conversation closed, the provider is down, a send-time validation trips — the approval errors and the row stays pending. A failed approve never half-sends: fix the cause and retry, or reject the row instead. Reject. Opens a dialog with an optional reason, up to 1,000 characters. Nothing is dispatched; the row is marked rejected with the deciding user and timestamp, and the reason rides on the row as the feedback the agent reads when they review their rejected submission. Leave a reason — “Offer the discount code instead of a refund” is actionable; a blank reject is a dead end for the agent. A failed reject surfaces inline inside the dialog, not only in the toast, so your typed reason survives a retry.
Reject dialog with the optional reason text field and its up-to-1,000-character hint, plus Cancel and Reject actions

The reject dialog with its optional reason field.

The reason field accepts up to 1,000 characters; leave one whenever you can, because it is the feedback the agent reads.

Reading the queue row

Every row is a complete decision record, before and after the decision:
  • Submitter — the composing agent’s display name (requested_by_user_id on the wire), resolved against the team roster so you read a name, not an id.
  • Proposed body — the exact text you approve or reject, rendered verbatim. Read it, not just the metadata line.
  • Age — how long ago the reply was submitted, rendered relatively (just now, 12m ago, 3h ago) until thirty days, then as an absolute date.
  • Decider and decision timestamp — once decided, the row records who decided (decided_by_user_id) and when (decided_at). The pair of submitter and decider is the controlled four-eyes record a QA review or audit reads.
  • Dispatched message link — on an approved row, dispatched_message_id points at the message that went out, so an auditor follows the decision through to the actual send. The Open thread link on the card takes you to the full conversation, which is how you review the customer’s earlier messages before deciding.
Decisions stay recorded on the row: the queue shrinks to pending-only, but the audit events (inbox.reply.queued, inbox.reply.approved, inbox.reply.rejected) and the row’s own fields keep both sides of every decision retrievable.

Bulk policies — deciding in batch

The queue processes one decision per action — there is deliberately no “approve all” button where a signature is a decision on outbound text. Batch work is still the normal case:
  • Read the batch, then decide the batch. Open the page, read top-down oldest-first, group by requester when one agent dominates the backlog, and run the decisions as a sweep. Approve is a single click per row, so a clean queue clears in seconds.
  • Rejections cluster. When a coaching pattern shows up — say, an agent offering refunds against policy — reject the affected rows in one sitting with the same corrective reason, so the agent gets consistent feedback rather than a mixed bag.
  • A second reviewer never tramples you. Both actions reject a row already decided (422 INVALID_STATE), so two supervisors working the same queue concurrently fail safe: the second decision attempt errors instead of overwriting the first.
  • Size your sweep to the window. The server caps the list at 50 pending rows; if the queue routinely saturates that cap, the bottleneck is review throughput, not the queue — gate fewer agents or add reviewer seats, and use Load more to keep the page responsive while you work it down.

The four-eyes rule in this queue

The gate is the four-eyes control for outbound text: two named people touch every customer-facing reply from a gated agent — the composing agent submits, a supervisor decides. In this queue’s context the rule means one thing concretely: the submitting agent must not be the decider.
  • The privilege split is structural: any full-seat agent can submit, but only owner, admin, and supervisor seats can list, approve, or reject. An agent who holds only the composer role can’t reach the queue at all — the page guard blocks it and the server returns 403 on the endpoints — so a gated agent can’t approve their own request even through the API.
  • The row records both sides: requested_by_user_id and decided_by_user_id name the pair, and that pair is exactly what a QA review or audit checks for self-approval.
  • The workspace owner is never gated — routing the sole administrator’s replies through an approval queue would strand them behind their own gate, so owner’s replies always dispatch directly. Four-eyes applies to gated agents, never to the owner.
Whether to run the gate at all, and on which agents, is a tenant-owned control: the per-agent override under Inbox → Settings → Reply approvals wins over the org default, and the org default require_reply_approval_default is off until you turn it on. Many teams apply it during junior onboarding, then lift it per agent once sending directly is proven; complaint-handling and regulated queues often keep it on as a standing policy.

Where the queue intersects AI deflection

The reply-approval gate and the AI co-pilot are two separate review surfaces that meet in the composer: the co-pilot decides what gets drafted, the gate decides whose reply needs sign-off.
  • Drafts are proposals. When the co-pilot is enabled, it drafts a suggested reply for each inbound message, and the draft sits above the composer with Accept, Edit, and Discard. Nothing ships until a person acts on the draft — every draft carries require_human_review = true.
  • The interactive path ends in this queue. A gated agent who accepts or edits a draft and hits send submits through the gate — the draft’s body becomes a pending row here, and only a supervisor’s approval turns it into a resolution. Drafts become resolutions through this queue on the interactive path; autonomy is a workspace policy decision, and the gate keeps the human checkpoint when it’s on.
  • The two gates compose. An agent can be gated while the co-pilot’s auto-approve confidence threshold qualifies a draft for one-click acceptance — one-click acceptance still routes through the reply gate, so the supervisor queue stays the last checkpoint. Only send-shaped co-pilot suggestions are ever one-click eligible, and that eligibility applies to the composer’s own accept flow, not to the supervisor decision here, which is always a deliberate approval click.
If your workspace runs both, the operating picture is: the co-pilot raises draft quality, the gate keeps a signature on gated agents’ sends, and this queue is where those two reviews meet.
Pending reply row carrying an Accepted AI draft badge above the proposed body, with Approve and send and Reject actions

A pending row whose body began as an AI draft the agent accepted in the composer.

A row whose body began as a draft carries that origin on the card; reading it costs nothing, but the approval decision is still yours alone.

Limits and edge cases

The limits below are enforced by the API, so the page can never show you a state the server has not already rejected:
  • Row cap. The pending list returns at most 50 rows per call; the page mounts the first 10 and Load more grows the mounted window, with the counter naming both numbers.
  • Mount window. The first 10 rows land on page load, so the queue paints instantly even when the cap is saturated.
  • Reject reason length. An optional reason of up to 1,000 characters; longer input is rejected, and a blank reject ships no feedback to the agent.
  • One decision per row. Approve or reject on an already-decided row returns 422 INVALID_STATE; two supervisors working the same queue concurrently fail safe instead of overwriting each other.
  • Failed approve keeps the row pending. A dispatch failure (closed conversation, provider outage, send-time validation) errors the approval and leaves the row pending; nothing half-sends. Fix the cause and retry, or reject the row instead.
  • Owner exemption. The workspace owner is never gated, so an owner’s replies never enter this queue; four-eyes applies to gated agents only.

The wire shapes — worked responses

Every supervisor-facing queue call shares the standard API envelope: a data payload plus a meta block carrying request_id and timestamp. The worked samples below match the curl requests in Reply approvals — same key header, same endpoint paths.

Queue submission — the gated-response envelope

Submitting through the gate (see Submit a reply through the gate) returns 202 ACCEPTED in one of two shapes. A gated submission reports the held row by id, object, and creation state:
A not-gated submission reports the immediate dispatch instead:
Branch on gated, not on the HTTP status — both shapes arrive as 202 ACCEPTED.

The pending list shape

GET /api/v1/inbox/replies/pending returns data as the array of pending rows, oldest first. Each row is the full decision record — the same fields the dashboard card renders:
Pending rows are always status: "pending" with the decision fields (decided_by_user_id, decided_at, reject_reason, dispatched_message_id) null until a decision lands. Decided rows leave the list — the list only ever shows the undecided backlog — and the decision fields stay readable on the row afterwards through the audit trail. When the workspace has no approvals table provisioned yet, data is an empty array, not an error. Approve and reject each return a single updated row in the same shape — data.id, data.status (approved or rejected), and on approve data.dispatched carries the dispatched message payload the row points to via dispatched_message_id.

The error envelope

Every failure across the queue surface returns the standard error envelope — an error block with the machine-readable code, human message, and HTTP status, plus the same meta block:
The two shapes a queue client must handle concretely: a full-schema validation failure adds a details.issues array of { field, message } pairs under error, and the double-decision case above is how a second concurrent supervisor learns the row already moved. Match the error code, never the message text — the message is operator-facing English and the code is the stable contract. The full code table lives in Reply approvals — Error contract.

See also