Skip to Content
SimulatorsResend changelog

Simulators

Resend changelog

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.

Last updated on