Idempotency Guide
Safely retry requests without duplicating operations
Use the Idempotency-Key header to safely retry requests without creating duplicates.
Why Idempotency Matters
Network failures happen. Without idempotency:
- Retry a send email request → 2 emails sent
- Retry a campaign send → 2 campaigns sent
With idempotency:
- Retry a send email request → Same response, 1 email sent
How It Works
- Generate a UUID v4 for your operation
- Include it in the
Idempotency-Keyheader - If the request fails, retry with the same key
- Tratto returns the original response (or the same result)
Implementation
Node.js
import { v4 as uuidv4 } from 'uuid';
const idempotencyKey = uuidv4();
const response = await fetch('https://api.tratto.email/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: '[email protected]',
to: '[email protected]',
subject: 'Hello',
text: 'Test',
}),
});
// If request fails, retry with same key
const data = await response.json();Python
import uuid
import requests
idempotency_key = str(uuid.uuid4())
response = requests.post(
'https://api.tratto.email/v1/emails',
headers={
'Authorization': 'Bearer tratto_live_...',
'Idempotency-Key': idempotency_key,
},
json={
'from': '[email protected]',
'to': '[email protected]',
'subject': 'Hello',
'text': 'Test',
},
)
data = response.json()Supported Endpoints
Two endpoints honour Idempotency-Key:
POST /v1/emails: Send emailPOST /v1/api-keys: Create an API key
Sending the header to any other endpoint is harmless but has no effect.
Key Format
Use UUID v4 (RFC 4122):
550e8400-e29b-41d4-a716-446655440000Any unique string up to 256 characters works (longer keys are rejected
with 422), but UUID v4 is recommended. Keys are scoped per environment:
a test-mode replay can never return a live response, and vice versa.
Replay Semantics
| Situation | Result |
|---|---|
| Same key, same payload, within 24h | The original response is returned — nothing is sent again |
| Same key, different payload | 409 IDEMPOTENCY_CONFLICT — a key identifies one request, not a slot |
| Same key, while the first request is still running | 409 IDEMPOTENCY_IN_PROGRESS — retry after it completes |
| The first request failed | The key is released immediately — retry right away with the same key |
Cache Duration and Guarantees
Idempotency keys are cached for 24 hours. After 24 hours, the key expires.
Idempotency is best-effort: it is backed by a fast in-memory store, and if that store is briefly unavailable the request proceeds without idempotency rather than failing your send. Design retries accordingly — treat the guarantee as strong in practice but not absolute.
Best Practices
1. Always Use for Critical Operations
// Always include for email send
const idempotencyKey = uuidv4();2. Generate Fresh Keys
// ✅ New key for each operation
const key1 = uuidv4();
const key2 = uuidv4();
// ❌ Reuse key for different operations
const key = uuidv4();
sendEmail(key); // OK
sendCampaign(key); // Wrong! Different operation3. Retry Strategy with Idempotency
async function sendWithIdempotency(emailData) {
const idempotencyKey = uuidv4();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const response = await fetch('https://api.tratto.email/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Idempotency-Key': idempotencyKey, // Same key across retries
},
body: JSON.stringify(emailData),
});
return await response.json();
} catch (error) {
if (attempt < 2) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(r => setTimeout(r, delay));
} else {
throw error;
}
}
}
}Next: Rate Limits
Edit this page on GitHub
Last updated on