Duplicate execution
The first two cases only touch the step that was cut off: the steps saved before it return their saved results.
Idempotency keys
Many services accept a key with each request, and ignore a second request with a key they’ve already seen. Payment and email providers commonly do. Build the key from your data, so every attempt sends the same one:nelkap/workflows/cancel-order.ts
A provider keeps a key for a limited time, at least 24 hours at Stripe. That covers a step that resumes after a crash, but not always a replay made days later.
Upserts
In your own database, give each row a key that says what it is, like a contact’s email address, and insert or update in one statement:Generated IDs
When your code makes up an ID for something it creates, draw the ID in a step of its own, then create the thing with it:invoice step uses the same one. createInvoice can then insert with ON CONFLICT (id) DO NOTHING, or pass the ID as an idempotency key.
Existence checks
When a service takes no key, look for what a first attempt left before you act:Runs and events in steps
Code outside steps runs again on every call of the run. Arun or an emit placed there would start a new run, or emit a new event, each time the run resumes. Place them in a step, so they happen once: