← All posts

New: HonkIO now refuses dead and fictional numbers before they cost you anything

As of September 1, a send to a 555 number, a carrier test code or a number three consecutive carrier failures have already declared dead is refused with a 422 instead of billed. What changed, why, and what it means for your retry logic.

Until this week, HonkIO treated every well-formed Canadian mobile number as a number worth trying. If you asked us to text +1 613 555 0100, we handed it to the carrier, the carrier told us nobody lives there, and you paid three cents to learn that. Ask again and you paid again.

That was defensible, in a narrow way. The carrier does charge us for the attempt, and we pass carrier costs through rather than absorb them. But it produced bills nobody could explain and did nothing to stop a script in a loop. So on September 1 we shipped two changes that refuse those sends before they cost anyone anything.

Change one: exchanges nobody can be behind

A North American phone number has three parts: an area code, an exchange, and a line. Some exchanges are reserved by the numbering plan and can never be assigned to a subscriber. The whole 555 block is the famous one, kept for film, television and directory services. The N11 codes such as 411 and 911 are services, not people. 958 and 959 are carrier test codes. Exchanges starting with 0 or 1 are not valid exchanges at all.

HonkIO now refuses a send to any of these with 422 RESERVED_DESTINATION before it reaches the carrier. No message row is created, nothing is charged, and the response names the exchange and says why:

http
HTTP/1.1 422 Unprocessable Entity

{
  "code": "RESERVED_DESTINATION",
  "message": "The destination is in a reserved exchange (555-XXXX, N11 codes, carrier test codes) that no subscriber can be behind. Nothing was sent or charged; the details name the exchange.",
  "statusCode": 422,
  "details": {
    "part": "NXX",
    "code": "555",
    "reason": "Exchange 555 is reserved for fictional use and information services and cannot receive SMS. Nothing was sent or charged."
  }
}

This applies in test mode as well as live mode, which is the part most likely to surprise you. A lot of integration tests reach for 555 numbers precisely because they are fictional. They will now fail fast with the code above. Use a test key instead: a test key never reaches a carrier and never bills, whatever number you point it at.

Reserved area codes, such as 555 or 911 in the first three digits, were already refused as non-Canadian numbers and still are. Nothing changed there.

Change two: numbers the network has already given up on

The second change is a list HonkIO now keeps of numbers that are, as far as the carriers are concerned, dead. Nobody curates it. It builds itself from delivery receipts. Every delivery receipt is a verdict from the carrier: delivered, failed or undelivered. When a number collects three consecutive failures within 30 days, from any HonkIO customer, it goes on the list for 90 days.

While it is listed, a send to it is refused with 422 UNDELIVERABLE_NUMBER. Again, no row and no charge. The response tells you how many failures put it there, when that happened and when we will try it again:

http
HTTP/1.1 422 Unprocessable Entity

{
  "code": "UNDELIVERABLE_NUMBER",
  "message": "This number is listed as undeliverable, so the send was refused without charge. The details show why it was listed and when it will be retried automatically.",
  "statusCode": 422,
  "details": {
    "failures": 3,
    "listed_at": "2026-09-02T14:07:11.000Z",
    "expires_at": "2026-12-01T14:07:11.000Z",
    "last_error_code": "30003"
  }
}

Ninety days is roughly how long Canadian carriers hold a disconnected number before reassigning it to someone new, so a listing expires at about the moment the number might have an owner again. After that we try it once more and start the count from zero.

The list is shared across the whole platform, because a dead number is dead for everyone, but it is anonymous. You are told the number is listed; you are never told who else tried it.

What does not put a number on the list

  • A carrier outage. If the carrier cannot be reached or answers with a server error, the send is refused with 503 CARRIER_UNAVAILABLE, nothing is charged, and the attempt is not a verdict about the number.
  • An empty balance or a billing error on your side. Those are about your account, not the recipient.
  • A message that is still pending. Only a final receipt counts.
  • A delivery in between. One successful delivery resets the streak, so a number that is merely flaky takes longer to list than one that is gone.
  • Verification codes. Those go to your own phone, which you just typed, so they skip the list entirely.
  • Test-mode sends. Test mode never consults the list.

Landlines and VoIP numbers get their own free refusal, 422 NOT_A_MOBILE_NUMBER, also new since September 1. Our carrier profile is mobile-only, so a number that cannot receive SMS is turned away before anything is sent.

What it means for your code

All three refusals are final for the number you sent to. Retrying is the one thing that will not help, and a retry loop against a listed number now also meets the per-recipient cap of 30 messages an hour and 100 a day, which answers 429 RECIPIENT_RATE_LIMITED. So:

  • Read the code field, not just the status. A 422 with RESERVED_DESTINATION, UNDELIVERABLE_NUMBER or NOT_A_MOBILE_NUMBER means stop, mark the contact and move on.
  • Keep 555 numbers out of your fixtures. Point a test key at any well-formed number instead.
  • When you do retry, on a timeout or a 503, send an Idempotency-Key header. A retry that carries one can never become a second charge.
  • Undelivered messages that the carrier accepted are still billed. That has not changed, because the carrier still charges us. What has changed is that we stop handing the carrier numbers it has already refused three times.

The trade-off

The list counts carrier failures without asking why the carrier failed. Most failures are dead numbers, but some are content filtering, and a legitimate number that receives three filtered messages in a month will be listed alongside the dead ones. We chose to accept that rather than guess at carrier error codes, which are not reliable enough to sort by. Two safety valves exist: the threshold and the window are tunable, and our staff can remove a number early if you tell us it is real. Once we have a few months of data we will revisit whether three is the right number.

The full rules, including every code and the fields each carries, are in the sending limits section of the docs. If one of these refusals ever looks wrong to you, tell us. Every entry on the list records the carrier code that put it there, so we can go through it with you.