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-09-27 — Every MCP tool returns structured output
Behavior-altering for clients that validate tool results. replylayer-mcp 0.14.0
adds an outputSchema to the 33 tools that returned only a text block, and they now
emit structuredContent (the same JSON as the text block, which is unchanged). Every
tool except remove_suppression, which always returns an error, now has one; the
hosted endpoints carry it from the api deploy that ships 0.14.0. The existing schemas
gain the fields they were missing, including hold_context.review_url and
review_expires_at. get_attachment_preview now requires attachment and preview.
A client that validates structuredContent against the schema (the MCP TypeScript
client does, error results included) rejects a result that does not match, so a server
and client on mismatched versions surface a validation error instead of a result.
Schemas accept fields the API adds later and describe enum-like values as strings. See
Structured output.
2026-09-26 — Tier denials name pay-as-you-go where it unlocks the feature
Behavior-altering for one field value. A reason_axis: "tier" denial whose feature
is included with pay-as-you-go credit (today, attachment previews) now carries
cheapest_next_step: "paygo" instead of "starter", and its error names both
options. required_tier still names the minimum plan ("starter"). A Sandbox agent
previewing an attachment gets "Attachment previews are available on the Starter plan
or with pay-as-you-go credit." Pro-only features are unchanged. Count caps follow the
same rule: a Sandbox account at its one-webhook cap now gets required_tier: "starter",
cheapest_next_step: "paygo"; the mailbox-cap 403 FORBIDDEN now carries the denial
envelope in details. Pay-as-you-go accounts can now set pii_mode (they were refused
both modes before). See the error catalog.
2026-09-26 — MCP results leave out internal identifiers
Behavior-altering for MCP result fields. replylayer-mcp 0.13.0 removes internal
identifiers and bookkeeping from tool results on every transport, in both the text
block and structuredContent: reviewed_by_id, added_by_actor_id, actor_id,
warmup.velocity_gate_mode, preview.extractor, attachment hash and bookkeeping
fields, and the mailbox scanner, PII-detector and instruction-trust configuration.
delete_message returns r2_objects_failed_count instead of the list of storage keys.
A vendor-named suppression source reads provider_webhook or provider_sync. A
400 VALIDATION_ERROR keeps only instancePath and message per validation item, and
500 SELF_HOSTED_DECRYPT_FAILED no longer carries details.domain_id.
list_drafts returns has_more again. The REST API, SDKs and CLI are unchanged. See
Fields MCP results leave out.
2026-09-26 — Every MCP tool has a title and all hints
Behavior-altering for hosts that honour tool hints. replylayer-mcp 0.13.0 gives
every tool a title and explicit boolean readOnlyHint, destructiveHint and
openWorldHint, plus idempotentHint on every tool that changes something.
create_draft, update_draft, add_recipient, both release tools, and the ten list
and marker writes (add_suppression*, add_inbound_blocklist*,
add_inbound_allowlist*, mark_*_read, star_*) are now destructive, so a host that
confirms destructive tools will ask before running them. remove_suppression is now
read-only. No parameter or output field changes, only descriptions. The hosted
server carries the metadata from its next deploy. See the MCP guide.
2026-09-24 — Redaction-placeholder idempotency keys are refused
Behavior-altering for one input. An Idempotency-Key that is a redaction
placeholder — only *, • or · characters, any value containing …,
__OPENCLAW_REDACTED__, or a bare or bracketed word such as [REDACTED] — is now
refused with 400 IDEMPOTENCY_KEY_INVALID before any replay lookup or write, on send,
reply, draft create and the GET /v1/messages/idempotency probe. Before, such a key
was stored like any other, so a second request with the same mask replayed the first
message instead of sending. Hosts that mask fields ending in _key (OpenClaw does)
must retry from a new session with the literal key. replylayer-mcp 0.12.3 carries the
same guidance in its tool descriptions. See the error catalog.
2026-09-24 — Text and CSV attachments send; sent attachments download
Behavior-altering for three outbound attachment cases. A staged .txt or .csv
upload used to pass POST /v1/attachments and then fail the send with 400 OUTBOUND_ATTACHMENT_TYPE_NOT_ALLOWED; it now sends, as text/plain or text/csv.
Text is admitted only under the extensions .txt, .text, .log, .md, and
.json (CSV under .csv), so a text file with any other extension (such as
.html or .svg) or with no extension is now refused at upload with the same
OUTBOUND_ATTACHMENT_TYPE_NOT_ALLOWED code. On sent messages and drafts,
attachments[].policy_action is now always deliver; it used to carry the inbound
disposition (quarantine for PDF and Office files, metadata_only for images and
text), which made GET /v1/messages/:id/attachments/:idx refuse the sender's own PDF
with 403 ATTACHMENT_QUARANTINED. Attachments sent before this change keep their old
value. Details on the attachment contract for agents.
Upgrade note: no client upgrade needed. An agent that switched text to PDF to work around the send error can send text directly.
2026-09-23 — API-key failures scoped per key; API_KEY_REVOKED
Behavior-altering for clients that retried every 429. A valid API key is never
refused by the failed-authentication block any more: the key is read and classified
first. A malformed token (for example Bearer Bearer rly_live_…) is always 401 UNAUTHORIZED and never counted, so it no longer turns into a 429. A wrong secret
for a real key counts against that key; an unknown key counts against the caller's
IP. New response code 401 API_KEY_REVOKED for the exact token of a revoked key
(never counted by the failed-authentication block, so repeating it never becomes that
429; it still counts toward the 600-a-minute per-address admission ceiling). The failed-authentication 429 keeps
code: "RATE_LIMITED" and gains details: { reason: "failed_authentication", retry_after } plus a Retry-After header — it means the key is wrong or revoked,
so stop and fix it rather than retrying. @replylayer/sdk and replylayer 0.27.1
throw it immediately instead of retrying, and replylayer-mcp 0.12.2 tells the agent
to stop and ask a human. Details on the error catalog
and auth reference.
2026-09-12 — Per-webhook custom request headers
POST /v1/webhooks and PATCH /v1/webhooks/:id accept request_headers, a map of
at most 8 static headers added to every delivery attempt for that webhook, for a
receiver that authenticates by a fixed header rather than by the ReplyLayer
signature. Values are write-only: every webhook response carries
request_header_names (lower-cased, sorted, [] when none) and no read path
returns a value. On PATCH the field behaves like description — an object replaces
the whole map, null clears it, absent leaves it untouched. Rejections are
WEBHOOK_HEADER_RESERVED, WEBHOOK_HEADER_DUPLICATE, WEBHOOK_HEADER_INVALID, and
VALIDATION_ERROR; limits and the reserved-name list are on the
webhook consumption contract.
Upgrade note: additive. One behavior change for webhooks that use the feature —
their deliveries store no response body, so response_preview is always null and
last_error is restricted to a fixed code set. Typed support lands in
@replylayer/sdk and replylayer 0.27.0; the REST field needs no client upgrade.
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.
rly simulate inbound and the rl.simulator resource are now registry-published:
rly 0.7.12+ (npm + PyPI; current stable 0.7.13), @replylayer/sdk 0.26.0
(npm), and replylayer 0.26.0 (PyPI). The REST endpoint remains usable directly,
and outbound scenarios work through any client's 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.