← Back to the journal

Make Retries Safe Before You Automate More

Design operation identities, retry budgets, and reconciliation paths that prevent an interrupted request from becoming duplicate work.

An automation submits a publishing request. The connection closes before the response arrives. The article may already be live, or the service may never have received the request. Retrying immediately seems helpful until it creates a second publication.

The engineering problem is uncertainty about an effect. A retry policy needs an operation identity, a definition of equivalent requests, and a way to resolve outcomes that remain unknown. Adding a loop around an HTTP client solves none of those questions by itself.

Separate the operation from its attempts

Treat “publish reviewed revision 12 of article 84” as one logical operation. Its first network request and its later retries are attempts to complete that operation. Assign the operation identifier before sending anything, persist it, and reuse it after process restarts.

Do not derive identity only from the payload. Two deliberate actions can have identical content: an editor might intentionally send the same announcement to two different campaigns. Conversely, a transport retry remains the same operation even when a timestamp in the HTTP envelope changes.

AWS describes caller-provided identifiers as a way to communicate repeated intent. It also discusses the conflict where a caller reuses an identifier with different request parameters. A receiving service needs a defined contract for that conflict. AWS Builders’ Library

For your own service, store the identifier with the authenticated account, action type, canonical payload, and outcome. Scope uniqueness to the appropriate account boundary. An unrelated tenant must not discover another tenant’s result by guessing a key.

Freeze the payload you intend to retry

Suppose the first publishing attempt used revision 12. While it was in flight, someone edited the article. A retry that reads the latest draft now publishes revision 13 under an identifier approved for revision 12.

Avoid that ambiguity by persisting the exact accepted payload or an immutable revision reference. Compare subsequent attempts against that record. A conflict should fail visibly instead of silently adopting newer content.

Canonicalization is a contract, not a magic property of JSON. Define field order, encoding, defaults, and schema version before hashing a payload. A digest detects a change; it neither authorizes publication nor proves that publication occurred. The deterministic-patterns guide includes a shared TypeScript serializer and transactional outbox component.

Give each failure a next action

Build a decision table around observable evidence rather than the phrase “request failed.” The exact status meanings depend on the downstream service contract.

Observation Useful interpretation Next action
Local validation rejects the payload No valid request was prepared Correct the input; do not retry unchanged
Credentials are rejected Current identity cannot perform the action Resolve access through the trusted application path
Service reports a retryable limit Capacity is temporarily unavailable Respect its delay guidance within the run budget
Connection times out after transmission The remote effect may have happened Retry with supported idempotency or reconcile
Service returns an operation receipt An operation can be identified Persist and query that receipt according to its contract

An HTTP success can mean “accepted for processing,” not “completed.” Record both states when an API is asynchronous. A local cancellation similarly does not prove that remote work stopped.

Commit an intent before dispatching it

When an operation belongs to a database-backed workflow, commit the state change and an outbox entry in one local transaction. A dispatcher can then discover committed work after a restart.

This prevents a workflow from advancing without a corresponding delivery record. It does not make the remote effect part of the database transaction. A dispatcher can succeed remotely and crash before marking the outbox row delivered.

Carry the stable operation identifier into a downstream idempotency mechanism when one exists. Read its retention, parameter matching, and response replay rules. If the receiver has forgotten the key, your local record cannot force it to remember.

When the service offers no suitable mechanism, investigate an authoritative lookup using a business identifier or remote receipt. If absence cannot be established reliably, stop in an explicit uncertain state and ask the responsible operator to reconcile. Do not convert lack of evidence into permission to repeat the effect.

Bound retries across the whole workflow

Retries consume elapsed time, provider capacity, and sometimes money. Allocate a total attempt budget and a deadline. Include retries performed inside SDKs; otherwise several layers can multiply attempts without the coordinator realizing it.

Space attempts with backoff and jitter instead of immediately sending synchronized requests. AWS explains how retries can amplify load and how jitter helps distribute retry traffic. Timeouts, retries, and backoff with jitter

Record why an attempt was scheduled and the next eligible time. A restarted worker should resume that decision, not reset the budget. Preserve the original request identity when transferring ownership between workers.

Make recovery observable

For the publishing example, the operator needs the article revision, operation identifier, attempt history, last remote receipt, and current uncertainty. A stack trace alone does not answer whether another click is safe.

Before enabling automatic retries, review the interruption points: before dispatch, during transmission, after remote success, and before local acknowledgement. Specify the expected recovery at each boundary. This is a design review checklist, not a claim that those failure cases have been exercised here.

Retain deduplication evidence for the longest legitimate replay window, including delayed jobs and manual retries. Document what happens after expiry. Safe automation makes repetition an intentional protocol rather than a guess about what happened last time.

← Explore the journal