# Resend changelog

What changed in each Resend simulator release, newest first.

What changed in each Resend simulator release, newest first.

## v0.0.11 — 2026-11-06

1. **The docs gather errors and limits in one section.** It shows Resend's flat error body, the shared ten-requests-per-second window and its headers, how `GET /emails` and `GET /webhooks` page, and how an idempotency key replays a send.

## v0.0.10 — 2026-11-06

1. **The docs name Resend's base URL option and read a seeded email back by its id.** The connect section names `resend.api_url` for Python, and the seeding example reads the email back with `GET /emails/{id}`.

## v0.0.9 — 2026-11-05

1. **Swagger UI to read and call the twin from the browser.** `/_rystic/swagger`, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin.

2. **The twin documents its own control plane in Swagger.** `/_rystic/swagger` now lists a **Rystic control plane** spec, served at `/_rystic/swagger/spec/rystic-control-plane.json`, describing the `/_rystic/*` routes this twin serves (its scenarios, inspect views and seedable registers by name) and its `/_bounce` route for bouncing a sent email.

3. **A state dump seeds back as it was, and the next record gets a fresh id.** `GET /_rystic/inspect` posted to `POST /_rystic/seed` after a reset restores every record where it was, per-caller records included, which used to land under a doubled key. The next record created after any seed passes every uuid and event id the simulator minted that the seed holds. `GET /_rystic/inspect?view=sequences` lists every id counter, and a world document's new `sequences` key restores them, so ids the simulator renders from a counter continue where the dump left off.

4. **A slow client no longer holds up the others.** The simulator reads a request's whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients' requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.

5. **The call log shows what each call asked for and whether it succeeded.** Each `GET /_rystic/calls` entry now carries `args`, the call's query and body arguments with credentials redacted (long values cut, file contents left out), and `ok`, false for an answer that is not 2xx. A path the simulator does not serve is marked `unknown_route: true`. `?tail=N` returns only the last N calls, and `dropped` counts the calls the log no longer keeps.

6. **The docs are one page, with a page per feature.** The archive ships `README.md` in place of `HOWTO.md`, with the same sections in the same order as every Rystic simulator's page, and `CHANGELOG.md` beside it. Scheduled emails and the Dashboard each have their own page under `docs/features/`. Sandbox recipients and bounces share another page. Every example on these pages runs as written against a fresh simulator.

7. **The docs page links the admin token section.** The page now sends you to the admin token's section of the shared-simulators page.

8. **Scenario descriptions use customer terms.** The Scenarios table and `--list-scenarios` describe each scenario in American spelling, with no internal references.

9. **The Scenarios table is easier to scan.** Each row says in a line what the scenario is for, and a list below the table says what each scenario holds.

10. **Pro starts with a 14-day trial.** Free has ended, so every simulator is on Pro and Enterprise, and the docs name only those plans.

11. **Limitations is shorter to read.** It opens with one sentence counting the limitations. The detailed lists follow, and the website folds them behind "Show all limitations".

12. **The page has no Plans section.** The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.

13. **The docs read more plainly, and each difference from Resend is marked.** Sentences are shorter, and each behavior that differs from Resend is marked "Differs from Resend" where it's described, such as an email to `delivered@resend.dev` that stays `queued`. Rules with several outcomes are tables: idempotency keys, a missing or malformed API key, the bounce route's refusals and the scheduling window. Auth, seeding and reset keep only the simulator's own facts, and the shared pages cover the rest.

## v0.0.8 — 2026-10-12

1. **A clock jump fires each timeline event at its own time.** When one `POST /_rystic/clock` or paced tick passes several events, each fires with the clock at its `at`, so the records it creates carry that time, and the clock then lands where the move asked. An event already due when the clock moves fires at the current time.

2. **Webhook signatures can follow a shifted receiver clock.** `POST /_rystic/clock {"wall_offset_seconds": N}` signs every webhook delivery N seconds past the wall clock, so a receiver whose own clock you moved forward (as `sandbox clock -advance` does) still accepts the `svix-timestamp`; `0` or `/_rystic/reset` puts it back.

3. **A timeline call can carry raw bytes, XML or a JSON array.** A `call` event may send `body_text` or `body_base64` instead of `body`; the twin sends it as written, under the route's own content type (XML, bytes, JSON or a form), so a scheduled call can upload an object's bytes or post an XML document, and `${name}` fills in `body_text`. A `body` may be a JSON array where the route reads one. A call that answers XML binds from its elements by JSON Pointer, such as `/InitiateMultipartUploadResult/UploadId`.

## v0.0.7 — 2026-10-12

1. **Registered webhooks can receive their events.** Boot with `RYSTIC_WEBHOOK_DELIVERY=on` and each webhook event a send triggers is POSTed to every enabled webhook registered through `POST /webhooks` whose `events` names it, signed with its `signing_secret` in Svix headers at wall-clock time so svix's `Webhook.verify` accepts it however the simulator clock is pinned; without the variable nothing is POSTed.

2. **A world can carry its own timeline.** A `--seed` file, or a complete world document posted to `POST /_rystic/seed`, may add `credentials`, binding presented credentials to actors, and `events`: calls the twin makes on its own API at set sim times as one of those actors, through the same handlers and egress as your requests. A top-level `credentials` or `events` key the twin used to ignore now takes effect. `RYSTIC_PACE=<speed>` plays the clock, and the timeline, in real time, and an event's `at` may be `+<duration>`. `/_rystic/inject`, `--inject` and `RYSTIC_INJECT` now need `state_set` instead of `scenarios`.

3. **Sandbox recipients bounce and complain.** An immediate send whose first `to` recipient is `bounced@resend.dev` or `complained@resend.dev`, with or without a `+label`, now emits `email.bounced`, or `email.delivered` and `email.complained`, after `email.sent`, and the email reads `last_event` `bounced` or `complained`; under `RYSTIC_WEBHOOK_DELIVERY=on` each is delivered Svix-signed to the webhooks subscribed to it. A bounced email's `email.bounced` and `GET /emails/{email_id}` carry the `bounce` object live Resend sends.

4. **A timeline call can hand what it minted to later events.** A `call` event may add `bind`, `{"<name>": "<JSON Pointer>"}`, saving fields of its answer, such as an ID the twin minted; a later event uses one as `${name}` in a call's path, headers or body or in a seed event's record keys and values (a register name cannot be bound). A schedule naming a `${name}` no earlier call binds is refused at load, and an event whose binding call failed records an error instead of firing.

5. **`design_studio` carries a timeline.** As the clock moves past `2026-03-01T00:00:00Z`, the world registers a billing webhook, sends Copperline Coffee's March plan invoice, and schedules an overdue reminder to Pickford Law, moves it off a Sunday and cancels it, each a call on the twin's own API under the world's bound key. Those calls take the first email and webhook IDs, so a test that advances the clock before sending sees its own email minted later in the sequence.

6. **A world can bounce an email.** `POST /_bounce` bounces an email that went out to any address: it reads `bounced` with its `bounce`, and `email.bounced` reaches the egress log and every webhook subscribed to it. A world's timeline schedules it like any other call.

## v0.0.6 — 2026-10-11

1. **An email and a webhook never share an ID.** Each kind of record draws its IDs from its own range: the first email you send is `28c9b732-cf71-466d-8000-000000000001`, where it and the first webhook were both `00000001-0000-4000-8001-000000000001`. Ids still count up in their last digits and restart on reset, and webhook signing secrets change the same way; a test that pinned an old ID needs the new one.

2. **Every route refuses a missing or malformed key the way Resend does.** All ten routes check `Authorization` before anything else: no header answers `401 missing_api_key` with "Missing API Key", and an empty Bearer key or another scheme such as `Basic abc` answers `400 validation_error` with "API key is invalid", where the twin used to answer 401 with "Missing API key in the authorization header." `GET /emails`, `POST /emails/{email_id}/cancel`, `POST /webhooks`, `GET /webhooks/{webhook_id}` and `DELETE /webhooks/{webhook_id}` used to answer without a key, so a test that calls them without one now needs a placeholder key.

3. **A scheduled send no longer shows an SMTP handoff.** `POST /emails` with `scheduled_at` writes only its `email.scheduled` event to `GET /_rystic/egress`, since Resend hands the mail to SMTP only at the scheduled time; an immediate send still writes the handoff before `email.sent`.

4. **A send or update with a form Content-Type is refused as Resend refuses it.** `POST /emails` and `PATCH /emails/{id}` with a JSON body labeled `application/x-www-form-urlencoded` answer 422 with Resend's message (``Missing `to` field.`` and ``The `scheduled_at` field is required.``), where the simulator answered 400 to a send and applied the update. Label JSON bodies `application/json`.

5. **A scheduled email's `scheduled_at` reads back as Resend renders it.** `GET /emails/{id}` and `GET /emails` return `scheduled_at` in the same form as `created_at`, such as `2026-01-03 00:00:00.439000+00`, where the simulator echoed an ISO 8601 string such as `2026-01-03T00:00:00.439Z`. A test that compares the string should compare the instant.

6. **The scripts in this archive are MIT-licensed.** `check.sh` and the archive's other scripts are published under the MIT License at https://github.com/Rystic-Inc/rystic-scripts, so you can copy, change and share them. `LICENSE.txt` no longer covers them; it still governs the twin itself.

## v0.0.5 — 2026-09-30

1. **Scheduled email reads preserve null values.** Text-only emails return `html: null`, and newly scheduled emails return `message_id: null` until their state indicates a completed send. Explicit HTML, including an empty string, and existing message identifiers are preserved.

2. **Resend fidelity limitations are explicit.** The HOWTO and website docs disclose missing throttling and sender-domain checks, distinguish local auth errors from measured Resend responses, and explain how to choose scheduled dates without treating `initial_clock` as the scenario clock. The dated fidelity result and absence of a standing daily converge round remain explicit.

3. **Sent-email events include ISO timestamps, message IDs and tags.** The `email.sent` events in `GET /_rystic/egress` use UTC ISO 8601 timestamps and include `data.message_id` and supplied tags as a name/value map, so tests can inspect these fields in the outbound event log.

4. Request admission shares a ten-per-second window across credentials, emits rate-limit headers on API responses, and returns 429 with retry-after when exhausted. The window uses real time; control-plane requests are exempt and reset restores the allowance. Daily/monthly quotas remain unmodeled. The full 39-probe live round matched and exercised 36/39, including the header and actual 429 burst probes; the three known POST/PATCH differences remain.

   Burst comparison requires a 429 observed from the live API in both two-way and three-way runs; if the live API never refuses, the result is inconclusive. Wire recording emits each burst request once, without duplicating the selected response as a synthetic exchange.

   The 36/39 rate-limit acceptance round predates the merge of the webhook fields. Both features and their separate live records are retained; the combined simulator is covered by local regression tests, without claiming a new combined live round.

5. **Rate-limit headers arrive spelled as Resend spells them.** `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`, `ratelimit-policy` and `retry-after` now reach the wire lowercase instead of as `Ratelimit-Limit` and friends, so a client that keys on the received header name sees the same keys it sees from Resend.

6. **The state UI shows the outbox as Resend's dashboard.** On Pro and Enterprise, `/_rystic/ui` opens a live Resend-style view of the twin — emails with their status, events and rendered body, and webhooks with the events they would receive — where you can reschedule or cancel a scheduled email and add, edit, disable or delete webhooks through the API, beside Your program and Egress cards.

## v0.0.4 — 2026-09-26

1. **The guide starts from an API key, says what each plan reaches, and documents the outbound mail log.** `HOWTO.md` now opens with `rystic login rys_…`, says what Free, Pro and Enterprise boot and which control-plane routes each answers, and names the five routes that answer `401 missing_api_key` without a bearer key and the five that check none; `SCENARIOS.md` says what each plan boots. Each `POST /emails` writes the SMTP message and its `email.sent` or `email.scheduled` event to `GET /_rystic/egress`, and the guide now says so. `check.sh` finds the API key `rystic login` stored as well as a license file, looked up in the CLI's order, and `rystic features` marks the features and commands that need Pro or Enterprise.

## v0.0.3 — 2026-09-26

1. **The fidelity number is current again.** The first full-suite comparison on the 38-probe suite scored 35 of 38 against api.resend.com on 2026-09-25, and the HOWTO and site page name the three misses (content-type mismatch on sending and updating an email, and the non-object send body) with the repair that closes them.

2. **Only `design_studio` ships as a named scenario.** The older scenarios are now test-only fixtures and are no longer accepted by `--scenario`, `RYSTIC_SCENARIO`, or `POST /_rystic/scenario`. Tests and evaluations can load their unchanged register maps and clocks from `sim/testdata/scenarios/` through the control plane.

## v0.0.2 — 2026-09-25

1. **Self-serve on the metered edition replaces the free edition.** A Rystic account's token runs the metered build: `rystic login rys_…` once, then `rystic run` or `docker run -e RYSTIC_TOKEN=…`. It carries every scenario and control-plane route your plan includes, and bills active 30-second blocks against the plan's allowance. The fixed-world free download and its usage report are gone.

2. **A new world, `design_studio`, now the default.** `--scenario design_studio` boots Website Design Contractors' outbox: 595 emails since November 2024 (invoices, receipts, overdue reminders, a declined-card notice, a refund notice, welcome and launch notes, and one receipt bounced off a departed contact, then resent), for the same clients, invoice numbers and dates the studio's Slack, Stripe, PayPal and Lob worlds hold. A lease that grants no scenarios boots it instead of `busy_outbox`.

3. **The limitations now date the fidelity evidence.** The HOWTO and the site page say which comparison rounds against api.resend.com stand behind the release: the 25/25 full-suite round of 2026-09-02 and the scoped rounds of 2026-09-24 that verified the 13 probes added since, with no full-suite round yet on the current 38-probe suite. The package manifest also describes the metered edition instead of the retired free edition.

## v0.0.1 — 2026-09-24

1. First Resend simulator release: email lifecycle and webhook APIs, three deterministic scenarios, licensed and free archives, and customer quickstart. Includes measured validation and idempotency behavior; asynchronous delivery and documented fidelity limitations remain outside the release claim.

2. **Resend email and webhook workflows, locally.** The first package provides ten stateful API routes, three embedded scenarios, a smoke check, and a quickstart. Forward email paging now traverses all records. No email or webhook is delivered; scheduling, validation, collection and fixture limitations are documented explicitly.

---

Source: https://www.rystic.ai/docs/simulators/resend/changelog · Markdown: https://www.rystic.ai/docs/simulators/resend/changelog.md
