Guides
Idempotency
A retried submission must never send twice. The Idempotency-Key header is how SMSend guarantees that.
The key is required, not optional
Submitting a batch without an Idempotency-Key is rejected. Making it optional would mean the default path is the unsafe one, and the failure mode — billing a customer twice because a response was lost on the way back — is not one worth leaving to chance.
The header
Choose a value that is unique to the submission, and derive it from something in your own system rather than generating it randomly. An order id works; a fresh UUID on every attempt defeats the whole mechanism, because the retry then looks like a new request.
- Maximum key length
- 255
- Replay window
- 24 h
- Stale in-progress claim taken over after
- 300 s
- Retry-After on an in-flight conflict
- 5 s
- Uniqueness is scoped to
- api_key_id + key
The three outcomes
What happens on a repeat depends on whether the first request finished, and on whether the body is identical.
Replaying a completed request
Send the same key with the same body inside the 24-hour window and you get the original response back — the stored bytes, not a freshly computed answer — along with a header telling you it was a replay. Nothing is sent a second time and nothing is billed again.
Reusing a key for a different request
The same key with a different body is a conflict, not a replay. This catches the bug where a key is derived from something insufficiently specific and two genuinely different batches collide.
Retrying while the first attempt is still running
If the original request has not finished, the retry is refused with a retryable error and a Retry-After. Wait and try again — the claim is released as soon as the first attempt completes.
How the body is compared
The fingerprint is a hash of the method, the path, and the payload with object keys recursively sorted. Sorting the keys means a client whose JSON library serialises fields in a different order on the retry still produces the same fingerprint.
Array order is significant and is not normalised. A recipients list in a different order is a genuinely different request and will be rejected as a reuse — which is correct, because it would send to a different set of people in a different order.
A refused submission releases the key
If a batch is refused — for insufficient funds, say — the idempotency record is deleted rather than kept. That means you can top up the wallet and retry the identical request with the identical key, which is exactly what a caller wants and what a naive implementation would prevent for 24 hours.
A claim left in progress for more than five minutes is taken over by the next attempt, so a worker that crashed mid-request cannot poison a key until the window expires.