When work can happen twice
The first two cases only touch the step that was cut off: the steps saved before it give back their results.
Pass an idempotency key
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:Write with an upsert
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:Draw IDs in their own step
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.
Check before you act
When a service takes no key, look for what a first attempt left before you act:Start runs and emit 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 picks up. Place them in a step, so they happen once: