Field notes / Startup & Product
Retries are a product feature: designing duplicate-safe workflows
How stable request identities, atomic persistence, and separate delivery states let a user retry without silently creating duplicate work.
A user presses Submit and the connection drops. They cannot tell whether the server saved the request, so they try again. A useful interface should help them recover without requiring them to understand which network step failed.
The backend needs the same clarity. “The request was saved” and “a notification was delivered” are separate facts. Combining them into one success flag makes recovery harder for both the user and the person operating the system.
Give a logical action a stable identity
For an unchanged retry, keep the same request key. On the server, bind that key to the form and the submitted content. If the action already exists with the same content, return its existing receipt rather than creating a second record.
If the user edits the content, treat it as a new logical action. Reusing a key for a different body should be rejected rather than silently overwriting the original request. The exact policy belongs in the API contract, not only in a button handler.
One request identity, several attempts
A stable key lets an unchanged retry find the same saved record. Changed content needs a new request identity.
Based on the documented workflow · Graphic by Nick JensenRead the diagram as text
Sequence showing two unchanged client attempts with one request key, a database insert-or-read operation, and the same saved receipt returned twice. A stable key lets an unchanged retry find the same saved record. Changed content needs a new request identity.
The sequence is a conceptual summary of the pattern used by this site’s inquiry flow. It is not a claim of exactly-once delivery across every downstream service.
Make persistence safe under concurrency
Checking whether a key exists and then inserting it as separate operations leaves a race. Two requests can both observe that the key is absent. The database needs to enforce the identity while the write happens.
The implementation here uses a deterministic inquiry ID and an atomic database write. A retry checks the stored request fingerprint before returning the existing receipt. The PostgreSQL INSERT documentation describes the conflict-handling primitive behind this class of implementation.
A useful concurrency test sends the same logical action several times and asserts that one record survives. A separate test changes the body while retaining the key and asserts a conflict. Testing only a second request after the first has completed misses the race.
Separate persistence from notification
Once the record exists, the operator can recover from a notification problem without losing the inquiry. Record the delivery outcome separately from the saved business record.
Saved and notified are separate facts
The durable inquiry receipt survives a notification failure. An unknown delivery result needs investigation rather than a blind resend.
Based on the documented workflow · Graphic by Nick JensenRead the diagram as text
State diagram in which a saved inquiry can have a disabled notification, pending notification, successful delivery, definite failure, or unknown delivery result. The durable inquiry receipt survives a notification failure. An unknown delivery result needs investigation rather than a blind resend.
A confirmed success is different from a definite failure. A timeout can mean that the provider accepted the request but the acknowledgement never reached the caller. That outcome is unknown, not automatically safe to retry. Provider-supported idempotency or an investigation may be needed before a resend.
Disabled notifications should also be explicit. Otherwise a deliberately disconnected integration looks like a delivery defect, and someone may try to repair a system that is behaving as configured.
Give the user evidence they can keep
Return a receipt when persistence succeeds. Explain the next step in ordinary language. Do not display a success message merely because the browser finished sending a request, and do not claim an email was delivered when the server only saved a record.
Keep the receipt out of public analytics. A reference can help an operator locate an inquiry, but it does not belong in marketing events or a query string that will be copied between tools.
Test the recovery path, not just the happy path
Useful cases include a duplicate request arriving concurrently, an unchanged retry after a lost response, an edited body with a reused key, a definite provider failure, and an uncertain provider response. Keep test providers and databases isolated from live customer services.
This pattern is useful for inquiries, booking requests, and integration jobs, but the business rule still matters. A second booking with different details may be intentional. Stable identity should prevent an accidental duplicate without preventing a legitimate new action.
The API-First Architecture guide offers a broader view of contracts between systems. If an integration needs a clearer failure and recovery story, discuss a build.
From the idea to the work
Make the workflow useful in a real product.
Bring a product idea, an internal tool, or an AI-assisted workflow that needs a working implementation.