Reply in-thread (recipients + threading derived)
POST /v1/projects/{project_id}/inboxes/{inbox_id}/reply
Reply in-thread. Recipients, subject and threading headers are derived from the parent at SUBMIT time, so a queued reply shows the human the real envelope and the pre-flight screens the real recipients.
The resolved account/inbox review policy is authoritative on every send. Omitting fields does not bypass it. Read effective_review_policy on GET /v1/inboxes/{inbox_id} to know which branch you are on:
require_review(the default for every account): a request WITHOUT anintentis rejected with422 intent_required. Nothing is sent or queued. A request WITH an intent returns202 queued_for_review. Then monitor the review until you receive asentorsend_failedreview event.allow_direct: a bare request (no mode/intent/category_id) still sends immediately and returns the legacy202body, now plusreview_id. Supplying an intent ormode: reviewqueues it for a human.auto_send_graduated: a categorized message that clears the graduation gates auto-sends; everything else is queued.
Contact lists, list-unsubscribe suppression and the billing quota are all enforced at SUBMIT time, so a rejected recipient fails fast rather than after a human has already approved the draft.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters ”Project containing the authorized inbox.
Opaque inbox id (the canonical path key). The inbox’s email address is also accepted here as a within-project alias. Treat the id as opaque.
Request Body required
Section titled “Request Body required ”Thread-aware reply. Exactly one of thread_id / message_id selects the parent; recipients default from that parent. Optional to replaces the derived To group, including reply-all. Subject and threading headers remain derived server-side. The resulting message must contain at most 50 combined To/Cc/Bcc recipients and 1,800,000 encoded bytes including headers, bodies and attachments.
object
Replace derived To recipients. Omit to retain defaults; an explicit empty list is invalid.
object
Include all original recipients (reply-all) when true.
Optional additional head assertion, validated for both thread and message selectors. Does not replace expected_context_version. Default replies target the newest message.
Context_version from the complete thread read used to compose this reply. Missing returns reply_context_required (422); changed returns reply_context_changed (409). Read the thread and reconsider the draft before retrying.
An outbound attachment on send/reply/forward.
object
Standard base64-encoded attachment bytes. Up to 20 attachments share the 1,800,000-byte encoded email limit with headers and both bodies.
Review Loop per-send assertion (see SendRequest.mode).
The agent’s “for the human reviewer” summary. summary is REQUIRED when the resolved mode is review (else the submit is 422).
object
Free-text intent summary (who/what/why).
Structured intent payload.
object
Opaque category id (cat_…) matched from the registry.
Agent-supplied confidence (0..1) in the category match (see SendRequest.category_confidence). Feeds the min_confidence auto-send gate only; the server never scores.
Opaque token from a fresh, unfiltered GET /v1/rules for this agent, project, and category.
DEPRECATED body-level alias for the Idempotency-Key header (see SendRequest.idempotency_key). Send the header instead.
Thread-aware reply. Exactly one of thread_id / message_id selects the parent; recipients default from that parent. Optional to replaces the derived To group, including reply-all. Subject and threading headers remain derived server-side. The resulting message must contain at most 50 combined To/Cc/Bcc recipients and 1,800,000 encoded bytes including headers, bodies and attachments.
object
Replace derived To recipients. Omit to retain defaults; an explicit empty list is invalid.
object
Include all original recipients (reply-all) when true.
Optional additional head assertion, validated for both thread and message selectors. Does not replace expected_context_version. Default replies target the newest message.
Context_version from the complete thread read used to compose this reply. Missing returns reply_context_required (422); changed returns reply_context_changed (409). Read the thread and reconsider the draft before retrying.
An outbound attachment on send/reply/forward.
object
Standard base64-encoded attachment bytes. Up to 20 attachments share the 1,800,000-byte encoded email limit with headers and both bodies.
Review Loop per-send assertion (see SendRequest.mode).
The agent’s “for the human reviewer” summary. summary is REQUIRED when the resolved mode is review (else the submit is 422).
object
Free-text intent summary (who/what/why).
Structured intent payload.
object
Opaque category id (cat_…) matched from the registry.
Agent-supplied confidence (0..1) in the category match (see SendRequest.category_confidence). Feeds the min_confidence auto-send gate only; the server never scores.
Opaque token from a fresh, unfiltered GET /v1/rules for this agent, project, and category.
DEPRECATED body-level alias for the Idempotency-Key header (see SendRequest.idempotency_key). Send the header instead.
Responses
Section titled “ Responses ”Sent immediately (policy-permitted direct or graduated path).
A Review Loop submit that was sent immediately (policy-permitted direct or graduated path). kind is always “sent”.
object
Whether the accepted message has a resolvable saved Sent copy. Archive failure never resends mail.
Per-recipient counts by transport state. Absent states have count zero.
object
object
Whether the accepted message has a resolvable saved Sent copy. Archive failure never resends mail.
Per-recipient counts by transport state. Absent states have count zero.
object
The review row that governed this send (ADDITIVE).
object
Opaque review id (rr_…).
Terminal state (sent | auto_sent).
Accepted; either queued for review or accepted for delivery.
A Review Loop submit that was parked for human review.
object
object
Opaque review id (rr_…).
Current review state (e.g. needs_review).
Console path to this draft. Sign in and claim the workspace if prompted before reviewing.
The mode after the policy resolved the agent’s assertion.
Durable send authorization accepted. Local queue custody is not provider acceptance. Follow status_url; never create a replacement while an attempt is unresolved.
object
The outcome of a reply or forward.
object
Whether the accepted message has a resolvable saved Sent copy. Archive failure never resends mail.
Per-recipient counts by transport state. Absent states have count zero.
object
Opaque review id (rr_…) for the review row that governed this send. ADDITIVE. Every agent-plane send now creates one, so an agent that crashes after issuing the request can still call GET /v1/reviews/{id} and read closed / sent_message_id instead of guessing whether the message went out.
The send/reply/forward is malformed or fails validation. Decode and validation failures are problem+json (code: bad_request) and NAME the offending field; an unknown key lists the accepted set, and supplying both text and the deprecated body with DIFFERENT content is errors[0].code = conflicting_alias (there is no safe guess, so the request is refused rather than relayed with the wrong bytes). A few pre-existing domain rejections reached through this path (e.g. domain_not_allowed) still carry the legacy {error, message} envelope.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
Structured support failure reason without changing the existing code.
Suggested recovery action within existing access.
object
object
object
object
object
Retained text after a concurrent case change.
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeMachine-readable quota reason. inbox_limit_exceeded means billing account inbox capacity across all organizations and projects sharing that account; enrollment_token_mailbox_budget_exhausted means the enrollment key lifetime creation allowance. Read message for recovery; inbox counts are separate from sending quotas.
Consumed plus reserved units.
Requested additional units.
Next UTC usage-period boundary; pending reservations survive this boundary.
Missing or invalid credential.
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeMachine-readable quota reason. inbox_limit_exceeded means billing account inbox capacity across all organizations and projects sharing that account; enrollment_token_mailbox_budget_exhausted means the enrollment key lifetime creation allowance. Read message for recovery; inbox counts are separate from sending quotas.
Consumed plus reserved units.
Requested additional units.
Next UTC usage-period boundary; pending reservations survive this boundary.
Authenticated but lacking the required scope, or out of quota.
The canonical error envelope. error is a stable machine code.
object
Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).
Example
forbiddenHuman-readable detail (never leaks internals).
Example
missing required scopeMachine-readable quota reason. inbox_limit_exceeded means billing account inbox capacity across all organizations and projects sharing that account; enrollment_token_mailbox_budget_exhausted means the enrollment key lifetime creation allowance. Read message for recovery; inbox counts are separate from sending quotas.
Consumed plus reserved units.
Requested additional units.
Next UTC usage-period boundary; pending reservations survive this boundary.
A Review Loop conflict, as problem+json. Branch on code, NOT on the 409 status; the four codes demand opposite behavior. stale: the revision/version you named is no longer current (a human moved the draft) and NOTHING was mutated; errors[] carries the current state, revision and version, so re-apply your change on top and resubmit with the new parent_revision (retry, bounded to ~3). born_stale: the redraft was built against an older rule high-water; re-read the rules and resubmit, or restamp_review if nothing genuinely changed (at most one retry per high-water). wrong_state: this VERB is illegal from the current state but the draft is still live; NEVER retry the same verb; errors[] repeats an allowed_action entry per verb that IS legal right now. terminal: the review is already sent/auto_sent/cancelled; nothing will ever succeed, stop retrying, and a front_run_next review event carries the outcome. send_needs_reconciliation: a prior attempt is unconfirmed and parked for recover-by-Message-ID; do NOT resend, poll the review.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
Structured support failure reason without changing the existing code.
Suggested recovery action within existing access.
object
object
object
object
object
Retained text after a concurrent case change.
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
The send/reply/forward is well-formed but cannot be processed. Always problem+json; branch on code. intent_required (D3): the resolved review mode is review and no intent summary was supplied; nothing was sent and nothing was queued, and detail names the field to add. recipient_suppressed: one or more recipients have a list-unsubscribe opt-out, and errors[] names ONLY the suppressed addresses; never the suppression scope or origin; so the agent can retry without them.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
Structured support failure reason without changing the existing code.
Suggested recovery action within existing access.
object
object
object
object
object
Retained text after a concurrent case change.
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
A dependency could not be read, so the request was failed CLOSED rather than served on a guess (Problem code = unavailable). Retryable; see Retry-After. Distinct from not_configured, which is permanent for this deployment.
RFC-9457 problem+json error body. code is a closed machine enum clients switch on; type is a dereferenceable URI under https://extrovert.dev/problems/. Served as application/problem+json.
object
Structured support failure reason without changing the existing code.
Suggested recovery action within existing access.
object
object
object
object
object
Retained text after a concurrent case change.
The CLOSED machine code. The Review Loop members split what used to be a single opaque conflict, because an agent must take a DIFFERENT action on each: stale (the revision/version you named is no longer current; nothing was mutated; re-read, re-apply, resubmit; retryable, bounded) and born_stale (built against an older rule high-water; re-read the rules or restamp_review; at most one retry per high-water) are the ONLY retryable 409s. wrong_state means this VERB is illegal from the current state while the draft is still live; never retry the same verb, read the state and the repeated allowed_action hints in errors[] and pick a legal one. terminal means the review is already sent/auto_sent/cancelled and nothing will EVER succeed; stop, and drain your review events for the outcome. send_needs_reconciliation means a prior send is unconfirmed and parked. Do not resend. Poll instead. unavailable (503) is the retryable fail-closed answer when a dependency could not be read; it carries Retry-After and is distinct from not_configured, which is permanent for this deployment.
object
Headers
Section titled “Headers ”Seconds to wait before retrying.