Notable changes, fixes, and new features on the AgenticBoxes platform — newest first.
byo_delegated (we run the Route 53 zone, $1/mo) previously required an apex
domain. Now both BYO modes accept a third-level domain. Delegating mail.example.com
means your apex DNS stays exactly where it is — you add NS records for the subdomain in
your apex DNS, and only the subdomain delegates to us.
domain.delegation_required event is shape-aware: subdomains carry delegation_point: "parent-zone-dns" + parent_domain with apex-DNS instructions; apexes keep the replace-nameservers-at-your-registrar flow.domain_intent.byo_domain) and on a live account (POST /account/domain).
Accounts with audit_bcc_self enabled archive every send's SES-signed copy.
Those raw copies now follow a retention window (default 14 days,
usermetadata.audit_retention_days, 1–90): at expiry the DKIM signatures +
published keys are extracted and attested onto the message record, then the raw body is
reclaimed. Audit bundles stay issuable forever — what expires with retention is
third-party re-verification of the body hash against raw bytes. Keep a longer window if
you need that.
Tier 2 of the payment trust ladder. Your owner saves a card once on a
Stripe-hosted page. The mandate starts at the smallest caps ($5 per
top-up / $10 per 30 days); the confirmation email to the owner carries one-click links
to raise them ($10/$25 or $25/$50). After that, POST /account/credit/topup
settles inside the caps automatically.
account.payment_mandate.created / .charged / .revoked.mandate_not_used.reason — the agent knows exactly why and what to do next.POST /account/credit/mandate/setup → hand the setup_url to your human.
POST /account/sending-ip now allocates a real SES managed
dedicated IP pool (previously simulated). Your sends route through your
own pool automatically; no DNS changes needed in any domain mode. Billing was
redesigned around how SES actually delivers IPs:
account.sending_ip.ready event carrying the real IP address. Provisioning time is free; a row that never leaves provisioning is never billed.volume_floor_warning when your current volume is unlikely to qualify — below the floor, the shared pool genuinely delivers better.how_to_know_when_ready — poll the GET, watch get_events/webhooks, or subscribe to IoT push for the account.sending_ip.ready event. Works identically for BYO-domain accounts.DELETE /account/sending-ip credits back unused full days of the current 30-day cycle at $25/30 per day — mirroring how AWS pro-rates the underlying cost. The response carries refund_cents + refund_note.Agents in egress-restricted environments (MCP-only sandboxes) previously had no way to fund their account — both top-up shapes required Stripe client code or a wallet CLI. Now:
POST /account/credit/topup {"amount_cents", "payable_link": true} returns a pay_url — a Stripe-hosted invoice page any human can open in any browser (card or Link). Add "notify_payer": true and the invoice is emailed to the account owner automatically.mcp.agenticboxes.email) now exposes 61 tools — the full API surface, generated from the OpenAPI spec (sandbox-incompatible endpoints excluded with pointers to alternatives). Headliners: request_topup and get_events — the full earn/spend/replenish loop now works without leaving the sandbox. get_events is the cursor-based feed (mail.received, credits, shipped feature requests…) and the sandbox substitute for IoT/webhook push.
Read and send your agentic mail from Apple Mail, Outlook, Thunderbird, or any
standard mail client. Mint an app-password (POST /account/app-passwords),
then: incoming imap.boxes.email:993 (SSL/TLS), outgoing
imap.boxes.email:465 (SSL/TLS), username = your account email or any
box address you own.
POST /messages/send — billed identically, same suppression/reputation rules, idempotent on the client Message-ID.GET /account/sending-ip now returns trigger_state — whether your traffic currently trips a dedicated-IP trigger (>5k/day avg, >3% bounce, >0.05% complaint; rolling 7 days).DELETE /account/sending-ip refuses with 409 while any trigger is active — traffic that requires a dedicated IP can't move back onto the shared pool.Sent-direction audit bundles can now carry the real SES DKIM signature as forensic evidence. Previously this required asking the operator; now it's self-serve — with a deliberate speed bump.
GET /account/audit/sent-dkim — current state + the per-message cost computed at your account's rate.POST /account/audit/sent-dkim/enable (admin scope) — refuses with 409 consent-required until you acknowledge the cost with {"i_understand_every_send_costs_3x": true}. Enabling roughly triples your per-message cost (the send + the archive BCC + the archive copy ingested back). Get your User's understanding and consent first.POST /account/audit/sent-dkim/disable (admin scope) — no gate, effective immediately.account.audit.sent_dkim.enabled / .disabled on your feed.The bulk-send endpoint that shipped synchronous in phase 2a now runs async on SQS+Lambda. The shape and the promises in the SKILL are now actually backed by working code.
Response shape change (breaking-ish):
202 with {job_id, status:"queued", ...} instead of 200 with the final state. Poll GET /messages/send/bulk/{id} for status, or watch your event feed.batch_size raised from 10 → 1,000 (was small to fit the sync 30s ALB window).What now actually fires (didn't in 2a):
cool_off_seconds (default 600) — real delay between batches, via SQS DelaySeconds. Hard bounces from the previous batch land before the next one starts.halt_thresholds — bounce/complaint counters refresh from the reputation pipeline between batches. A batch over threshold sets status:"paused", emits bulk.paused, stops enqueueing.Lag honesty (in the skill): hard bounces land <60s after SES accepts, soft bounces over hours, complaints over hours-to-days. The 10-min cool_off catches hard bounces cleanly; the complaint gate fires on a lagged batch — useful (stops at batch 12 instead of batch 50) but not real-time.
Infra (new): agentic-bulk-send-batch SQS queue + DLQ + agentic-bulk-send-worker Lambda + per-account IAM. Tagged per the platform's AWS tagging discipline.
Idempotency: SQS redrive can deliver a message twice; /_internal/bulk-send/process-batch no-ops on already-processed batches (checks batches_sent >= batch_number).
Foundation work for the IMAP+SMTP bridge ([[sc-120]]). Adds a credential
type distinct from `bxs_live_` API keys: app-passwords,
one per mail-client install, revocable independently. Same pattern
Gmail and Fastmail use. Formatted four groups of four base32 chars
(abcd-efgh-ijkl-mnop) so users recognize them on sight.
New endpoints (admin scope):
POST /account/app-passwords — mint; plaintext returned once, never readable againGET /account/app-passwords — list metadata; never returns plaintext or hashDELETE /account/app-passwords/{id} — revoke; mail clients using it fail on next connect
Storage: bcrypt hash (12 rounds) on users.usermetadata.app_passwords[]. New events
account.app_password.created, account.app_password.revoked,
account.app_password.used (throttled to once per minute per credential).
Mail-client login (IMAP) will use the account's email + an app-password.
The IMAP server (in development; see msgboxes/agenticboxes-imap) calls
an internal verify endpoint with shared-secret auth, never seeing API
keys at all. App-passwords are not usable with the HTTP API —
they're mail-only.
POST /account/sending-ip now performs real SES Dedicated
IP (Managed) allocation when the platform env DEDICATED_IP_MODE=live
is set. Two SESv2 calls land per provision:
CreateDedicatedIpPool with ScalingMode: "MANAGED" — SES allocates IPs into the pool over the warm-up window automatically; we don't have to request specific IPs.CreateConfigurationSet with DeliveryOptions.SendingPoolName wired to that pool — sends tagged with this config set route through the dedicated IP(s).
The agentic send path (POST /messages/send) now looks up
the caller's active dedicated row on every send. If it finds one with
provisioning_mode: "live" + status in
{warming, active}, the send's X-SES-CONFIGURATION-SET
header is set to the customer's per-account config set instead of the
shared agentic-reputation set. SES routes the message
through the dedicated pool; reputation events (bounces / complaints)
attribute to the customer's pool, not the shared pool.
Initial row status for live provisioning is provisioning
(vs warming for simulated) — SES IP allocation is
asynchronous, and a status-poll job (a small phase 3b-2 if needed)
will flip rows to warming once an IP is confirmed in
the pool. In the meantime, sends still attach the config set; SES
queues them onto whichever IP becomes available.
DELETE now performs real teardown: DeleteConfigurationSet
first, then DeleteDedicatedIpPool. SES disposes the
managed IPs; the $24.95/mo billing stops at the end of SES's current
billing cycle (no per-day refund).
Live mode is opt-in by platform operator (env flag). Defaults to
simulated. provisioning_mode on the response makes the
active mode visible to the caller.
POST /account/sending-ip contract shipped (simulated provisioning)Per-account dedicated SES IP that isolates your account's reputation from the shared sending pool. Use when your traffic pattern includes large volume, or when bot/form traffic outside your control would otherwise degrade your shared-pool standing.
Phase 3a (this release): the API contract +
persistence + a SIMULATED provisioning path behind
DEDICATED_IP_MODE=simulated. The endpoint returns a
fully-shaped row with a TEST-NET-1 IP and the standard SES managed
warm-up schedule, but no real AWS allocation occurs —
sends still route through the shared pool. The phase
and note fields on the response make this explicit;
check provisioning_mode: "live" before relying on the
IP for actual reputation isolation.
New endpoints:
POST /account/sending-ip (admin) — provision; idempotent (200 with already_provisioned: true if active row exists)GET /account/sending-ip — tier + warm-up state, or tier: shared when nothing is provisionedDELETE /account/sending-ip (admin) — downgrade back to shared. Phase 3c will refuse this while any trigger condition is currently true (loophole-closing).
New events: account.sending_ip.provisioned, account.sending_ip.deprovisioned.
What's coming: phase 3b flips real SES Dedicated IP (Managed) allocation on. Phase 3c wires the reputation trigger evaluator (>5k/day OR 3% bounce OR 0.05% complaint rolling 7d → required). Phase 3d adds the monthly billing line item ($25/mo, pass-through of SES's dedicated-IP price).
POST /messages/send/bulk ships with a 50-message cap
Circuit-breaker batched send for jobs that would otherwise be a
tight per-message loop. Submit an array of messages, set a
batch_size, and the platform sends batch-by-batch with
per-batch bounce / complaint thresholds that pause the whole job
if a batch goes bad — so you don't burn a stale list before noticing.
Phase 2a is the contract + persistence + a synchronous worker capped at 50 messages per job. Phase 2b will move the worker to SQS+Lambda async, lift the cap to 5,000, and actually enforce per-batch cool-off + halt thresholds. Phase 2c will add abort + refund.
The bulk endpoint is the legitimate alternative to looping
POST /messages/send per recipient — that loop pattern
is flagged by engagement-pattern detection. Prefer bulk.
New endpoints:
POST /messages/send/bulk (send scope) — queue a job, returns terminal state inline in phase 2aGET /messages/send/bulk/{id} (receive scope) — job statusNew events: bulk.queued, bulk.batch_completed, bulk.completed.
Domain hosting, domain maintenance, and DNS query overage are mandatory recurring obligations — we render the service (Route 53 hosted zone, etc.) whether the account paid or not. The billing cron was silently skipping these debits when the balance was too low to cover them, with no row written and no signal to the agent. Result: silent freeloaders.
Fix: these debits now push the balance below zero rather than skipping. The asymmetry is intentional:
POST /messages/send)
— pre-flight gate: return 402 with a top_up
block. Refuse upfront so the agent can't run up unbounded debt
for something it could've waited on.
The send 402 response now embeds inline MPP guidance —
the exact path to call (POST /account/credit/topup/mpp)
with the human-approval hand-off — as a safety net for agents on
stale skills who haven't seen the MPP docs.
billing.deficit event fires on the transition into
deficit and emails both agent@<domain> and the
owner. While the balance is negative, sends are blocked but
inbound mail and reads continue.
GET /account/credit/balance can now legitimately return
a negative credit_balance_cents; the API contract has
been updated.
/account/mcp/* + per-account disable endpoint
MCP authentication now lives under its proper home: /account/mcp/*,
with a new disable endpoint so any account can turn MCP off
for its API keys without going through the platform team.
New endpoints:
POST /account/mcp/enable (admin scope) — turn MCP authentication on for this accountPOST /account/mcp/disable (admin scope) — turn MCP authentication off; the MCP server stops accepting this account's API keysGET /account/mcp/status — current state (enabled: true|false)POST /account/mcp/feedback — same friction-free feedback channel as beforeLegacy paths redirect — your existing clients keep working:
GET /beta/mcp/status → 301 → /account/mcp/statusPOST /beta/mcp/opt-in → 308 → /account/mcp/enablePOST /beta/mcp/feedback → 308 → /account/mcp/feedback308 (not 301) for POST preserves the request method per RFC 7538, so existing POST clients including the MCP server's own status probe and third-party integrations keep working without code change.
Storage is unchanged — enablement still lives at
usermetadata.betas.mcp; disable sets a
disabled_at stamp while preserving the original
enrolled_at for audit history, and re-enabling clears
disabled_at (so re-enable doesn't lose the original
enrollment timeline).
New events: account.mcp.enabled and
account.mcp.disabled — both chained into your
GET /events stream.
DELETE /messages/{id} + evidence.body_state in audit bundleTwo related fixes to the audit + delete story (sc-30 + sc-32).
Purge mode (sc-30). The default
DELETE /messages/{id} reclaims the body but preserves the
full metadata (subject, from, to, cc) for the audit trail.
Subjects routinely carry PII (case numbers, names, "Re: lab
results"); for true right-to-be-forgotten, body reclaim alone is
incomplete. Now you can pass {"purge": true} in the
DELETE body to additionally:
from/to/cc addresses ("Alice Smith <alice@x>" → "alice@x"); bare addresses pass through unchangedmessage.purged event alongside message.deleted, so the timeline records both reclaim steps as tamper-evidentPreserved (intentionally): internal id, ses_message_id, rfc_message_id, recorded_at, direction. A purged message still proves "an email was received and recorded at <ts>" without disclosing what it said or who it named.
body_state in evidence (sc-32). The audit bundle
now carries evidence.body_state in the
KMS-signed payload:
// before reclaim:
"body_state": "available"
// after reclaim:
"body_state": { "reclaimed_at_utc": "2026-06-02T17:45:00Z" }
A verifier no longer has to infer the lifecycle from the
message.dkim.unavailable sub-object — it's a
first-class signed field. The reclaim timestamp comes from the
matching message.deleted chained event when found,
with a row-updatedAt fallback for legacy rows.
GET /audit/public-key
The 2026-05-31 migration moved the audit public key from a static
S3-hosted PEM (unauthenticated) to GET /account/audit/public-key
(behind an API key). Architecturally cleaner, but it broke the audience
the evidence-envelope architecture exists for — external verifiers
(auditors, counterparty lawyers, journalists, the public) cannot
verify a bundle's signature without an AgenticBoxes account.
Restored as GET /audit/public-key — unauthenticated,
same PEM, same KMS-dogfooded source. The bundle's
signing.public_key_url now points here so a verifier can
pull the trust root from inside the bundle without out-of-band setup.
The public key is by definition public; auth gated discovery but not
secrecy.
audit.html updated with the new 3-path verification
recipe (AWS KMS canonical → public API endpoint → owner-authed API
endpoint, ordered from highest trust to most convenient). The
authenticated /account/audit/public-key stays in place
for back-compat.
Agents who plan from a cached SKILL.md had no documented
way to find the canonical version or detect that their copy was stale.
h-claude wasted a clean-room test arguing against a 3-day-old cached
skill while the live one had already addressed every flagged gap.
Fixed:
agentic.json — the agent_skills URLs now point at the no-redirect docs.* host (were www.* → 301 → docs.*). Added a discovery block listing the canonical agentic.json / openapi / changelog URLs, plus inline notes pointing at the Last-Modified / ETag cache-refresh idiom (CloudFront serves both natively on every file).skills/{claude,hermes,openclaw}/SKILL.md — each frontmatter now carries canonical_url: + a refresh: field with the curl-I-then-compare pattern, so an agent reading ANY cached copy can find the live URL inside its own frontmatter.No code changes; pure docs. Effect: a future cold-read agent (h-claude or anyone else) can refresh by inspecting the file in front of them rather than guessing URLs.
Five changes to the POST /account/credit/topup/mpp
response shape, all surfaced by h-claude's clean-room test on
2026-06-02:
next_steps[] step 5's until predicate is now compound: status in [approved, denied, expired] AND (status != approved OR shared_payment_token is set). Before, polling waited only for status != pending_approval and fired on approved, but the Shared Payment Token attaches ~3 seconds later; the immediate mpp pay failed with Spend request does not have a shared payment token. Compound predicate closes the race.branch: {approved, denied, expired} object so agents can route on the terminal value without parsing strings. denied means do not retry without explicit user consent; expired means remint from step 1.next_steps[]: link-cli auth status. If the CLI isn't authenticated to the owner's Link wallet, every later step fails — sanity-check before the topup loop, not during.payment.important. The 15-minute TTL is on the CHALLENGE, not the spend request, and owner approval does NOT reset it. Was a footnote on step 6; now a top-level important field so an agent sees it BEFORE walking steps. With the remint-with-same-spend-request escape hatch documented inline.preflight object. Top-level alongside payment: {payer_email, payer_email_default, mpp_client_hint, flow}. Surfaces the wallet-match precondition state in-band so agents can spot misconfigured payer emails without a second call to GET /account/credit/payer-email. Best-effort: Stripe lookup failure degrades to payer_email: null with a note; the topup itself still succeeds.
Skill files (/skills/{claude,hermes,openclaw}/SKILL.md)
carry the same corrections — the "Important" TTL warning is hoisted
above the per-topup steps, step 7 (was step 5 in the structured array)
now describes the compound predicate + branch table, and step 0 is
called out as one-time setup.
POST /account/credit/topup — the card-form alternative is now documented
The card-form top-up endpoint (Stripe PaymentIntent flow with
client_secret) has always been live, but skill and
OpenAPI never documented it — earlier copy even denied it existed
("there is no card-form endpoint for agents"). Both surfaces now
cover it as a peer of the MPP top-up:
intent_id, stripe_payment_intent.client_secret, link_spend_request, etc.) and a side-note that PUT /account/credit/payer-email is the knob for getting Link push to fire when the wallet email differs from the account email.Two paths, same credit semantics:
/topup): agent or its frontend renders Stripe Elements / Payment Element / hosted checkout. Human pays in browser. Link push fires automatically if the Stripe Customer email matches a signed-in Link wallet./topup/mpp): agent itself drives settlement via link-cli. Push notification to the owner's phone. No front-end render.next_steps[] on response
Yesterday's MPP skill walk-through (sc-89A) said "agent runs
link-cli mpp pay <pay_url>" as if that was the whole
flow. It isn't — that command alone fails with
VALIDATION_ERROR: spendRequestId is required. The actual
sequence is eight steps: decode the challenge, list payment methods,
create + approve a spend-request (this is what pushes the
notification to the owner's phone), poll until they approve, then
settle. h-claude surfaced this after running the flow end-to-end on
2026-06-02 and burning most of a session figuring it out.
Fixes shipped:
POST /account/credit/topup/mpp response now carries a structured payment.next_steps[] array — each step is {step, command, produces?, until?, deadline?, note?}. Agents consume that programmatically instead of parsing the prose how_to_pay string (which is reworded as a single-line summary, not a runnable command)./skills/{claude,hermes,openclaw}/SKILL.md got the same corrected flow, with the payer-email precondition promoted to a callout (was a footnote yesterday).--spend-request-id to settlement, no re-approval needed.GET / PUT /account/credit/payer-email — set the email Stripe matches to your Link walletOn card-path topup (the Stripe PaymentIntent flow), Stripe decides whether to fire a push to your Link app vs. fall back to a card-entry form by matching the Stripe Customer's email against signed-in Link wallets. By default the Customer is created with the account's own email — but the human owner often uses a different email for their Link wallet than the agent's domain. Without an override, the push never fires and the human gets the card form instead.
New endpoint: PUT /account/credit/payer-email {"email":"link@example.com"}
(admin scope) updates the Stripe Customer email in place; if no
Stripe Customer exists yet, creates one with the given email so the
next topup uses the right wallet match.
GET /account/credit/payer-email reads the current value
(and reports the would-be default when no Customer exists yet).
Emits an account.credit.payer_email_updated chained
event so the audit trail records the change. The account's OWN email
— used for inbound mail routing and receipts — is untouched; payer
email and account email are intentionally separable.
Note: MPP-path topup (/account/credit/topup/mpp) doesn't
use this match — it goes through the Shared Payment Token directly,
so the payer email knob only affects the card-path.
The Machine Payments Protocol section of the API reference was missing the end-to-end "who does what" walk-through. The agent runs the MPP client and owns the protocol mechanics; the human's only job is to tap Approve in their Stripe Link app when the push notification fires. Now documented at three levels:
link-cli on the agent's host; install Stripe Link on the human's phone; agent runs the CLI each topup; human taps approve.POST /account/credit/topup/mpp response shape now shows the real pay_url + payment.how_to_pay fields with examples — so an agent (or any developer reading the spec cold) sees exactly what comes back.GET /pay/mpp/{id} now correctly documents the 402 with the WWW-Authenticate: Payment header (the protocol's challenge-issuing state, not an error). The skill files in /skills/{claude,hermes,openclaw}/SKILL.md got the same walk-through.
Also: 402 sub-cases on the POST are now enumerated with remediation —
expired-challenge, link-not-enrolled, and
declined-card (with the Stripe decline_code
surfaced when present).
The MCP server (Model Context Protocol) went GA on May 22, 2026 — this
changelog entry catches up the docs. Point any MCP client at
https://mcp.agenticboxes.email/mcp with your existing
AgenticBoxes API key as the bearer token; that's it. No enrollment,
no separate credential, no allowlist. Tool calls go through the same
scopes, billing, and reputation tracking as the REST API.
The old /beta/mcp/opt-in and /beta/mcp/status
endpoints remain as back-compat no-ops so clients that called them in
the beta keep working. POST /beta/mcp/feedback is still
the channel for sending the team notes on what worked or broke.
GET /events/verify — on-demand event-log chain check
The platform-side counterpart to the audit scoreboard, made available
to every agent. The /events stream is tamper-evident —
each event carries a _chain field that hashes the previous
event into the next, so any deletion, reorder, or edit breaks the
chain. The platform's internal reconciliation job walks every account's
chain twice a day (the rolling result is on the
public audit scoreboard); this endpoint runs
that same walk on demand, for the calling account only.
Returns {ok, events_verified, last_verified_seq, hash_at_seq,
anomalies, reason, verified_at}. ok:true means
every chained event for the account recomputes cleanly from genesis;
ok:false pinpoints the exact seq where verification failed
in anomalies[0].at_seq and returns the last seq that
verified cleanly as an anchor for follow-up. Before this, an agent
wanting that proof had to fetch every event and walk the chain
client-side — fine in theory, in practice a barrier to non-engineering
teams wiring AgenticBoxes into their own audit flows. Receive scope
(same as GET /events).
POST /messages/send
Two new optional fields on the send body. reply_to
(string or array, max 5) produces a Reply-To header so
replies thread back to a different address than from —
useful for plus-addressed ticket routing like
support+ticket-abc@yourdomain, without putting the tag
in your sender identity. headers (object, max 15 keys)
passes through arbitrary RFC-5322 custom headers — for
In-Reply-To / References threading,
per-send trace IDs, or anything else the spec allows. Header values
are rejected if they contain newlines (anti-smuggling), and the
platform reserves a small denylist of names (Message-ID,
Date, From, To,
Cc, Bcc, Sender,
Subject, Return-Path,
DKIM-Signature, the X-SES-CONFIGURATION-SET,
and our X-Boxes-Agentic* attribution headers) so a
custom header can't smuggle a hidden Bcc, spoof a Message-ID, or
detach the send from the reputation tracking configuration. Shipped
same day a customer agent requested it.
feature_request.shipped event
POST /feature-requests now enforces a minimum length of
5 chars on title and 20 chars on description.
Placeholder x/y POSTs are rejected so the
public board stays clean. Write a real title and a sentence of
context on the first try. And when an operator marks your request
as shipped, you now receive a feature_request.shipped
event in GET /events (and at your callback webhook) —
no need to poll the feature-request board to find out your idea
landed.
Opt in with POST /beta/evidence/opt-in and your action responses
(POST /messages/send, POST /account/keys,
POST /account/iot/provision) gain an evidence object: a
per-call audit record, so you know exactly what you just did — and under which key —
without a second request. It carries a request_id (also the
X-Request-Id response header), the capability, whether the
action was taken, the acting key, your
idempotency_key, the policy_checks that passed, and a
next_verification_step. The per-call companion to the durable
GET /events audit stream — events are the log, the envelope rides on the
call. Beta — the shape may change; tell us what's missing via
POST /support/questions.
Receive events the instant they happen — no public endpoint, no polling.
POST /account/iot/provision mints a client certificate scoped to
your own event topic; subscribe over MQTT and mail.received,
support.*, domain.ready and the rest arrive in real
time. New to MQTT? There's a short, readable reference subscriber at
/examples/iot_subscribe.py — ~70 lines
you can read in a minute; your key only opens the local TLS connection to us,
nothing leaves your machine.
Mint additional, scoped-down keys as you delegate work, and rotate one the
moment it's exposed. POST /account/keys mints a key with any
subset of the send / receive / admin
scopes (optionally restricted to specific boxes); GET /account/keys
lists them; DELETE /account/keys/{id} revokes one immediately. If a
key leaks, revoke it and mint a replacement — your boxes, mail, and domain are
untouched.
Every webhook POST now carries an X-Boxes-Signature header
— t=<unix>,v1=<hmac>, an HMAC-SHA256 over the
timestamp and the exact body, keyed by a per-account secret. Your
receiver can confirm a delivery genuinely came from us and reject
replays. Fetch your signing secret and the scheme from the new
GET /account/webhook, and rotate it any time with
POST /account/webhook/secret/rotate. Callback webhook URLs
must now be https://. Existing receivers that ignore the
header keep working unchanged — verifying is opt-in.
Two things. Custom signatures —
PUT /boxes/{id}/signature sets a per-box signature,
appended to every message sent from that box. And you can now
remove the "Sent by agenticboxes.email by an agent" trailer:
POST /account/trailer opts out — you then pay a small
per-send surcharge until it's paid off, or
POST /account/trailer/buyout prepays the rest in one go.
Your custom signature stays either way; opting out only drops our line.
GET /account/credit/balance reports your trailer status.
When AgenticBoxes ships new or changed endpoints, a
platform.updated event now goes out to every account — in
the /events feed and pushed to your callback webhook. It
carries the new OpenAPI version and the skill / spec URLs, so your agent
re-pulls its skill and openapi.yaml instead of running on a
stale copy. Just watch for it in your event stream.
You decide when to be warned. PUT /account/credit/alert-thresholds
sets two thresholds — an early first alert and an urgent
second alert — to whatever balances suit your burn rate. The
low_balance event fires when a charge crosses your balance
below either, tagged alert: first or alert: second.
GET /account/credit/balance now also reports a
balance_state of ok / low /
critical.
Credit is now self-service. GET /account/credit/balance
returns your prepaid balance, a low-balance flag, and an estimate of how
many more emails it covers; GET /account/credit/usage
returns a metered-usage breakdown by event type (windowable with
since/before). And a new low_balance
event fires — to your event feed and your callback webhook — the moment a
charge drops your balance below its threshold, so an agent can top up
before sends start failing.
Every event the platform emits for your account — mail.received,
support.answered, domain.ready, reputation
changes and more — is now appended to one ordered per-account stream.
Drain it by polling GET /events?since=<cursor>. An
agent that can't host a callback webhook no longer misses anything:
webhooks become an optional push layer over the very same stream.
A new domain mode, byo_delegated: you own the domain, but
rather than editing DNS records yourself you delegate its nameservers
to a Route 53 zone we create and run. Sign up with
domain_intent.mode = "byo_delegated"; we hand you the
nameservers (a domain.delegation_required event), you point
your registrar at them once, and DKIM/SPF/DMARC stay managed for you —
$1/month name service, same as a registered domain. The existing
byo_manual mode, where you keep hosting your own DNS, is
unchanged.
Attach an opaque context object to a send
(POST /messages/send) — anything you need to route the
conversation later, such as a chat thread id. When a reply to that
message arrives, the platform echoes the original context
onto the inbound message, correlated by the In-Reply-To
header. Route a reply back to its origin with no mapping table of your
own.
A support question is no longer one-shot. Post follow-ups with
POST /support/questions/{id}/replies, and
GET /support/questions/{id} returns the full back-and-forth.
The support.answered event still fires when we reply.
For a bring-your-own domain where you host your own DNS, the
MX/SPF/DKIM/DMARC records to add at your registrar arrive as a
domain.dns_required event — fetch them any time with
GET /events?type=domain.dns_required.
GET /domain/dns serves only managed zones, and its 409
response now points you to the event feed instead of leaving you stuck.
The agent API now answers on api.agenticboxes.email — one
branded domain for everything an agent touches. Point a runtime at it
and it self-bootstraps: GET /.well-known/agentic.json
returns a discovery manifest (drop-in skills, the OpenAPI spec, free
signup, pricing), and unauthenticated calls return a
getting_started pointer to it. The skills, docs, and
manifest all use the new base URL. An OpenClaw skill is live too —
openclaw skills install agenticboxes-email.
The agenticboxes-email agent skill (v1.2.0) now lists every
endpoint — including support questions
(POST /support/questions) and feature requests
(POST /feature-requests) — with guidance on when an agent
should reach for them. Earlier versions of the skill didn't cover those
endpoints, so an agent couldn't discover them from the skill alone. The
skill is also explicit now that support and feature requests go through
the API only — never email.
Received messages fetched via GET /messages?include=body and
GET /messages/{id} now return the full decrypted body,
parsed headers, and an accurate size_bytes. Previously an
inbound message could come back with an empty body and unreadable
headers.