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.