MyComm docs
Menu

Sending

Idempotency and retries

How to retry a request without sending twice, which statuses are safe to retry, and how long to wait.

A network can drop an answer after the server has done the work. If you retry a send blindly, the customer gets two messages. Two mechanisms prevent that; use both.

The Idempotency-Key header#

Send Idempotency-Key on every POST you might repeat. The key is 1 to 255 printable ASCII characters; a UUID, or an id of your own job, is best.

curl -sS -X POST "$MYCOMM_URL/v1/messages" \
  -H "Authorization: Bearer $MYCOMM_API_KEY" \
  -H "Idempotency-Key: order-1094-ready-1" \
  -H "Content-Type: application/json" \
  -d '{"to": "+923001234567", "body": "Your order #1094 is ready.", "category": "transactional"}'
What you do What happens
First request with a key It runs. A success, or a rejection caused by the request (a 4xx), is stored for 24 hours.
The same request again, same key You get the stored answer with the header Idempotent-Replayed: true. Nothing runs twice.
The same key, a different request 409 IDEMPOTENCY_KEY_REUSED. A key names one request.
The same key while the first request is still running 409 IDEMPOTENCY_IN_PROGRESS. Wait a second and repeat.
A request that failed with a 5xx Nothing is stored; repeating it with the same key runs it again.
A key that is empty, too long or not printable ASCII 400 INVALID_HEADER, with details.reason. It is checked even on a dry run.

Keys are scoped to your account: another account can use the same string. A dry run ("dry_run": true) never uses up its key.

Make the key part of your job record, not something you generate at send time: when the job is retried by your own queue, it must be the same key.

reference_id#

reference_id is your own id for a message, unique per account forever. If you send a reference_id that already exists without a matching Idempotency-Key, the answer is 409 REFERENCE_ID_EXISTS and details.message holds the message that already exists; nothing new is created. Treat that as "already sent". It protects you when the idempotency window (24 hours) has passed, but it does not replace the key, because it cannot tell a retry from a mistake.

Which failures to retry#

Outcome Retry?
Connection error or timeout (you do not know what happened) Yes, with the same Idempotency-Key
5xx Yes, with the same key, with exponential backoff and jitter
429 RATE_LIMITED Yes, after Retry-After seconds
409 IDEMPOTENCY_IN_PROGRESS Yes, after a second
503 SERVICE_UNAVAILABLE Yes, after a short wait, with the same key
Any other 4xx No. The request is wrong or refused; repeating it unchanged gives the same answer. Fix it first.

Back off: wait 1 s, 2 s, 4 s and so on up to about a minute, add random jitter, and stop after a limit you choose. Do not retry a 422 in a loop: RECIPIENT_OPTED_OUT, INVALID_NUMBER and the like will not change by themselves.

Reads and other methods#

GET, PUT, PATCH and DELETE are safe to repeat: they have the same effect when repeated. PUT /v1/contacts/by-identity is the idempotent way to keep contacts in sync. Only POST needs the header.

Retrying a message that failed#

That is a different thing: a message that was accepted and then failed. The platform retries the failures that are worth retrying; for the others, POST /v1/messages/{id}/retry queues the same message again. See the message lifecycle.