Skip to Content
SimulatorsLobMail stream

Simulators

Mail stream

The mail stream is what happens to a postcard after you send it: its USPS scans and the window in which you can still cancel it. Creating a postcard returns it with status processed, and it stays there. Record each scan with POST /_tracking_event, and cancel the postcard with DELETE /postcards/{id} while its window is open:

curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"design_studio"}' -o /dev/null
curl -s -u test_anything: "$RYSTIC_URL/addresses" -d name='Harry Zhang' -d address_line1='210 King St' -d address_city='San Francisco' -d address_state=CA -d address_zip=94107 | jq -c '{id}'
# -> {"id":"adr_0000000000000001"}
curl -s -u test_anything: "$RYSTIC_URL/postcards" -d to=adr_0000000000000001 -d from=adr_0000000000000001 -d front='<html>Hello</html>' -d back='<html>World</html>' -d use_type=operational | jq -c '{id, status, send_date, tracking_events}'
# -> {"id":"psc_0000000000000001","status":"processed","send_date":"2026-03-01T00:05:00.002Z","tracking_events":[]}
curl -s -X POST "$RYSTIC_URL/_tracking_event" -H 'Content-Type: application/json' -d '{"postcard":"psc_0000000000000001","name":"In Transit","location":"94107"}' | jq -c '{name, location, type}'
# -> {"name":"In Transit","location":"94107","type":"normal"}
curl -s -u test_anything: "$RYSTIC_URL/postcards/psc_0000000000000001" | jq -c '{status, scans: [.tracking_events[].name]}'
# -> {"status":"processed","scans":["In Transit"]}
curl -s -u test_anything: -X DELETE "$RYSTIC_URL/postcards/psc_0000000000000001" | jq -c .
# -> {"deleted":true,"id":"psc_0000000000000001"}

Differs from Lob: Lob leaves tracking_events empty on a test-mode postcard, and a live postcard gets its scans from USPS by itself. The simulator serves the scans a scenario or seed carries and the ones you post.

Record a scan

POST /_tracking_event appends a normal tracking event to a postcard, so the postcard’s next read serves it. Its body, form-encoded or JSON with Content-Type: application/json, takes these fields:

Field
postcardrequired: the postcard’s id, psc_…
namerequired: one of the scans Lob documents, listed below
locationoptional: a ZIP code. The event’s location reads null without it.
timeoptional: an RFC 3339 time. It defaults to the simulator’s clock.

The scans name takes:

  • Mailed
  • In Transit
  • In Local Area
  • Processed for Delivery
  • Delivered
  • Re-Routed
  • Returned to Sender
  • International Exit

A scan moves no other field and sends no webhook.

The bodyThe route returns
has no postcard, a name not in the list or a time that is not RFC 3339422 invalid
has a postcard the simulator does not hold404 not_found
has a postcard that failed or was canceled409 invalid_state, since a canceled piece is never mailed

This is a simulator route, not a Lob one: it takes no Lob key, and under RYSTIC_ADMIN_TOKEN a request needs that value in X-Rystic-Admin-Token, as every control route does. A scenario timeline can call it as one of its steps.

The cancellation window

A postcard stays cancelable through the end of its send_date day, in UTC:

You cancel a postcardDELETE /postcards/{id} returns
on or before its send_date day{"deleted": true, "id": …}
from the next day on, or when it has no send_date422 not_deletable, postcard can no longer be canceled

Differs from Lob: Lob refuses a cancel once send_date has passed, while the simulator checks only the calendar day.

A canceled postcard stays retrievable with deleted true and leaves the lists. Move the clock into the day after the send_date with POST /_rystic/clock to see the window close.

A postcard created with a future send_date, such as the design_studio card to Atlas Moving scheduled for 2026-03-09, stays cancelable until the end of that day.

Seeded outcomes

A seeded postcard can carry any stage you need, a captured rendered or failed outcome included, because POST /_rystic/seed writes the record as given. That replays an outcome deterministically; it is not a rendering clock, and the simulator never moves a postcard from processed to rendered on its own.

Last updated on