Sending
Idempotency
Retry a send safely. An idempotency key guarantees a given request runs at most once, so a dropped connection or a retry loop never turns into a double-send.
Networks fail in the worst places — right after your request reaches Deckle but before the response reaches you. Instead of guessing whether the email went out, attach an idempotency key and retry with confidence: the second call returns the first result rather than sending again.
Why idempotency#
A POST /v1/send that times out is ambiguous. Maybe the email sent and you lost the response; maybe it never arrived. Without a safeguard, retrying risks sending the same message twice, and skipping the retry risks not sending at all.
An idempotency key removes the ambiguity. You generate one stable key per logical send and pass it on every attempt. Deckle records the first request under that key and, for any repeat, replays the original response. That gives you:
- Safe retries. Retry a timed-out or failed request as many times as you need — only the first one actually sends.
- No double-sends. A duplicated webhook, a double-clicked button, or an at-least-once job runner can all fire the same send twice without reaching your recipient twice.
The Idempotency-Key#
Send the key in the Idempotency-Key request header on POST /v1/send. With the SDK, pass idempotencyKey on the send call and the header is set for you.
curl -X POST https://app.getdeckle.com/api/v1/send \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1234" \
-d '{
"from": "Acme <hello@acme.com>",
"to": "jane@example.com",
"subject": "Your receipt",
"html": "<h1>Thanks for your order</h1>"
}'Where it applies
TheIdempotency-Key header is honored on POST /v1/send. Scope it to the transactional sends you retry — receipts, magic links, and alerts.Behavior#
The first request with a given key runs normally and Deckle stores its result. Any later request that reuses the same key skips sending entirely and returns that stored result, with an extra idempotent: true field so you can tell a replay from an original.
{
"id": "email_9f2c...",
"status": "queued",
"messageId": "0100018e..."
}The repeat returns the same id and messageId as the original — it points at the one email that was already created. Nothing new is sent.
| Scenario | What happens |
|---|---|
| First request with a key | Sends normally; result is stored under the key. |
| Repeat with the same key | Returns the first result with idempotent: true; does not resend. |
| Different key (or no key) | Treated as a brand-new send. |
Same key, different body
A key identifies one logical send. If you reuse a key but change the payload, you still get the original result back — Deckle does not re-evaluate the new body. Use a fresh key when you genuinely mean to send something different.Choosing a key#
Derive the key from a stable business identifier that maps one-to-one to the email you intend to send — the order id, the invoice number, the password-reset token. That way every retry of the same logical event naturally produces the same key.
- Do use a deterministic id like
order_1234,invoice_2026_0042, orreset_{userId}_{tokenId}. - Don't use a random value generated fresh on each attempt — a new UUID per retry defeats the purpose, since every call looks unique.
If a single business event legitimately triggers several distinct emails, give each one its own key by adding a suffix — for example order_1234_receipt and order_1234_shipping.
Keys are per project
Idempotency keys are scoped to your project, so keys from different projects never collide. A short, human-readable key also makes retries easy to trace in your own logs.