Overview
Retry the failures that can succeed later, such as server errors (5xx), and fix the ones that can't, such as validation and authentication errors (4xx). Back off exponentially with jitter, cap the number of attempts, and check status before resending anything that may already have been accepted.
What to retry
Match your retry policy to the error class. Errors come back as an HTTP status code with an ExceptionResponse body (status, error, message, errorCode, requestUri). Client errors mean the request itself is wrong, so retrying it unchanged will fail again. Server errors are usually transient.
| Status | Meaning | Retry? |
|---|---|---|
| 400 | Validation error or missing required field | No, fix the request |
| 401 / 403 | Missing, invalid or unauthorised Bearer token | No, fix the token |
| 500 / 502 / 503 / 504 | Server-side or upstream error | Yes, with backoff |
| Timeout / connection reset | No response received | Only after checking status |
Exponential backoff with jitter
For 5xx responses, wait progressively longer between attempts and add random jitter so a fleet of clients doesn't retry in lockstep. Cap both the delay and the number of attempts, then surface the failure to your own alerting.
async function sendWithRetry(body, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch("https://api.broadnet.me/sms/sendSms", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BROADNET_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (res.ok) return res.text(); // message ID
// 4xx: the request is wrong. Retrying won't help.
if (res.status < 500) throw new Error(await res.text());
// 5xx: back off exponentially, with jitter.
const delay = Math.min(30000, 2 ** attempt * 500) + Math.random() * 250;
await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Exhausted retries");
}Timeouts: check before you resend
The dangerous case is a request that succeeded on the server but whose response never reached you. The API does not take an idempotency key, so an automatic retry after a timeout can send the message twice. Record every attempt, keep the message ID from each 200 response, and check delivery status before you resend. The Idempotency guide shows the pattern.
Handling delivery webhooks
Delivery status is pushed to the webhook URL configured on your account. Reply with a 2xx quickly and do the heavy work asynchronously, and make your handler safe to run twice for the same status update.
Interpreting delivery failures
A message can be accepted by the API yet fail later at the operator, reported as a FAILED or REJECTED status. SMPP integrations receive a numeric error code in the deliver_sm status. Use the SMPP error-code lookup to translate those codes into a cause and a recommended action before deciding whether a resend is worthwhile.