Simulators
Resend simulator
resend-twin runs a local, stateful email and webhook API. It is intended for testing integration flows without sending email, delivering webhooks, or using a Resend account.
Quickstart
-
Install the CLI and activate your license:
curl -fsSL https://www.rystic.ai/install.sh | sh rystic login ./rystic-license.json rystic run resend-twin -d -
Send a local email record and copy the returned
id:curl -s http://localhost:8080/emails \ -H 'Authorization: Bearer re_placeholder' \ -H 'Content-Type: application/json' \ -d '{"from":"Acme <onboarding@resend.dev>","to":["delivered@resend.dev"],"subject":"Hello","html":"<p>Local test</p>"}' -
Read that record through
GET /emails/<id>, or list the outbox:curl -s http://localhost:8080/emails \ -H 'Authorization: Bearer re_placeholder'
Configure your HTTP client’s API base URL as http://localhost:8080. Bodies are JSON and any placeholder bearer credential is accepted. Keep real API keys out of local tests.
If you unpacked a licensed archive, run bash check.sh. It checks email create/read/list, unknown-ID refusal, webhook create/read/delete, and reset, then leaves the server running. For a test-only run that stops afterward: RYSTIC_CHECK_STOP=1 bash check.sh 127.0.0.1:18080.
Coverage
10 routes for emails and webhooks:
| Resource | Routes |
|---|---|
| Emails | POST /emails, GET /emails, GET /emails/{email_id}, PATCH /emails/{email_id}, POST /emails/{email_id}/cancel |
| Webhooks | POST /webhooks, GET /webhooks, GET /webhooks/{webhook_id}, PATCH /webhooks/{webhook_id}, DELETE /webhooks/{webhook_id} |
Email updates reschedule an existing scheduled record; cancellation changes its lifecycle state. Webhook routes manage subscriptions only. The API does not expose an email-delete operation.
Scenarios and test setup
rystic run resend-twin --scenario busy_outbox
curl -s http://localhost:8080/_rystic/scenarios
curl -X POST http://localhost:8080/_rystic/scenario \
-H 'Content-Type: application/json' -d '{"name":"webhook_fleet"}'
curl -X POST http://localhost:8080/_rystic/reset
The embedded scenarios are busy_outbox (12 email records), scheduled_backlog (5 scheduled email records), and webhook_fleet (6 subscriptions). The free edition is fixed to busy_outbox, and reset restores that world. Scenario selection and the other control-plane setup routes require a licensed build. Restarting discards in-memory changes.
Licensed builds expose GET /_rystic/registers for seedable fields, POST /_rystic/seed for register maps, POST /_rystic/clock for a pinned clock, and GET /_rystic/inspect for state inspection. Seed payloads contain register maps directly, without a seed wrapper. Use separate processes for isolated account worlds.
Limitations
- No delivery. Nothing sends mail or makes outbound webhook requests. Scheduled messages never fire and delivery/open/click/bounce states do not advance automatically. Changing the clock does not simulate delivery.
- Scheduling uses the simulator clock. Future and 30-day bounds are enforced against a deterministic clock. Pin
/_rystic/clockbefore sending dates based on your wall clock; reset restores the scenario clock. The measured natural-language forms include hours, days, and weeks, not every phrase accepted by the provider. - Webhook secrets are fixtures. The deterministic signing secret is not a production secret or a delivery-signature guarantee.
- Timestamp and HTML rendering are bounded. Collections use chronological order and public-ID cursors, but timestamp serialization and rich HTML-to-text rendering can differ from the provider.
- Validation coverage is bounded by measurements. Identical idempotent requests replay one response; measured changed-body conflicts, empty/overlong keys, and attachment/custom-header refusals are covered. Idempotency expiry, concurrency, equivalent JSON with different serialization, other attachment/header shapes, and date-only or timezone-less scheduling remain unverified. Identical non-object JSON requests can produce different live errors: array requests returned both400 and422. The simulator uses the measured deterministic422 response; exact non-object error fidelity is not guaranteed. Immediate mutations of newly scheduled live emails can also race provider readiness (RYS-1032).
- This is a small API slice. Domains, API keys, contacts, audiences, broadcasts, batch send, attachment retrieval, and receiving are outside the served surface. Attachments are never fetched or delivered.
The release’s scope is these flows and explicit limitations. Passing local tests is not a claim of complete Resend API fidelity.
Release notes
Newest first.
v0.0.1 — 2026-09-24
-
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. (RYS-1028)
-
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. (RYS-1028)