Retries and idempotency
Networks fail – especially on mobile. All Frontmail SDKs retry failed requests automatically, and they do it safely: a retried send never results in a duplicate email or a double charge.
What is retried
Section titled “What is retried”| Situation | Retried? |
|---|---|
| Network error (offline, DNS, connection reset) | ✓ |
Timeout (timeoutMs, default 15 s per attempt) |
✓ |
5xx responses |
✓ |
429 rate_limited |
✓ – waits for Retry-After (if it is 60 s or less) |
4xx validation, auth, origin, CAPTCHA errors |
✗ – retrying wouldn’t help |
Client-side blocks (blockHeadless, blockList, limitRate) |
✗ |
Default policy: 3 retries after the first attempt, exponential backoff starting at 300 ms
with full jitter, at most 10 s between attempts. Configure it with the retry option:
init({ publicKey: 'pk_4f2a…', retry: { retries: 5, baseDelayMs: 500, maxDelayMs: 20_000 } });init({ publicKey: 'pk_4f2a…', retry: false }); // disableIdempotency keys
Section titled “Idempotency keys”Every logical send() gets a random UUID as its Idempotency-Key, and all retries of that send
reuse the same key. On the server, Frontmail remembers keys for 24 hours:
- the same key with the same body returns the original response (same
message_id) without sending again, - the same key with a different body fails with
409 idempotency_conflict.
So if the first attempt actually reached Frontmail but the response got lost, the retry just gets the original result.
Your own keys
Section titled “Your own keys”Pass an explicit key when the trigger itself can repeat – a webhook delivered twice, a job that restarts, a user double-submitting a form in two tabs:
// @frontmail/nodeawait frontmail.send({ templateId: 'tpl_receipt', params, idempotencyKey: `receipt-${order.id}` });
// @frontmail/browser – options are the last argumentawait send('svc_01J9…', 'tpl_receipt', params, { idempotencyKey: `receipt-${order.id}` });Keys can be up to 255 characters. Use a new key for every distinct email – reusing one for a different email returns an error instead of sending it.
With the REST API, send the header yourself:
curl https://api.frontmail.dev/v1/send \ -H 'Idempotency-Key: receipt-A-1042' \ -H 'Authorization: Bearer sk_…' \ -H 'Content-Type: application/json' \ -d '{ "template_id": "tpl_receipt", "template_params": { "order_id": "A-1042" } }'For batch sends, set idempotencyKey (idempotency_key in the REST API) per message.
After the API accepted the message
Section titled “After the API accepted the message”Once the API returned 202, delivery retries are Frontmail’s job: the sender retries transient
provider errors with backoff, fails over to the fallback service on auth/permanent errors and holds
messages when a service is broken. Your code doesn’t need to do anything – check the result with
getStatus() or in History if you need to.