SMS
Status Codes

SMS Status Codes

Every SMS message carries two status fields:

FieldTypeDescription
statusstringHuman-readable lifecycle state (see Message Status)
delivery_codeinteger | nullNumeric code from the gateway (see Delivery Codes)

Use status for application logic. Use delivery_code to pinpoint the exact gateway reason for failures.


Message Status

The status field tracks the full lifecycle of a message through your system and the network.

StatusMeaning
queuedAccepted and waiting to be dispatched to the provider
sentSubmitted to the SMS gateway — awaiting delivery confirmation
deliveredConfirmed delivered to the recipient's handset
failedThe gateway rejected or could not send the message
undeliveredSent to the gateway but could not be delivered to the handset
blockedRecipient is on the DND registry or has blacklisted this sender — do not retry
heldQueued but paused — provider credits exhausted, will resume automatically
cancelledCancelled by the user before dispatch

Delivery Codes

The delivery_code field is set when the gateway provides a definitive outcome. It is null while a message is queued or held, and may remain null for older messages sent before this field was introduced.

Success & In-Progress

CodeNameWhen it appears
100ProcessedMessage delivered to the handset (status: delivered)
101SentMessage submitted to and accepted by the gateway (status: sent)
102QueuedMessage waiting at the gateway before network submission

Rejection Codes

CodeNameMeaningAction
401RiskHoldGateway held the message for risk/fraud reviewWait — usually auto-released
402InvalidSenderIdSender ID not registered or approved for this networkRegister or change the sender ID
403InvalidPhoneNumberDestination number is malformed or not a valid mobile numberValidate and correct the number
404UnsupportedNumberTypeNetwork or number type not supported by this routeRemove number from your list
405InsufficientBalanceGateway account has insufficient creditsTop up provider balance
406UserInBlacklistRecipient has blacklisted this sender IDRemove from future sends
407CouldNotRouteGateway could not find a delivery routeRetry once; if persistent, remove number
409DoNotDisturbRejectionRecipient is registered on the DND registryRemove permanently from all lists

Error Codes

CodeNameMeaningAction
500InternalServerErrorUnexpected error on our endContact support if persistent
501GatewayErrorGateway configuration or credential issueCheck your sender ID setup
502RejectedByGatewayGateway accepted the message but the network returned a hard rejectionCheck number validity

How Codes Map to Status

delivery_codestatus
100delivered
101sent
102queued or held
401held
402409failed or blocked
500502failed or undelivered

Examples

Checking codes in campaign results

curl -X POST https://api.v1.talkntalk.africa/v1/sms/campaigns/messages/ \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "X-Organisation-Id: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Content-Type: application/json" \
  -d '{"campaign_id": "44210c48-5ba9-4784-b434-dd57bf1d2b46"}'
{
  "results": [
    {
      "mobile":         "254712345678",
      "status":         "sent",
      "message_status": "delivered",
      "delivery_code":  100,
      "error":          null
    },
    {
      "mobile":         "254799999999",
      "status":         "failed",
      "message_status": "failed",
      "delivery_code":  403,
      "error":          "Invalid mobile number"
    },
    {
      "mobile":         "254701234567",
      "status":         "sent",
      "message_status": "blocked",
      "delivery_code":  409,
      "error":          "Recipient has blocked promotional messages from this sender."
    }
  ]
}

Filtering failures by code in Python

import requests
 
API_KEY = "tk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ORG_ID  = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
HEADERS = {
    "Authorization":     f"Bearer {API_KEY}",
    "X-Organisation-Id": ORG_ID,
    "Content-Type":      "application/json",
}
 
r    = requests.post(
    "https://api.v1.talkntalk.africa/v1/sms/campaigns/messages/",
    headers=HEADERS,
    json={"campaign_id": "44210c48-5ba9-4784-b434-dd57bf1d2b46", "page_size": 1000},
)
data = r.json()
 
# Numbers to remove permanently (DND or blacklisted)
do_not_contact = [
    m["mobile"] for m in data["results"]
    if m["delivery_code"] in (406, 409)
]
 
# Temporary failures safe to retry
retryable = [
    m["mobile"] for m in data["results"]
    if m["delivery_code"] in (407, 501, 502)
]
 
print("Remove permanently:", do_not_contact)
print("Safe to retry:",      retryable)

Best Practices

  • 406 / 409 — Remove these numbers immediately and permanently. Retrying will result in continued rejection and may get your sender ID flagged.
  • 403 / 404 — Validate your contact list before sending. Use a number validation service if you maintain a large database.
  • 402 — Ensure your sender ID is registered and approved before running campaigns.
  • 405 — Monitor your provider balance proactively; messages held due to depletion (102) resume automatically once topped up.
  • 502 — Usually a transient network issue. Safe to retry once after 15–30 minutes.