advanced 2 min answer

An API platform sends messages on behalf of external developers whose clients retry aggressively after network timeouts. Design idempotency so that a retried request never sends a second message - and state precisely where the guarantee begins and ends.

idempotencyretriesapi-designtwiliodesign
Show the full answer Hide the answer

The core mechanism

The client supplies an idempotency key — a value it generates once per logical operation and reuses on every retry of that operation. The server stores the key with the outcome, and a second request with the same key returns the first result rather than performing the work again.

The three properties that make it correct:

1. The key is written in the same transaction as the effect. If the key is recorded before the work, a crash between the two loses the operation and the retry is rejected as a duplicate — silently dropping a customer's message. If it is recorded after, a crash between them causes a genuine duplicate send. Key and effect must commit atomically, which usually means the key row lives in the same database as the message record.

2. Concurrent retries are serialised, not raced. Aggressive clients retry before the first request finishes. Two in-flight requests with the same key must not both proceed. The standard construction is a unique constraint on the key: the first insert wins and proceeds; the second collides and either waits for the first's outcome or returns "in progress" with a retryable status.

3. The stored response is returned verbatim. Including the original message identifier and status. A retry that returns a different result — even a "correct" one reflecting later state — breaks the caller's mental model.

Where the guarantee begins and ends

This is the part most designs get wrong.

It begins at the API boundary and ends at the point of handover to a carrier or downstream provider. Once the platform has handed a message to an external network, exactly-once is no longer achievable — the provider may itself duplicate, and the platform cannot know.

So the honest contract is: the platform will not create a second send as a result of your retry. It is not: the recipient will receive exactly one message. Publishing the weaker, true guarantee is better engineering than implying the stronger, false one, because customers design around what they are told.

The operational details that matter

  • Key scope. Keys are scoped per account, so one customer cannot collide with another's, and are meaningless across endpoints.
  • Retention. Keys expire — commonly 24 hours. A retry after expiry will duplicate, which must be documented rather than hoped about.
  • Request fingerprinting. If the same key arrives with a different body, that is a client bug, and the correct response is an error rather than silently returning the old result.
  • Keys are required, not optional, on operations with irreversible effects. Making them optional guarantees that the customers who most need them are the ones who omit them.

The interview-grade insight

Idempotency is not a property of an endpoint; it is a property of a conversation between a client and a server across an unreliable network. The server provides the mechanism, but only the client can supply the identity of the logical operation — which is why the key must come from the caller and must survive the caller's own retry loop.