Changelog (agent-facing)
This page records changes that alter an agent-visible contract — a new field, a new response code, a changed default, or a behavior an integration branches on. It is not the full product changelog; it is the subset that can break or unblock a running agent. Newest first, dated.
Almost every change here is additive and optional: new fields are never in a
schema required array, and a caller that ignores them keeps working. Where a
change is behavior-altering rather than additive, the entry says so explicitly.
When two clients disagree, trust the API response over any cached client type.
Normative contracts referenced below live on exactly one page each — the message lifecycle and verdict vocabulary, the error and denial-envelope catalog, the send-gate decision tree, the attachment staging lifecycle, the CLI machine interface and exit codes, and the per-field trust taxonomy. Entries summarize the change in a sentence and link; they do not restate those tables.
2026-07-14 — One-account Sandbox simulator matrix
The exact first-party outbound scenarios (delivered@, bounced@, complained@,
and suppressed@ at simulator.replylayer.net, including valid +label forms) no
longer consume Sandbox recipient-domain concentration quota. One Sandbox account can
therefore run all four scenarios in the same 24-hour period without receiving
SANDBOX_RECIPIENT_DOMAIN_CAP_EXCEEDED on the fourth send.
This is a narrow no-network exemption. Simulator sends still consume paid usage and daily/cumulative Sandbox allowance, create normal send-attempt records, and pass through authentication, API rate limiting, scanning/review policy, and idempotency. Arbitrary local parts, subdomains, lookalikes, Mailgun's simulator, and real recipients retain the ordinary destination-concentration controls. See the email simulator guide and send-gate reference.
2026-07-09 — First-party email simulator API
The production API now recognizes exact simulator.replylayer.net recipients for
delayed delivery, bounce, complaint, and suppression-shaped webhook outcomes without
contacting a real address. POST /v1/simulator/inbound injects clean or
prompt_injection_quarantined mail through normal ingestion and scanning, returning
available, quarantined, or pending. POST /v1/webhooks/:id/test also accepts an
optional event selector for real-shaped message.delivered, message.bounced, and
recipient_blocklist.added payloads. The authoritative scenario and caveat contract
is the email simulator guide.
Client source at CLI 0.7.12 and TypeScript/Python SDK 0.25.0 includes
rly simulate inbound and the rl.simulator resource. Those source versions are
release-ready but are not registry-published as of 2026-07-14. The REST endpoint is
usable directly, and outbound scenarios work through older clients' normal send
methods.
Upgrade note: additive API surface. Simulator sends still consume normal paid usage and send allowance; synthetic bounce/complaint outcomes do not affect reputation or write a real do-not-contact row.
2026-07-01 — Trusted instruction sources: read-time trust basis
A qualifying inbound read can now carry an agent_safety_context.instruction_trust
basis — { version, match: 'address', verified_domain, verdict, provenance } —
present only when the read actually relaxes, absent (not null) otherwise. When it is
present, agent_safety_context.guidance is replaced with a single
trusted-instruction line for that one sender; untrusted_content stays true. A
configured agent-role key receives the basis automatically on a qualifying read;
there is no client-sent opt-in header, flag, or SDK parameter, and it never
appears on a session read.
You cannot enable this from an agent credential. Designating a trusted source, enabling the mailbox mode, or turning on a key's capability are all loosening operations that require a human dashboard session with fresh re-authentication. Branch on the presence of the basis; never assume you can grant it. Full consumption and fail-closed matrix: /agents/trusted-instructions and /agents/security-model.
Upgrade note: additive read field; no change for keys the operator has not configured. Read-mirror types shipped in the TS + Python SDKs and MCP read tools.
2026-06-26 — Agent-send containment: RECIPIENT_AGENT_CONTAINED
When agent-send containment is enforced for your mailbox, an agent-origin send to
a recipient outside the mailbox's approved set (its recipient allowlist plus visible
thread participants) is denied with 403 RECIPIENT_AGENT_CONTAINED — a code distinct
from RECIPIENT_SUPPRESSED and from the allowlist rejection, so you can tell "the
platform contained this agent" apart from "this recipient is blocked." The mailbox
read surface exposes agent_send_policy ('restricted' | 'open') and
restricted_by ('mailbox_allowlist' | 'agent_containment' | null) so you can
preflight. The remedy is data — approve the recipient or continue an existing thread —
not a plan upgrade. Decision tree and preflight pattern:
/agents/send-gates.
Upgrade note: behavior applies only where containment is enforced for the mailbox; an uncontained mailbox is unaffected.
2026-06-24 — Idempotent immediate send and reply
POST /v1/messages/send and POST /v1/messages/:id/reply accept an
Idempotency-Key header. A retried request carrying the same key replays the
original outcome — the same message_id — instead of sending (or charging)
twice. A retry-safe probe, GET /v1/messages/idempotency, reports whether a key was
already used (200 replay / 404 NOT_FOUND / 400 when the key is blank). Typed
errors distinguish a key already bound to a draft from one bound to an immediate
send, and a not-proven-sent (indeterminate dispatch) outcome, so a caller can branch
deterministically rather than blind-retry. Codes: /agents/errors.
Upgrade note: fully backward-compatible — calls without a key behave exactly as
before. Exposed as idempotencyKey / idempotency_key in the SDKs and CLI.
2026-06-24 — Formal exit-code contract + strict send/reply codes
The CLI has a stable process exit-code contract (0 success · 1
remote/runtime/gate-reject · 2 local usage · 3 auth, opt-in). Notable asymmetry:
a send/reply whose message was created but scanner-blocked/quarantined exits
0 — the request produced a message, and you branch on the JSON status. Passing
--strict (which forwards Prefer: outcome=strict) instead maps a non-delivered
governed effect to a distinct non-zero exit: 4 blocked (terminal), 5
held-infrastructure (retry later), 6 unrecognized effect (fail closed, upgrade the
CLI). The full table is single-sourced at /agents/cli.
Upgrade note: scripts that branched on the old ad-hoc draft send codes must key on
the JSON code instead; the strict codes are emitted only under --strict.
2026-06-20 — Machine-readable denial envelope
Capability denials (403/429 on gated features) now carry a structured envelope in
details instead of only human prose: reason_axis (a closed enum answering why —
e.g. tier, key role, mailbox config, rate capacity, recipient containment,
accountability floor), a typed remedy, an optional cheapest_next_step, and an
upgrade_url that attaches only on monetary axes (so a wrong-credential denial
never misdirects you to a billing page). Switch on reason_axis + remedy; do not
string-parse the message. Axis and remedy catalog: /agents/errors.
Upgrade note: additive within details; existing top-level error/code are
unchanged.
2026-06-16 — agent_instructions[] on scan findings
Each scan.findings[] row can carry an optional agent_instructions: string[] —
stable, machine-readable handling guidance derived structurally from the finding's
typed fields (never from parsed prose). It is vendor-free and index-agnostic, and it
short-circuits to empty for allow decisions and for infrastructure (inference_error)
findings. Use it as the canonical "what should I do about this finding" signal.
Rendered by the CLI as → lines. Finding semantics: /agents/errors
and /agents/messages.
Upgrade note: additive/optional; findings scanned before this shipped simply lack the field.
2026-06-01 — Outbound attachment staging (stage-then-send)
Outbound attachments use a stage-then-consume flow: upload bytes to
POST /v1/attachments for a handle, poll the handle until its content scan is
terminal, then reference it by attachment_ids on a send/reply/draft. Handles are
consume-once. Key failure modes are typed: referencing a still-scanning handle
returns 409 ATTACHMENT_SCAN_PENDING; re-referencing a spent handle returns
409 ATTACHMENT_ALREADY_CONSUMED; a content block on a staged attachment folds into
the message verdict (the send returns a blocked message, not a 4xx). Full lifecycle,
enablement gates, and image-risk acknowledgement: /agents/attachments.
Upgrade note: additive surface; sends without attachment_ids are unchanged.
Attachment-bearing drafts are synchronous-send only.
2026-05-31 — Inbox search operators
GET message-list surfaces accept Gmail-style search operators — from:,
subject:, after:, before:, is:starred, and has:attachment. Because
has:attachment needs a server capability, clients probe GET /v1/health for the
messages.has_attachment_filter capability and return a structured
SEARCH_OPERATOR_UNSUPPORTED error against a server that does not advertise it,
rather than silently returning unfiltered results. Available across REST, CLI
(inbox list --search), and MCP list_messages.
Upgrade note: additive query surface; an unset search behaves as before.
2026-05-30 — Quota preflight + enriched RATE_LIMITED
GET /v1/accounts/quota is an agent-accessible send-budget preflight reporting
your effective (trust-derived) daily cap — it works with mailbox-scoped agent keys.
Complementarily, a RATE_LIMITED 429 on any send path now carries
details: { daily_limit, sends_remaining, reset_at } so you can compute back-off
without a second call. Tier and sandbox cap definitions live at
/docs/limits.
Upgrade note: additive response fields; the 429 status and code are unchanged.
2026-05-24 — Async optimistic-ack for draft send
POST /v1/drafts/:id/send supports the Prefer: respond-async request header
(RFC 7240). When the server applies it, the call returns 202 with
Preference-Applied: respond-async and { status: "queued_for_dispatch", ... }; when
it does not, you get a normal 200 with the scanner verdict inline — branch on the
status code. After a 202, poll GET /v1/messages/:id until state is terminal
(available / delivered / bounced / pending_review / draft, where draft
means the send was rolled back with the verdict on the message), or subscribe to the
lifecycle webhook. POST /v1/messages/send and /reply remain synchronous and
ignore Prefer. State machine: /agents/messages.
Upgrade note: opt-in per request; omitting the header preserves synchronous behavior.
2026-05-06 — Scan verdict: quarantined split from blocked
scan.verdict distinguishes quarantined (releasable — a human or policy can let it
through) from blocked (terminal — a content rejection you never blindly retry).
scan.verdict is the scanner-decision channel and is not a mirror of delivery
state. The confidentiality subtype was renamed to the direction-neutral
secret_value. Verdict and state vocabulary is single-sourced at
/agents/messages.
Upgrade note: behavior-altering for verdict consumers — a caller that treated any
non-clean verdict as terminal should now branch quarantined (recoverable) vs
blocked (terminal). SDK/CLI/MCP verdict enums are open strings, so a new value never
fails a client call.
2026-05-05 — Server-persisted read markers; reads no longer auto-mark
Read state is now set explicitly: POST /v1/messages/:id/read (single) and
POST /v1/mailboxes/:id/threads/:thread_id/read (bulk). GET /v1/messages/:id no
longer silently stamps a message read — an agent can inspect a message without
mutating its unread state. Exposed as SDK methods, CLI subcommands, and the MCP
mark_message_read / mark_thread_read tools.
Upgrade note: behavior-altering if you relied on a GET to clear unread state —
call the explicit read endpoint instead.