Simulators
Lob simulator
lob-twin is a local, stateful simulator of Lob’s print-and-mail API — addresses and postcards with real validation, cancellation windows and idempotent replay. Nothing it answers can ever print or mail a piece.
Free edition
No license needed: download the free edition, untar, run ./lob-twin. One fixed world (lob_mailroom: six addresses and five postcards from rendered through delivered), reads and writes, no control plane beyond POST /_rystic/reset — the free edition section says exactly what is in and out.
./lob-twin # port taken? ./lob-twin --addr :9000
curl -s -u test_anything: 'http://localhost:8080/postcards?status=delivered'
Quickstart
Builds are in limited release — request access for a download link.
curl -fsSL https://www.rystic.ai/install.sh | sh
rystic login ./rystic-license.json
rystic run lob-twin -d --scenario lob_mailroom
curl -fsS http://localhost:8080/_rystic/version
Expect product to be lob-twin and version to be 0.0.2. An older version means an older install: rystic pull lob-twin upgrades it.
Lob’s auth is the API key as the Basic username with an empty password; the twin accepts any key:
curl -s -u test_anything: 'http://localhost:8080/postcards?status=delivered'
Point the SDK at it — host only, code unchanged. The twin serves /addresses, not /v1/addresses:
configuration = lob_python.Configuration(
username="test_anything",
host="http://localhost:8080",
)
Coverage
8 routes: addresses (create, retrieve, delete, list) and postcards (create, retrieve, cancel, list), plus the /_rystic/ control plane. POST /postcards honours Idempotency-Key, so a retried send returns the original piece. The postcard list filters on status, mail_type, scheduled, campaign_id, send_date, date_created and metadata, and pages with limit, before and after.
Not modelled: letters, checks, self-mailers, templates, campaigns as a resource, address verification, webhooks.
Worlds
Four named worlds are baked into the binary — --scenario <name>, or POST /_rystic/scenario to switch without restarting:
| world | for |
|---|---|
lob_quiet_desk | the smallest complete account: one recipient, one freshly rendered postcard |
lob_mailroom | every healthy stage of the mail stream, rendered through delivered, with tracking histories |
lob_returned_mail | returned to sender, failed verification, re-routed and canceled pieces, each with its failure_reason |
lob_campaign_blast | six sends under one campaign_id on staggered dates, for paging and campaign filters |
Seeding, determinism, reset
POST /_rystic/seed takes a register map (state_address, state_postcard, state_idempotency_key) and lands it directly; GET /_rystic/registers lists every field. POST /_rystic/reset rewinds the id sequence and the clock, so two identical runs give byte-identical postcards down to id, send_date and expected_delivery_date.
curl -X POST localhost:8080/_rystic/reset
curl -X POST localhost:8080/_rystic/seed -d '{"state_address":{"adr_seed1":{"id":"adr_seed1","object":"address","name":"SEED","address_line1":"185 BERRY ST","address_city":"SAN FRANCISCO","address_state":"CA","address_zip":"94107"}}}'
curl -s -u x: localhost:8080/addresses/adr_seed1
Limitations
-
Nothing is rendered:
url,raw_urland front/back thumbnail links are placeholders; the twin generates no PDFs or images. -
A postcard does not progress on its own. A create answers
processedand stays there; boot a world or seed the stage you need. -
No general address verification: the captured 210 King St, San Francisco case receives ZIP+4 94107-1702, but other addresses have no USPS lookup or deliverability check. Reads preserve stored address values.
-
No webhooks; the twin never makes an outbound call.
-
Ids are a sequence (
psc_0000000000000001), not Lob’s random hex. -
Populated asynchronous
renderedandfailedresults, including provider-driven transitions andfailure_reason, remain outside verified probe coverage (RYS-1084; residual dispositions in RYS-623). Seeded worlds do not verify Lob’s rendering process. -
Failed postcards: Lob rejects cancellation with
422 not_deletable; the twin currently allows it inside its send window. This refusal path is not supported in this release (RYS-1090).
Canceled postcards remain retrievable with deleted: true and disappear from lists. Terminal pages return next_url: null, inline postcard addresses include nullable contact fields, and created postcards retain front/back thumbnail links.
The full guide ships in the release as HOWTO.md.
Release notes
Newest first.
v0.0.2 — 2026-09-24
-
Postcard cancellation and response consistency. Canceled postcards remain retrievable with
deleted: trueand disappear from collection lists; created postcards retain front/back thumbnail links. Scheduled filtering compares send and creation dates, and omitted send dates use the documented default cancellation window. Address creation stores its standardized response, while reads preserve stored address values. Lob API keys resolve through the shared HTTP Basic authentication layer, including regenerated testbooks and shape checks. Collection probes isolate their seeded postcards with a fresh metadata filter and exercise pagination with two records. (RYS-1067) -
Updated coverage guidance. The Lob guides now describe repaired cancellation, pagination, thumbnail and address responses, clarify HTTP Basic authentication, and distinguish seeded mail stages from unverified asynchronous rendering and failure transitions. (RYS-1085) The guides also disclose the failed-postcard cancellation mismatch, with repair tracked in RYS-1090. (RYS-1085)
v0.0.1 — 2026-09-24
-
A local Lob for your mail-sending code’s tests. The first release of the Lob twin: 8 routes over addresses (create, retrieve, delete, list) and postcards (create, retrieve, cancel, list), served from a model validated against real
api.lob.comwith a test key. Point your Lob SDK’s host at the twin —http://localhost:8080, no/v1— and your existing calls run against it unchanged, with form-encoded or JSON bodies. (RYS-1023) -
A retried send is the same postcard.
POST /postcardshonours anIdempotency-Keyheader, or anidempotency_keyquery parameter, as Lob does: a replay under the same key answers the original postcard instead of sending a second one, so the retry loop around your send is testable without mailing anything twice. (RYS-1023) -
Four named worlds, baked in.
--scenario lob_mailroomboots five postcards standing at every healthy stage from rendered through delivered, with their tracking histories;lob_returned_mailthe returned, failed, re-routed and canceled pieces with theirfailure_reason;lob_campaign_blastsix sends under onecampaign_idfor paging and filters;lob_quiet_deskone of everything.SCENARIOS.mdsays what each is for. (RYS-1023) -
Seed the mailroom your test needs, and get the same one twice.
POST /_rystic/seedlands addresses, postcards and idempotency keys directly;GET /_rystic/registerslists every field it accepts.POST /_rystic/resetrewinds the id sequence and the clock, so two identical runs give byte-identical postcards down toid,send_dateandexpected_delivery_date. (RYS-1023) -
It never reaches the network, and it needs no key. Any key is accepted as the Basic username and the twin dials nothing: nothing you send renders a PDF, counts against a rate limit, or could ever print or mail a piece. Not in this release: letters, checks, self-mailers, templates, campaigns as a resource, address verification and webhooks; a postcard does not move through the mail stream on its own — boot a world or seed the stage you need. (RYS-1023)
-
What is measured, and what is not. Fidelity is scored differentially against real
api.lob.comwith a test key, per response field, not by eyeball: the last full-suite round was 13 of 21 probes on 2026-09-24. That is an early number and this release ships it as-is. The misses are four known differences, listed inHOWTO.mdunder Limitations:next_urlis a link on the last page where Lob sends null; an inlinetoorfromaddress omitsemailandphone, and a postcard read answersdeleted: false, where Lob sends null; a canceled postcard reads back as 404 where Lob answers the canceled piece; and an address reads back with the ZIP you sent, not Lob’s ZIP+4. Rounds are re-run daily and each release states its own number. (RYS-1023)