Idempotency keys
Networks fail. If a request times out you cannot tell whether the email was accepted. Send an Idempotency-Key header and retry freely: the same key with the same body returns the original response and sends the email once.
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const { data, error } = await spacepost.emails.send( { from: "Acme <hello@acme.com>", to: "user@example.com", subject: "Receipt", html: "<p>Thanks</p>" }, { idempotencyKey: "receipt-order-1042" },);
console.log(error ?? data);curl -X POST https://api.spacepost.dev/emails \ -H "Authorization: Bearer $SPACEPOST_API_KEY" \ -H "Idempotency-Key: receipt-order-1042" \ -H "Content-Type: application/json" \ -d '{ "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Receipt", "html": "<p>Thanks</p>" }'requests.post( "https://api.spacepost.dev/emails", headers={ "Authorization": f"Bearer {os.environ['SPACEPOST_API_KEY']}", "Idempotency-Key": "receipt-order-1042", }, json={"from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Receipt", "html": "<p>Thanks</p>"},)- Supported on
POST /emailsandPOST /emails/batch. - The key is 1 to 256 characters.
- Keys are scoped to your team and remembered for 24 hours.
- Same key and same request body: the stored response is returned (same status, same body), nothing new is sent.
- Same key and a different body:
409 invalid_idempotent_request. - Same key while the first request is still running:
409 concurrent_idempotent_requests. Wait a moment and retry. - A key outside 1 to 256 characters:
400 invalid_idempotency_key. - Requests that fail are not stored, so you can fix the problem and reuse the key.
“Same body” means the parsed JSON is identical. Changing any field, including scheduled_at: "in 1 hour" (which is evaluated at request time), counts as a different request.
Choosing keys
Section titled “Choosing keys”Derive the key from the business event, not from a random value generated at retry time:
| Good | Bad |
|---|---|
receipt-order-1042 |
crypto.randomUUID() on each attempt |
welcome-user-u_1042 |
Date.now() |
digest-2026-w43-chunk-3 |
The same constant for every email |