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

  1. Generate a UUID v4 for your operation
  2. Include it in the Idempotency-Key header
  3. If the request fails, retry with the same key
  4. 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 email
  • POST /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-446655440000

Any 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

SituationResult
Same key, same payload, within 24hThe original response is returned — nothing is sent again
Same key, different payload409 IDEMPOTENCY_CONFLICT — a key identifies one request, not a slot
Same key, while the first request is still running409 IDEMPOTENCY_IN_PROGRESS — retry after it completes
The first request failedThe 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 operation

3. 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