Skip to main content

Troubleshooting: SMS destination and spending blocks

A pre-send destination block rejects an outbound SMS before it is routed to any carrier — no sender, route, or provider change will clear it, and a blind retry replays the same check. The reject comes in one of four codes, and each one belongs to a specific guard layer. This page maps every code to its layer and the fix you own. It is the messaging mirror of Troubleshooting: voice destination and emergency blocks — same triage shape, different gates: where voice guards the caller-id you present, SMS guards the destination prefix, the velocity, and the daily spend.

Decision matrix — which layer fired

Find the error code first, then jump to its section below. Each layer takes one control you own — the tenant blocklist, the send pacing, or a scoped test campaign — so triage ends in a small set of places. If your reject is FRAUD_SHIELD_BLOCKED (403) instead, that is your own tenant-configured Fraud Shield, not these platform baselines — decode it on Troubleshoot FRAUD_SHIELD_BLOCKED.

Destination prefix blocked

422 SMS_BLOCKED_DESTINATION fires when the destination matches a prefix on the platform destination blocklist — the premium-rate and pumped-route prefixes that account for the most expensive SMS abuse — or on your own organization’s prefix blocklist. The same code also fires when the destination is a NANP +1 555-NXX number: carriers silently accept submissions to that reserved fictional range but never deliver, so the platform rejects it at send time instead of paying for an attempt with no delivery receipt. The 422 body names what matched — details.prefix for a blocklist match, details.reason: "nanp_555_test_range" for the fictional range — so branch on it before you touch anything. Fix it by changing the destination, not by working around the gate:
  1. Read details on the 422 body. For a prefix match, compare the prefix against the segment of the destination you sent.
  2. Fictional test range. If reason is nanp_555_test_range, replace the placeholder with a real subscriber number — or use the sandbox magic numbers (+1 500 555 000X), which pass in test mode because no real carrier attempt exists.
  3. Platform blocklist prefix. If the block came from the platform list, the route class is premium-rate or pumping-prone; the gate exists to stop that spend. Reroute to the subscriber’s real number or accept the destination as unsendable — there is no tenant-level toggle that re-opens a platform-blocked prefix.
  4. Your organization’s prefix blocklist. If your own organization added the prefix (an operator throttling a hot route for one tenant, or an automated SMS-pumping scorer marking it), remove that prefix from the organization’s fraud settings to re-open it. Until you do, the block applies to every sender in the organization.
A 422 in this class is not recoverable by retrying — the same prefix check re-fires on every attempt. It is also deliberately sticky: a blocked attempt is recorded as a blocked rejection rather than silently dropped, so your send analytics show the refusal instead of reading it as normal traffic.

Tenant self-service blocklist

422 CHANNEL_BLOCKED_DESTINATION with details.reason: "tenant_blocklist" means the exact destination is on your own outbound blocklist — the operator-managed list at Messages → Blocklist in the dashboard (or the POST /api/v1/messages/blocklist API). The match is verbatim, not a prefix, so the listed number is blocked exactly as entered. This check runs before the prefix layers, so your entry takes precedence over the broader rules — and it applies to every channel at once, not just SMS. Fix it at the blocklist:
  1. Check Messages → Blocklist for the destination; if it is listed, remove the entry and the destination becomes send-eligible immediately.
  2. If the entry is intentional (an abuse case, a bad-actor number), stop targeting that destination — the block is doing exactly what it was written to do.
  3. Audit who blocked it and why: every entry carries a free-text reason and removals are soft, so the history answers “when was this destination un-blocked.”
For the full list surface, read Messages Blocklist. Reach for opt-outs instead of the blocklist when the recipient initiated the stop — opt-out and suppression lists are the consent record regulators expect you to honor.

Per-minute rate window

429 SMS_RATE_LIMITED fires when your account’s outbound SMS exceeds the per-minute cap over a sliding 60-second window. The guard weights the current minute and the tail of the previous minute, so a burst concentrated at a minute rollover trips it even when each single send looked fine. The 429 body names the limit, e.g. { "limit": 60, "window": "1m" }. Fix it by smoothing the send, not by retrying harder:
  • The window slides rather than hard-blocks, so pacing the bulk send clears it without any configuration change.
  • A campaign that holds the cap pinned re-trips on every attempt — distribute the batch over minutes instead of firing it in one burst.
  • Where your organization runs a custom per-minute override, the lower value wins; an active automatic anti-fraud throttle can also clamp the cap down for a limited window, and it reverts on its own.

Daily spend ceiling

429 SMS_DAILY_SPEND_CAP fires when the day’s estimated SMS spend reaches the daily ceiling. The estimate is computed from the number of segments sent at a conservative per-segment price — deliberately pessimistic so the cap engages before real spend does — and it resets at the day boundary. The 429 body names both sides of the check, e.g. { "estimated_spent_cents": 5000, "cap_cents": 5000 }. Fix it by scoping the campaign:
  • Pace or stop today’s sends and retry after the day rolls over — the counter resets.
  • If the reject surprised you, review which campaign consumed the day’s spend; a compromised API key pumping premium destinations is exactly what this ceiling exists to cut off.
  • Where your organization runs a custom daily-spend override, the lower value wins — the ceiling engages at whichever bound you configured.

What to capture before escalating

Work the matrix above first. If a block still misreads — the destination is a real subscriber on a prefix that should be open, or the rate window keeps tripping well under its named limit — open a ticket with:
  1. The full error body — the code, message, and the details object (the prefix, reason, or limit fields above).
  2. The destination number, E.164 normalized.
  3. The send path — single API send, batch send, or campaign.
  4. Your organization ID (Settings → Organization, or organizationId from GET /api/v1/me).

What not to do

  • Do not retry in a loop. Every pre-send gate re-fires on every attempt; only the rate and spend windows clear on their own.
  • Do not reconfigure the sender or route. The block fires before the message reaches any carrier.
  • Do not treat a blocked attempt as delivered traffic. Blocked sends record as rejections in your analytics, so a spike in SMS_BLOCKED_DESTINATION rejections reads as refusals, not deliveries.
  • Do not reclassify a Fraud Shield 403 as one of these codes. FRAUD_SHIELD_BLOCKED is your own tenant policy — decode it on Troubleshoot FRAUD_SHIELD_BLOCKED.

See also