apis & sending · idempotency key email
HTTP Idempotency Keys for API Developers: Handle 400, 409, 422
Learn how API developers use the Idempotency-Key header to make POST and PATCH retries safe, follow IETF draft guidance, and handle 400, 409, and 422.
An email request times out. You still need to know whether it sent before trying again. For POST and PATCH requests, an Idempotency-Key lets the server identify a retry and avoid repeating the operation. Use one key per intended operation, keep it for retries of that request, and check the endpoint’s retry contract. This guide covers that contract and its three responses: 400, 409 and 422.
TL;DR
- Generate a UUIDv4 for each logical operation, reuse it only for retries with the identical payload, and create a new key when the request changes.
- Servers should scope keys to each tenant and route, fingerprint request bodies, and save the original response so retries cannot cross accounts or alter results.
- Return 400 for missing or malformed keys, 409 while the original request remains in progress, and 422 when a reused key carries a different payload.
- Publish a key expiry window and prune records on a schedule; clients cannot safely retry after that window, especially when mobile outages delay requests.
Table of Contents
- What idempotency means for HTTP APIs and which methods need it
- The Idempotency-Key header: syntax, recommended format and server contract
- Client guidance: generating, reusing and sending idempotency keys safely
- Server implementation: storage, fingerprinting, concurrency and correct responses
- Key lifecycle, expiry policy and storage size planning
- Security, validation and common implementation pitfalls
- Testing, observability and operational practices for idempotency
- Practical retry checks for agents
- Author perspective: pragmatic priorities for teams adopting idempotency
- Agent registration with Myagent
- FAQ
- Sources
- Authoritative links and specs to read next
What idempotency means for HTTP APIs and which methods need it
Repeat an idempotent HTTP method and its intended effect stays the same. RFC 7231 includes GET, HEAD, PUT, DELETE and the other safe methods in that definition. POST and PATCH carry no general guarantee. Repeating a POST could create another order or charge. A PATCH that increments a counter could increment it again, while even an idempotent operation can return different responses despite leaving the intended effect unchanged.
A mobile client loses signal mid-request. A load balancer times out before the response arrives, or a user taps “submit” twice. The original operation could already be complete in any of those cases. A missing response alone doesn’t tell you that it failed, which is why retries matter.
The client labels its intended operation with an idempotency key. If the server implements key handling, it can check whether it has handled that identifier already before repeating the side effect. That helps resolve the uncertainty around retries.
The Idempotency-Key header: syntax, recommended format and server contract
Send Idempotency-Key once per request, with one string encoded using structured-header syntax. The IETF HTTP API Working Group draft calls it an Item Structured Header. Keep unrelated metadata out of the token and avoid sending multiple Idempotency-Key headers.
Use a UUIDv4 or another sufficiently random identifier as the key. Randomness makes accidental collisions unlikely. They remain possible. Predictable values, including sequential IDs and timestamps, can make existing keys easier to guess.
On the server side, the contract runs both ways. According to MDN’s reference on the header, an HTTP idempotency key is a client-generated, unique identifier that lets a server recognise and safely handle retries of non-idempotent operations, so the intended effect happens only once no matter how many times the request arrives. Meeting that promise means the server has to:
- Keep key lookups within the scope you’ve defined for identifying one operation.
- Tell clients how long keys remain valid, including whether a retry three days later is covered.
- If you fingerprint requests, store a representation of the payload with the key to detect changes.
Use the key to coordinate execution and keep enough information to recognise a retry. Accepting a header alone won’t do that. A request fingerprint also lets the server detect a changed payload submitted with the same key.
Client guidance: generating, reusing and sending idempotency keys safely
The client side of this is simpler than the server side, but getting it wrong defeats the whole mechanism. A few rules cover almost every case:
- Assign a UUIDv4 or another sufficiently random token to each intended operation, keeping it across HTTP retries.
- Leave personal data, account numbers and other sensitive information out of keys.
- After a timeout, retry the same payload with its existing key within the endpoint’s documented validity window.
- Use a fresh key for a changed payload or a separate operation the user deliberately starts.
Define the operation first. A checkout, for example, could keep one key for a payment attempt through automatic retries. If the user navigates away, resolve the payment’s status before starting another attempt, because leaving the page doesn’t tell you whether it completed. For an email queue submitting messages separately, give each queued message its own key so it can be retried independently.
Keep the key where it survives the failures you’re handling. Browser memory may be enough for retries within the current page. An app restart changes that. A mobile app may need persistent storage for its pending operation, while a backend job can store the key with its payload and retry status in the job record.
Give each intended operation its own key, such as “this checkout attempt”, and keep it while retries remain valid. A long-lived user identifier would reuse the key across purchases. The server could mistake a later purchase for a retry.
Server implementation: storage, fingerprinting, concurrency and correct responses
The server side is where idempotency guarantees actually get enforced, and it needs more than a simple key lookup table. Four pieces make it work reliably.
Combine Idempotency-Key with the authenticated client or tenant and the relevant route. Even identical tokens then refer to separate accounts’ records. Enforce that composite scope on every lookup so a customer’s key can’t retrieve someone else’s result.
Store a fingerprint of the request, often a hash, for comparing payloads. Define the request parts it covers and how you normalise them. Reject a different fingerprint submitted with a reused key, rather than handling it as a retry.
Two requests can both start an operation if they check for a key and insert it in separate, unprotected steps. Reserve it atomically instead. A database uniqueness constraint with suitable transaction handling is one way to ensure that only one request owns execution.
Putting that logic in order, a request arrives with an Idempotency-Key and the server should:
- Reserve the composite key atomically and store its fingerprint. A successful reservation executes the operation and persists its result. For an existing reservation, follow the duplicate-handling steps below.
- Return 409 Conflict for a matching request while the original is still running, without starting another execution.
- Before replaying a result, compare the incoming payload with the stored fingerprint. Return 422 Unprocessable Entity for a mismatch and the stored response for a matching completed request.
- Validate the header before reserving or executing. Return 400 Bad Request if
Idempotency-Keyis missing where required or malformed under the endpoint’s contract.
The fourth piece, persistence, ties it together: the stored record needs the full response, status code, relevant headers and body, not just a flag saying “done”. A retry should get back exactly what the original caller would have received, including things like a Location header on a resource creation, so the client’s own retry logic behaves identically to a first-time success.
Key lifecycle, expiry policy and storage size planning
Document a retention window long enough for your clients’ retries. Http gives background on key handling. Keeping records takes storage, but early deletion loses the information you need to recognise delayed retries. Expiry alone doesn’t turn an old request into a new intended operation.
The practical approach is a TTL-based cache with a background pruning job, rather than a table that only grows. http.dev recommends documenting the exact expiry window for integrators, since a client’s retry guarantee is only as good as the window the server actually honours. If a mobile client queues a retry for an hour after a flight-mode outage and the server’s TTL expired after 30 minutes, the “safe retry” quietly becomes a duplicate request.
Multiply the rate of new keyed operations by the retention window to estimate how many records you’ll keep, allowing headroom for peaks. Multiply by average record size for storage. Protect running operations’ records and schedule pruning to avoid overwhelming live request handling. Monitor record count and storage size, along with retry hits, misses and expired-key lookups you can identify.
Security, validation and common implementation pitfalls
Validate Idempotency-Key as untrusted input before lookup. Use safe database queries and check the caller’s account scope before returning a stored response. Without those checks, guessing a key could expose another caller’s data.
Practical defences worth building in from the start:
- Check key format and length before lookup. Return 400 for malformed keys and for missing keys where required.
- Scope each record and lookup to the authenticated tenant or account to keep cached results inside that boundary.
- Publish your key-generation requirements and flag predictable values. Apparent randomness cannot replace access control.
- Rate-limit key lookups to curb abuse, including guesses at existing keys.
Give cached responses the same protection as live ones. If a fingerprint doesn’t match, return an error that keeps the original payload and result from anyone unauthorised to see them.
Testing, observability and operational practices for idempotency
Test simultaneous requests and crashes as well as retries. Sequential unit tests can exercise individual branches. They can’t establish whether two workers coordinate correctly or recover safely from a failure.
A workable test suite covers:
- After completion, resend the key and payload. Check that the stored status code, body and relevant headers replay without another side effect.
- Change the payload while keeping the key, and verify a 422 response without execution of the changed request.
- Keep the first request running as you send a matching duplicate. Check for one execution and a 409 response to the duplicate.
- After the record’s TTL expires, verify the documented behaviour with another request. Test recovery after execution succeeds but result recording fails too.
Track lookup hits and misses, rejected payload mismatches and in-progress 409 responses, alongside retained record size and expiry counts. Mismatches could mean a client is reusing keys for different operations. Conflicts could mean aggressive retries or slow execution. Investigate those possibilities using the signals rather than assuming a cause.
In staging, send matching requests close together, then introduce delays or failures around reservation, execution and result storage. These checks can expose races missed by manual retries. Keep logs access-controlled, correlate request IDs with the operation’s key and leave sensitive payloads out.
Practical retry checks for agents
When an agent’s email request times out, check the outcome or retry under the endpoint’s contract. Keep the intended message with its payload and key. A retry can then stay tied to that send instead of accidentally starting another.
A repeated email webhook should leave the action it triggers unduplicated. The receiving side needs retry handling too. Explore it in our walkthrough on production-ready email webhooks with idempotency.
Author perspective: pragmatic priorities for teams adopting idempotency
For an existing API, define one operation and its account scope first. Implement atomic reservation with payload comparison and result persistence together to support the retry guarantee. Include 409 handling for simultaneous matching requests, and test the case where execution succeeds but saving the response record fails.
Document the retention window before clients build around it. A 24 hour window gives them a defined boundary if it covers their retry needs. Tell them how to check the original outcome once that window closes.
Keep the policy workable in production with monitoring and scheduled pruning. Watch storage growth, payload mismatches and recovery failures. Alert when those signals need attention.
Agent registration with Myagent
Before choosing an agent’s email tooling, work out where pending sends will be stored and how you’ll resolve uncertain outcomes. See Sendmux for the product overview. Myagent offers the anonymous registration and shared-domain mailbox option described below.
Myagent supports anonymous agent registration and provisions a mailbox on the shared @myagent.mx domain.
FAQ
What does idempotence mean in HTTP?
Idempotence means that calling a method once or many times with the same input leaves the server in the same resulting state. RFC 7231 classifies GET, PUT and DELETE as idempotent for this reason, while POST and PATCH are not guaranteed to behave that way, which is why they need explicit idempotency keys for safe retries.
Can you give a real-life example of idempotency?
A checkout button that a user taps twice because the page hasn’t refreshed yet is the classic case: without an idempotency key, that could create two separate charges for one purchase. With a key attached to the first attempt, the second tap, treated as a retry, returns the original charge result instead of creating a duplicate.
What are the main HTTP methods, and which ones are idempotent?
The core methods are GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. Per RFC 7231, GET, PUT, DELETE, HEAD and OPTIONS are idempotent by definition, while POST and PATCH are not, which is exactly where Idempotency-Key handling earns its place.
How does idempotency apply in system design?
In distributed system design, idempotency keys let services retry failed calls safely across network boundaries, queues and microservices, without a timeout or a duplicate message causing a side effect to run twice. Storing keys with composite lookups and fingerprints, as described in the IETF draft, is what turns “retry until it works” into a safe default rather than a risk.
What should a server return if an idempotency key is reused with a different payload?
A server should return 422 Unprocessable Entity when the same key arrives with a payload that doesn’t match the fingerprint stored from the original request, according to MDN’s reference. This signals a client-side bug or misuse of the key rather than processing the mismatched request as if it were a valid retry.
Sources
- Idempotency-Key header - HTTP | MDN
- draft-ietf-httpapi-idempotency-key-header-06
- RFC 7231, HTTP/1.1 Semantics and Content
- Http
Authoritative links and specs to read next
The IETF HTTP API Working Group’s Idempotency-Key header draft covers syntax, fingerprinting and server obligations in detail. For a quick practical summary of header behaviour and standard error responses, use MDN’s Idempotency-Key reference. RFC 7231 defines which HTTP methods are idempotent by design.
For retry safety beyond HTTP APIs, flowstates.net’s OTP API documentation describes failover and retry logic for one-time-password delivery. It offers a comparison for resilient messaging. Our webhook idempotency walkthrough and SPF, DKIM and DMARC guide cover the implementation details referenced above in more depth.
Recommended
Give an agent its own address
Sendmux is the Email Inbox API for AI Agents.