Skip to content

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.

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 }); // disable

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.

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/node
await frontmail.send({ templateId: 'tpl_receipt', params, idempotencyKey: `receipt-${order.id}` });
// @frontmail/browser – options are the last argument
await 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:

Terminal window
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.

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.