Skip to main content

Reply approvals — the supervisor review gate for the inbox

Reply approvals turn an agent’s outgoing reply into a supervised decision instead of an immediate send. An agent whose approval requirement is on composes a reply as usual; Orbit holds the proposed text in a queue instead of dispatching it, and a supervisor — or any owner/admin — approves it or rejects it with a reason. Approving dispatches the exact queued body through the same channel path a direct reply would take; rejecting sends nothing and tells the agent why, so they revise and resubmit. The gate and the queue live under Inbox → Pending replies (supervisors) and Inbox → Settings → Reply approvals (who is gated). The API surface mirrors both endpoints and is documented here in full.

Who the gate is for

Use the gate where a reply must be reviewed before it leaves the workspace:
  • Junior-agent onboarding. A new or training agent answers with real customers from day one, and a supervisor clears every reply until the required competence to send directly is proven. Turn the gate off for that agent when the review stops adding value.
  • Regulated or high-stakes queues. Teams answering financial, legal, or complaint-handling conversations often run a four-eyes rule — two people touch every customer-facing message — as a customer control. Orbit supplies the control; whether it applies is the tenant’s call, default off.
The gate is scoped per agent. Agents left un-gated send directly and never see the queue; supervisors work one shared queue of pending replies from all gated agents.

When approvals are required (resolution rules)

The requirement resolves in two levels, checked every time a reply is submitted:
  1. Per-agent override — the requirement set for that specific user wins over everything below.
  2. Org-wide defaultrequire_reply_approval_default under the org’s inbox settings; the fallback for every agent with no override. Default is off.
The composer never has to branch — submit every reply through the queue endpoint and let the server resolve. A reply from a non-gated agent dispatches immediately; a reply from a gated agent returns gated: true and lands in the queue. The workspace owner is never gated. Routing the sole administrator’s own replies through an approval queue would strand them behind their own gate, so the server always dispatches owner’s replies directly — enabling an override for an owner is refused, and clearing a stale one is always allowed.

Submit a reply through the gate

POST /api/v1/inbox/replies/queue — any authenticated full-seat agent may call it.
Two shapes come back, both 202 ACCEPTED — the call is accepted either way:
  • Gated. {"gated": true, "approval_id": "repla_...", "status": "pending"} — the reply is queued and waits for a supervisor.
  • Not gated. {"gated": false, "dispatched": {...}} — the reply was dispatched immediately through the standard reply path, and carries the dispatched message payload.
Body fields mirror the direct-reply endpoint: conversation_id and body are required; channel, from, media_url, metadata are optional. Email replies additionally accept subject, cc, and bcc, and those ride with the queue row so an approved email reply ships with the operator-entered headers instead of a blank subject.

Work the pending queue

List, approve, and reject are restricted to owners, admins, and supervisors — the approval charter. List pending rows:
Returns pending rows oldest-first (the queue reads top-down), each with its conversation_id, requested_by_user_id, body, channel fields, and status. A workspace where the approvals table isn’t provisioned yet gets an empty list, not an error. Approve:
The queued body dispatches through the exact same channel path a direct reply uses, and the row is marked approved with the dispatched message id stamped on it. Reject:
Nothing is dispatched. The row is marked rejected with an optional reason (up to 1,000 characters) the agent can read as review feedback. The dashboard renders the same list under Inbox → Pending replies: each card shows the composing agent, conversation, channel, and proposed body, with approve and reject actions inline. Rejecting opens a reason dialog.

Manage who is gated

Read the effective setting. Any workspace member can read the resolution for themselves — the composer uses it to decide whether the send goes through the queue:
The agent_overrides map — the full per-agent override table — only ships to supervisor roles; other callers get back their own override and the org default. Set an agent’s requirement. Owners, admins, and supervisors may pin or unpin an agent:
The dashboard does the same work under Inbox → Settings → Reply approvals, where a row per team member carries the gate toggle. Setting true on the workspace owner returns 422 — the owner exemption; clearing a stale override (false) always succeeds.

Error contract

A 422 from a parse failure carries the field-level issue list, so a client surfaces “body is too long” rather than an opaque rejection. If dispatch fails when the supervisor approves — the provider is down, the conversation closed, a send-time validation trips — the approve request errors with the dispatch reason and the row stays pending, so the supervisor can retry after the cause is fixed. Nothing is half-approved. On reject, the reason is optional; an empty reject works, but the agent reviewing their rejected queue row only sees feedback when one is supplied.

Audit trail

Every gate decision writes an audit event against the org: the queued submission (inbox.reply.queued), the not-gated bypass (inbox.reply.queue.bypassed), the supervisor decision (inbox.reply.approved or inbox.reply.rejected), and every settings change (inbox.reply.settings.updated). Each event carries the acting user, the conversation or approval id it touched, and the extra detail (the reject reason, the dispatched message id, the new setting value). The queue row itself keeps the record too: requested_by_user_id and decided_by_user_id name both sides of the four-eyes pair, decided_at timestamps the decision, reject_reason holds the feedback, and dispatched_message_id points to the message that actually went out. That pair — submitter and decider — is the controlled record an audit or QA review reads.

See also