Skip to content

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 an intent is rejected with 422 intent_required. Nothing is sent or queued. A request WITH an intent returns 202 queued_for_review. Then monitor the review until you receive a sent or send_failed review event.
  • allow_direct: a bare request (no mode/intent/category_id) still sends immediately and returns the legacy 202 body, now plus review_id. Supplying an intent or mode: review queues 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.

project_id
required
string

Project containing the authorized inbox.

inbox_id
required
string

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.

One of:

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
thread_id
required
string
message_id
string
to

Replace derived To recipients. Omit to retain defaults; an explicit empty list is invalid.

Array<string>
>= 1 items <= 50 items
text
string
html
string
cc
Array<string>
bcc
Array<string>
reply_to
string
headers
object
key
additional properties
string
reply_all

Include all original recipients (reply-all) when true.

boolean
expected_last_message_id

Optional additional head assertion, validated for both thread and message selectors. Does not replace expected_context_version. Default replies target the newest message.

string
expected_context_version

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.

string
>= 1 characters
attachments
Array<object>
<= 20 items

An outbound attachment on send/reply/forward.

object
filename
string
content_type
string
content_base64
required

Standard base64-encoded attachment bytes. Up to 20 attachments share the 1,800,000-byte encoded email limit with headers and both bodies.

string
mode

Review Loop per-send assertion (see SendRequest.mode).

string
Allowed values: review direct
intent

The agent’s “for the human reviewer” summary. summary is REQUIRED when the resolved mode is review (else the submit is 422).

object
summary

Free-text intent summary (who/what/why).

string
meta

Structured intent payload.

object
goal
string
recipient
string
prior_touches
integer
urgency
string
category_id

Opaque category id (cat_…) matched from the registry.

string
category_confidence

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.

number format: float
<= 1
composition_token

Opaque token from a fresh, unfiltered GET /v1/rules for this agent, project, and category.

string
idempotency_key

DEPRECATED body-level alias for the Idempotency-Key header (see SendRequest.idempotency_key). Send the header instead.

string

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
submission_id
string
sent_message_id
string
nullable
sent_copy_status

Whether the accepted message has a resolvable saved Sent copy. Archive failure never resends mail.

string
Allowed values: pending stored unavailable
transport

Per-recipient counts by transport state. Absent states have count zero.

object
queued
integer
waiting_for_parent
integer
transmitting
integer
accepted
integer
failed
integer
dependency_failed
integer
unknown
integer
kind
required
string
Allowed values: sent
message
required
object
submission_id
string
sent_message_id
string
nullable
sent_copy_status

Whether the accepted message has a resolvable saved Sent copy. Archive failure never resends mail.

string
Allowed values: pending stored unavailable
transport

Per-recipient counts by transport state. Absent states have count zero.

object
queued
integer
waiting_for_parent
integer
transmitting
integer
accepted
integer
failed
integer
dependency_failed
integer
unknown
integer
id
string
thread_id
string
review

The review row that governed this send (ADDITIVE).

object
id

Opaque review id (rr_…).

string
state

Terminal state (sent | auto_sent).

string

Accepted; either queued for review or accepted for delivery.

One of:

A Review Loop submit that was parked for human review.

object
kind
required
string
Allowed values: queued_for_review
review
required
object
id
required

Opaque review id (rr_…).

string
state
required

Current review state (e.g. needs_review).

string
review_path

Console path to this draft. Sign in and claim the workspace if prompted before reviewing.

string
effective_mode

The mode after the policy resolved the agent’s assertion.

string
Allowed values: review direct

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
reason

Structured support failure reason without changing the existing code.

string
next_action

Suggested recovery action within existing access.

string
projects
object
object
required
string
Allowed values: list
data
required
Array<object>
object
id
required
string
org_id
required
string
name
required
string
has_more
required
boolean
next_cursor
required
string
current_state
object
id
required
string
number
required
string
project_id
required
string
title
required
string
status
required
string
Allowed values: received working waiting_on_customer resolved
impact
required
string
Allowed values: blocked workaround_available recovered unknown
version
required
integer format: int64
created_ms
required
integer format: int64
updated_ms
required
integer format: int64
resolved_ms
integer format: int64
customer_confirmed_ms
integer format: int64
resolution_kind
string
resolution_summary
string
feedback_id
required
string
org_id
string
console_url
string
latest_update
object
id
required
string
sequence
required
integer format: int64
kind
required
string
actor_kind
required
string
Allowed values: customer staff
body
required
string
created_ms
required
integer format: int64
observed_runtime
object
source
string
package_version
string
profile
string
profile_version
string
build
string
transport
string
catalog_digest
string
instance_id
string
request_id
string
observed_ms
integer format: int64
truncated
required
boolean
unsent_text

Retained text after a concurrent case change.

string
type
required
string format: uri
title
required
string
status
required
integer
detail
string
code
required

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.

string
Allowed values: bad_request unauthorized forbidden_scope not_found conflict support_record_deleted idempotency_conflict breadth_required quota_exceeded rate_limited domain_not_allowed recipient_blocked recipient_suppressed not_configured domain_unavailable internal reply_context_required reply_context_changed reply_workflow_required reply_already_pending intent_required wrong_state terminal stale born_stale send_needs_reconciliation graduation_locked maturity_gate_unmet scope_taken unavailable
request_id
string
errors
Array<object>
object
field
string
code
string
detail
string

Missing or invalid credential.

The canonical error envelope. error is a stable machine code.

object
error
required

Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).

string
Example
forbidden
message

Human-readable detail (never leaks internals).

string
Example
missing required scope
reason

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

string
used

Consumed plus reserved units.

integer format: int64
delta

Requested additional units.

integer format: int64
limit
integer format: int64
remaining
integer format: int64
consumed
integer format: int64
reserved
integer format: int64
next_reset_at

Next UTC usage-period boundary; pending reservations survive this boundary.

string format: date-time
upgrade_required
boolean

Authenticated but lacking the required scope, or out of quota.

The canonical error envelope. error is a stable machine code.

object
error
required

Stable error code (e.g. unauthorized, forbidden, not_found, invalid, quota_exceeded, rate_limited).

string
Example
forbidden
message

Human-readable detail (never leaks internals).

string
Example
missing required scope
reason

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

string
used

Consumed plus reserved units.

integer format: int64
delta

Requested additional units.

integer format: int64
limit
integer format: int64
remaining
integer format: int64
consumed
integer format: int64
reserved
integer format: int64
next_reset_at

Next UTC usage-period boundary; pending reservations survive this boundary.

string format: date-time
upgrade_required
boolean

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
reason

Structured support failure reason without changing the existing code.

string
next_action

Suggested recovery action within existing access.

string
projects
object
object
required
string
Allowed values: list
data
required
Array<object>
object
id
required
string
org_id
required
string
name
required
string
has_more
required
boolean
next_cursor
required
string
current_state
object
id
required
string
number
required
string
project_id
required
string
title
required
string
status
required
string
Allowed values: received working waiting_on_customer resolved
impact
required
string
Allowed values: blocked workaround_available recovered unknown
version
required
integer format: int64
created_ms
required
integer format: int64
updated_ms
required
integer format: int64
resolved_ms
integer format: int64
customer_confirmed_ms
integer format: int64
resolution_kind
string
resolution_summary
string
feedback_id
required
string
org_id
string
console_url
string
latest_update
object
id
required
string
sequence
required
integer format: int64
kind
required
string
actor_kind
required
string
Allowed values: customer staff
body
required
string
created_ms
required
integer format: int64
observed_runtime
object
source
string
package_version
string
profile
string
profile_version
string
build
string
transport
string
catalog_digest
string
instance_id
string
request_id
string
observed_ms
integer format: int64
truncated
required
boolean
unsent_text

Retained text after a concurrent case change.

string
type
required
string format: uri
title
required
string
status
required
integer
detail
string
code
required

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.

string
Allowed values: bad_request unauthorized forbidden_scope not_found conflict support_record_deleted idempotency_conflict breadth_required quota_exceeded rate_limited domain_not_allowed recipient_blocked recipient_suppressed not_configured domain_unavailable internal reply_context_required reply_context_changed reply_workflow_required reply_already_pending intent_required wrong_state terminal stale born_stale send_needs_reconciliation graduation_locked maturity_gate_unmet scope_taken unavailable
request_id
string
errors
Array<object>
object
field
string
code
string
detail
string

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
reason

Structured support failure reason without changing the existing code.

string
next_action

Suggested recovery action within existing access.

string
projects
object
object
required
string
Allowed values: list
data
required
Array<object>
object
id
required
string
org_id
required
string
name
required
string
has_more
required
boolean
next_cursor
required
string
current_state
object
id
required
string
number
required
string
project_id
required
string
title
required
string
status
required
string
Allowed values: received working waiting_on_customer resolved
impact
required
string
Allowed values: blocked workaround_available recovered unknown
version
required
integer format: int64
created_ms
required
integer format: int64
updated_ms
required
integer format: int64
resolved_ms
integer format: int64
customer_confirmed_ms
integer format: int64
resolution_kind
string
resolution_summary
string
feedback_id
required
string
org_id
string
console_url
string
latest_update
object
id
required
string
sequence
required
integer format: int64
kind
required
string
actor_kind
required
string
Allowed values: customer staff
body
required
string
created_ms
required
integer format: int64
observed_runtime
object
source
string
package_version
string
profile
string
profile_version
string
build
string
transport
string
catalog_digest
string
instance_id
string
request_id
string
observed_ms
integer format: int64
truncated
required
boolean
unsent_text

Retained text after a concurrent case change.

string
type
required
string format: uri
title
required
string
status
required
integer
detail
string
code
required

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.

string
Allowed values: bad_request unauthorized forbidden_scope not_found conflict support_record_deleted idempotency_conflict breadth_required quota_exceeded rate_limited domain_not_allowed recipient_blocked recipient_suppressed not_configured domain_unavailable internal reply_context_required reply_context_changed reply_workflow_required reply_already_pending intent_required wrong_state terminal stale born_stale send_needs_reconciliation graduation_locked maturity_gate_unmet scope_taken unavailable
request_id
string
errors
Array<object>
object
field
string
code
string
detail
string

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
reason

Structured support failure reason without changing the existing code.

string
next_action

Suggested recovery action within existing access.

string
projects
object
object
required
string
Allowed values: list
data
required
Array<object>
object
id
required
string
org_id
required
string
name
required
string
has_more
required
boolean
next_cursor
required
string
current_state
object
id
required
string
number
required
string
project_id
required
string
title
required
string
status
required
string
Allowed values: received working waiting_on_customer resolved
impact
required
string
Allowed values: blocked workaround_available recovered unknown
version
required
integer format: int64
created_ms
required
integer format: int64
updated_ms
required
integer format: int64
resolved_ms
integer format: int64
customer_confirmed_ms
integer format: int64
resolution_kind
string
resolution_summary
string
feedback_id
required
string
org_id
string
console_url
string
latest_update
object
id
required
string
sequence
required
integer format: int64
kind
required
string
actor_kind
required
string
Allowed values: customer staff
body
required
string
created_ms
required
integer format: int64
observed_runtime
object
source
string
package_version
string
profile
string
profile_version
string
build
string
transport
string
catalog_digest
string
instance_id
string
request_id
string
observed_ms
integer format: int64
truncated
required
boolean
unsent_text

Retained text after a concurrent case change.

string
type
required
string format: uri
title
required
string
status
required
integer
detail
string
code
required

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.

string
Allowed values: bad_request unauthorized forbidden_scope not_found conflict support_record_deleted idempotency_conflict breadth_required quota_exceeded rate_limited domain_not_allowed recipient_blocked recipient_suppressed not_configured domain_unavailable internal reply_context_required reply_context_changed reply_workflow_required reply_already_pending intent_required wrong_state terminal stale born_stale send_needs_reconciliation graduation_locked maturity_gate_unmet scope_taken unavailable
request_id
string
errors
Array<object>
object
field
string
code
string
detail
string
Retry-After
integer

Seconds to wait before retrying.