Idempotency keys: retry sends safely
Retry a send request safely, without the risk of sending the same email twice.
#How to use it
A send request can time out after Postal has already accepted the email. Retrying carelessly sends the email twice. To avoid that, send a unique identifier for the operation in the Idempotency-Key header:
cURL
curl -X POST https://api.postal.so/emails \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Idempotency-Key: order-10482-shipped" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme Store <[email protected]>",
"to": ["[email protected]"],
"subject": "Your order #10482 is out for delivery",
"html": "<p>Order <b>#10482</b> is on its way.</p>"
}'
Node.js
const response = await fetch('https://api.postal.so/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10482-shipped',
},
body: JSON.stringify({
from: 'Acme Store <[email protected]>',
to: ['[email protected]'],
subject: 'Your order #10482 is out for delivery',
html: '<p>Order <b>#10482</b> is on its way.</p>',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails",
headers={
"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}",
"Idempotency-Key": "order-10482-shipped",
},
json={
"from": "Acme Store <[email protected]>",
"to": ["[email protected]"],
"subject": "Your order #10482 is out for delivery",
"html": "<p>Order <b>#10482</b> is on its way.</p>",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('emails', [
'headers' => [
'Authorization' => 'Bearer '.getenv('POSTAL_API_KEY'),
'Idempotency-Key' => 'order-10482-shipped',
],
'json' => [
'from' => 'Acme Store <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Your order #10482 is out for delivery',
'html' => '<p>Order <b>#10482</b> is on its way.</p>',
],
]);
$data = json_decode($response->getBody(), true);
If the same key arrives again within 24 hours, with the same body, Postal does not send again: it returns the stored response of the first request, with the same status and the same ID.
#Rules
- It applies to
POST /emails,POST /emails/batch,POST /events(a repeated event does not start the automation again),POST /contacts/batchandPOST /segments/merge. On other endpoints the header is ignored. - The key must be 1 to 256 characters long; otherwise the response is
400 invalid_idempotency_key. - Keys are scoped to the team: two API keys of the same team share the same idempotency keys.
- Only successful responses (
2xx) are stored. If the first attempt failed with422or429, fix the problem and retry with the same key. - Same key with a different body, method or path:
409 invalid_idempotent_request. - Same key while the first request is still in progress:
409 concurrent_idempotent_requests. Wait a moment and retry.
#Choosing the key
Use something that identifies the reason for the send, not the attempt: order-10482-shipped, password-reset-8f2a91 or a UUID generated and saved together with the order. Generating a new UUID on every attempt defeats the protection.