Idempotency
Safely retry API requests without duplicate sends
Idempotency ensures that retrying a failed API request does not accidentally send duplicate emails. Postkit supports idempotent requests via the Idempotency-Key header.
What is idempotency?
A request is idempotent if making it multiple times has the same effect as making it once. This is critical for email sending, where a duplicate request can result in a real person receiving the same message twice -- an outcome that is difficult to undo and erodes trust.
Network failures, timeouts, and client crashes can all cause situations where you are unsure whether a request succeeded. Without idempotency, the safe choice is to not retry, which risks losing important transactional emails. With idempotency, you can always retry safely.
Using the Idempotency-Key header
To make a request idempotent, include the Idempotency-Key header with a unique string that identifies the operation:
curl -X POST https://api.postkit.eu/v1/emails \
-H "Authorization: Bearer pk_live_abc123..." \
-H "Idempotency-Key: order-confirmation-4821" \
-H "Content-Type: application/json" \
-d '{
"from": "orders@yourdomain.com",
"to": ["customer@example.com"],
"subject": "Order #4821 confirmed",
"html": "<p>Your order has been confirmed.</p>"
}'The Idempotency-Key header has the following rules:
- Maximum length: 255 characters
- Cache duration: 48 hours from first use
- Scope: Unique per organization -- different organizations can use the same key independently
- Required: No, but strongly recommended for all email sending operations
How it works
When Postkit receives a request with an Idempotency-Key:
-
First request: The API processes the request normally, stores the response in Redis, and returns the result. The key-to-response mapping is cached for 48 hours.
-
Subsequent requests with the same key: The API recognizes the key, skips processing, and returns the stored response from the first request. No duplicate email is sent.
-
After 48 hours: The cached response expires and the key can be reused. A request with the same key after expiration will be treated as a new request.
This means that if your client crashes after sending a request but before receiving the response, you can safely retry with the same idempotency key. If the original request succeeded, you will get the same success response back. If the original request never reached the server, it will be processed normally.
Best practices
Use deterministic keys
Build your idempotency keys from data that naturally identifies the operation, rather than generating random UUIDs:
order-confirmation-{order_id} -- ties to a specific order
password-reset-{user_id}-{timestamp} -- ties to a specific reset request
welcome-{user_id} -- ties to a specific user signupDeterministic keys are easier to debug and naturally prevent duplicates even across separate retry attempts that may not share state.
Include the operation type
If you use the same entity ID across different email types, include the operation name in the key to avoid collisions:
order-confirmation-4821 -- confirmation email
order-shipped-4821 -- shipping notification
order-delivered-4821 -- delivery notificationWithout the operation prefix, all three emails for order 4821 would share the same key, and only the first would be sent.
Always use keys for sending operations
While the Idempotency-Key header is optional, you should always include it for POST /v1/emails and POST /v1/emails/batch requests. The cost of sending a duplicate email is much higher than the cost of generating a key.
For read-only operations like GET requests, idempotency keys are unnecessary -- those operations are already naturally idempotent.
What happens during failures
| Scenario | What happens on retry |
|---|---|
| Original request never reached the server (network error) | Retry is processed as a new request. The email is sent. |
| Original request succeeded, but you did not receive the response | Retry returns the cached success response. No duplicate email. |
| Original request failed with a 4xx error | Retry returns the cached error response. Fix the issue and use a new key. |
| Original request failed with a 5xx error | Retry may re-execute the request, since server errors indicate the operation may not have completed. |
If a request fails with a 4xx error (like a validation error), the error response is cached under that key. To retry with corrected data, you must use a new idempotency key.
What's next?
- Sending Email guide -- complete guide to all email sending features
- Rate Limits & Quotas -- understand API rate limits and pagination