PristineSend
Get started
API Reference

Idempotent requests

Network calls can fail after the server has already done the work, leaving you unsure whether to retry. Idempotency keys make a retry safe: send the same key and PristineSend guarantees the operation runs at most once, replaying the original result instead of sending again.

Overview

Idempotency is opt-in. Add an Idempotency-Key request header and the call becomes idempotent; omit it and behavior is unchanged. It applies to the two side-effecting send endpoints:

POST/api/v1/send
POST/api/v1/send/batch

The read endpoints (Emails, Events, and so on) are naturally safe to retry and ignore the header. Creating a contact already has natural idempotency — a duplicate email returns 409 conflict — so it does not take a key today; key support there is a possible future extension.

The idempotency key

You generate the key — a UUID v4 is a good default. It must be 1 to 255 characters. Keys are scoped per workspace and remembered for a 24-hour window; after that the key expires and reusing it starts a fresh operation.

HeaderValue
Idempotency-KeyA unique string of 1–255 characters you choose per logical request (e.g. a UUID). Optional — present means idempotent.

Use a new key for each distinct operation, and the same key for every retry of that operation. Reusing a key for genuinely different content is an error (see Errors).

Replays

When you retry with the same key and the same body, PristineSend returns the byte-identical stored response from the first call — same status code, same body. The replay carries an Idempotency-Replayed: true response header so you can tell a replay from a fresh execution.

A replay is a pure read of the stored outcome: it does not re-send the email, re-meter the send, or re-run any sending gate. That is the whole point — one logical request, one set of side effects, no matter how many times you retry.

HTTP/1.1 200 OK
Idempotency"color:#ff7b72">-Replayed: true
X"color:#ff7b72">-Request"color:#ff7b72">-Id: req_8f3c9a1b2d4e5f6071829304
Content"color:#ff7b72">-Type: application/json

Batch requests

For Batch send, the key covers the whole call — the entire batch is one idempotent unit. A retry with the same key replays the identical 207 response array (every item's result in order), and re-sends nothing. There are no per-item keys: you cannot make individual entries within a batch idempotent on their own. If you need per-item idempotency, send those items as separate /api/v1/send calls, each with its own key.

Errors

Three error codes are specific to idempotency. They use the standard error envelope (see Error codes):

StatuscodeWhen
400idempotency_key_invalidThe Idempotency-Key header was sent but empty, or longer than 255 characters.
409idempotency_key_in_progressA concurrent request with the same key is still processing. Retry shortly.
422idempotency_key_reusedThe same key was sent with a different request body. The key is valid, but the params conflict.

The distinction between 409 and 422 matters. idempotency_key_in_progress means the key is fine but a twin request is still running — back off and retry, and you will get the stored result. idempotency_key_reused means the key was already spent on a different body — pick a new key, because reusing one for new content is almost always a client bug. Example 422 body:

{
  "error": {
    "code": "idempotency_key_reused",
    "message": "This Idempotency-Key was already used with a different request body.",
    "request_id": "req_8f3c9a1b2d4e5f6071829304"
  }
}

Transient faults are not cached

Only 2xx and 4xx outcomes are stored and replayed. A 5xx response — internal_error, provider_error, or a metering 503 service_unavailable — is treated as transient and releases the key. A retry through the same key then re-processes cleanly rather than replaying the failure. This is why you should keep retrying through the same key on a 5xx instead of minting a new one.

Crash recovery

If a request claims a key and then the server dies mid-flight, the key does not stay locked until it expires. A claimed-but-unfinished key auto-recovers: a retry after about 90 seconds re-claims the key and processes the request normally. In the meantime, retries see 409 idempotency_key_in_progress — so a brief back-off and retry is the right response there too.

Code examples

Generate one key per logical request and reuse it across every retry of that request:

"color:#79c0ff">curl "color:#ff7b72">-X POST https://pristinesend.com/api/v1/send \
  "color:#ff7b72">-H "Authorization: Bearer ps_live_YOUR_API_KEY" \
  "color:#ff7b72">-H "Content">-Type: application/json" \
  "color:#ff7b72">-H "Idempotency">-Key: 8f3c9a1b-2d4e-5f60-7182-930400000000" \
  "color:#ff7b72">-d "color:#a5d6ff">'{
    "to": "recipient@example.com",
    "from": "orders@yourdomain.com",
    "subject": "Your order has shipped!",
    "html": "<p>It is on the way.</p>"
  }'

See Error codes for the canonical error envelope and the complete list of codes.