Skip to main content

Troubleshoot a blocked send

A send that returns a blocked-class error was refused by a pre-dispatch compliance gate before it reached the carrier. Each code names the gate family that fired; the fix lives on a different, deeper page that owns the full lifecycle of that gate. This page is the single routing surface between the two — read the code here, then drop one hop into the page that owns the control.
Every control on this page is tenant-owned — you enable the gate, record the consent, widen the window, enable the scrub, or execute the BAA. The one platform-owned control (the US TCPA federal voice window) is called out where it applies. Orbit enforces the gates; approvals always come from the carrier or regulator reviewing your packet.
This page describes Orbit’s platform controls. It is not legal advice. Which laws apply to your traffic, and what posture is adequate, depends on your jurisdiction, your recipients, and what you send. Confirm with qualified counsel.

1. Map the code to the gate

Sort the envelope’s error.code by the gate family. The “Go to” link is the page that owns the gate’s full lifecycle; the rest of this page is the one-paragraph diagnosis for each code. A 422 means a posture change is owed — retrying the same request burns rate-limit budget without moving the state. A 403 on DNC_SYNC_NOT_ENABLED is the opt-in you owe before the pre-flight serves. A 5xx-shaped code is transient and is not on this page — retry with backoff first.

2. SUPPRESSION_BLOCKED — opt-out suppression

What fired. The recipient’s address sits on your suppression list — they replied STOP, unsubscribed through the Preference Center, were recorded as opt_in: false through the Consent API, bounced, or filed a complaint. A suppressed address is dropped before dispatch on every regulated channel regardless of campaign, contact import, or API call. Channel scopes. Each suppression row carries a scope, and the send gate honours the scope that covers the channel you sent on. The full scope set is: all, sms, voice, whatsapp, email, push, telegram, messenger, rcs.
  • all blocks every channel reachable on that address — the scope the Consent API and the Preference Center always write, and the default for phone/WhatsApp rows on a bulk CSV import.
  • email blocks email only — the default for email rows on a bulk CSV import unless a channel column overrides it.
  • A per-channel scope (sms, voice, whatsapp, …) blocks only that channel.
Tenant fix. If the suppression is correct, do not send — honouring it is a legal requirement. If the recipient has since re-opted-in, reverse the suppression through the Consent API (opt_in: true) on Opt-Out & Suppression Lists, which revokes the all plus matching per-channel rows without deleting the audit trail. Re-check the posture. Confirm the address is no longer suppressed:
Escalate. Never owed for a genuine suppression — the fix is the re-opt-in, or do not send. Open a ticket only if a re-opt-in succeeded but the send still returns SUPPRESSION_BLOCKED.
What fired. The send requires a positive consent grant and none is on file for this (contact, channel) pair — the latest consent row is opted_out, expired, pending (an unconfirmed double opt-in), or absent altogether. Marketing sends fail closed by default: outbound is eligible only on a live, unexpired opted_in grant. Tenant fix. Record an opt-in through the Consent API with the channels the send targets, and supply the GDPR evidence fields a consent-based grant requires — lawful_basis, purpose, and consent_text_version (the disclosure version the recipient agreed to). A re-confirmation in a jurisdiction that treats a stale grant as lapsed is an ordinary opt-in POST, optionally with a fresh validity window. The full shape is on Consent Management & Receipts.
Re-check the posture. Read the consent state the send gate reads:
marketing_eligible: true (equivalently status: "opted_in") is the only state the send gate treats as eligible. expired or pending means the send is correctly blocked — re-confirm first. Escalate. Never owed for a genuinely missing or lapsed grant — the fix is the opt-in. Open a ticket only if lookup returns opted_in but the send still returns CONSENT_FALSE.

4. Quiet hours — advisory vs. enforced

What fired. The send landed outside the recipient-local calling or messaging window. The same gate family returns several codes, and the fix depends on which layer owned the block.
  • QUIET_HOURS_BLOCKED — the messaging quiet-hours gate (tenant window) refused the send.
  • TCPA_QUIET_HOURS / TCPA_DIALING_WINDOW_BLOCKED — the voice quiet-hours gate (tenant window) refused the send.
  • TCPA_FEDERAL_DIALING_WINDOW_BLOCKED — the sole platform-owned federal guard refused a US voice send outside 8 AM–9 PM recipient-local. No tenant toggle widens it; the statutory exposure is not yours to waive.
  • TCPA_STATE_DIALING_WINDOW_BLOCKED — a stricter state overlay (Florida’s Sunday ban, Mississippi’s early close, and the OK / LA / AL / AR / WV windows) sits on top of the federal rail on a most-restrictive-wins rule.
  • QUIET_HOURS_TIMEZONE_UNKNOWN / TCPA_TIMEZONE_UNKNOWN — the recipient’s timezone could not be resolved; voice fails closed by default, messaging defaults to fail open unless you set deny.
Advisory vs. enforced. The GET /compliance/quiet-hours/preview endpoint is advisory — it answers “would this send be held, and until when?” without performing it. The send-time gate is enforced — it refuses the dispatch and returns the codes above. A blocked preview is not a hard block from the preview itself; it is a prediction of the enforcement that follows. Schedule at the returned next_allowed_at instead of polling. Tenant fix. For your own tenant window, widen, narrow, or disable it under Settings → Timezone Policy. For the federal and state guards, schedule inside the window — no toggle exists. For an unresolved timezone, set the org-level unknown_timezone_policy knob (skip to allow unresolved recipients, deny to fail closed) or correct the recipient number so it resolves. Re-check the posture. Preview the resolved window per destination before a rollout:
Escalate. A next_allowed_at that contradicts the window you configured, or a real E.164 number that fails to resolve, is the only ticket class — open it with the destination and the meta.request_id from the envelope. The full enforcement forks are on Quiet-Hours Preview.

5. DNC / RND pre-flight

DNC_CONTACT / DNC_NUMBER — 422. The recipient’s contact row carries dnc=true, or the number sits in a scrubbed DNC source. This gate fires only once you opt in (dnc_sync_enabled) — otherwise the DNC scrub is not in the path. The fix is to lift the recipient from the DNC list with a documented reason, or do not send. Sources, freshness, and the check endpoint are on DNC Scrubbing. DNC_SYNC_NOT_ENABLED — 403. The pre-flight GET /dnc/check or POST /dnc/scrub refused because the org never enabled the scrub or the sync feed has not yet synced. Enable dnc_sync_enabled; a synced snapshot retires the gate. RND_CONTACT / RND_REASSIGNED — 422. The number was scrubbed against the FCC Reassigned Numbers Database and matched a reassigned line — the prior owner’s consent does not transfer to the new subscriber. The gate is opt-in per the RND flag; the lifecycle, single-number check, and batch pre-campaign flow are on RND Scrub. Re-check the posture. Run the pre-flight read the send path enforces, without performing a send:
Every served payload carries federal_feeds_synced and intl_feeds_synced so you see the live feed state — when either is true the check serves directly; when both are false the per-org opt-in is required first. Escalate. Never owed for a genuine DNC or RND match — the fix is the list lift or do not send. If the feed stays synced: false past a re-enable, open a ticket with the check response payload.

6. HIPAA_BAA_REQUIRED — the BAA lifecycle 422

What fired. HIPAA mode is opted-in and the send’s audience or content matched PHI, so the workspace refused PHI-bearing traffic until your Business Associate Agreement is executed and in-term. This is the one 422 that is owed by a lifecycle step rather than a per-recipient gate — the BAA state machine gates every PHI send until the agreement is current. Tenant fix. Execute the BAA flow — attest PHI scope, preview, and e-sign by typing the name — on BAA. HIPAA mode itself stays off until the BAA reads executed, so the gate is not just “re-send later”; the agreement is the prerequisite. Re-check the posture.
Anything but executed means no current agreement, and the send stays blocked. The full posture, PHI vocabulary, and the checklist runbook are on HIPAA and HIPAA checklist runbook. Escalate. For a workspace-role block on the BAA flow itself — a 403 on the execute endpoint rather than the 422 on the send — the escalation path is on Compliance send-gate error codes. A 5xx-shaped HIPAA_BAA_GATE_DB_FAIL is transient: retry with backoff, then open a ticket with meta.request_id if it persists.

Worked flows

1. SMS to a recent STOP reply. 422 SUPPRESSION_BLOCKED — the recipient replied STOP last week, which wrote a scope all row. Honour it and do not send, or reverse the suppression through the Consent API on Opt-Out & Suppression Lists if they re-opted in. 2. Marketing send, no consent on file. 422 CONSENT_FALSE — the contact has no consent row for the channel. Record an opt-in through the Consent API with the GDPR evidence fields, re-check with GET /compliance/consent/lookup, then re-send. 3. Voice campaign at 10 PM recipient-local. 422 TCPA_DIALING_WINDOW_BLOCKED (no FEDERAL in the code) — this is your own tenant window. Reschedule at the next_allowed_at from a preview, or widen the window. A code carrying FEDERAL is the one platform-owned guard: schedule, no toggle. 4. DNC scrub gated before a campaign. 403 DNC_SYNC_NOT_ENABLED on GET /dnc/check — enable dnc_sync_enabled in compliance settings; once a feed snapshot is synced the check serves directly. A subsequent DNC_CONTACT on the send means a matched number: lift with a documented reason or exclude it. 5. PHI campaign, BAA pending. 422 HIPAA_BAA_REQUIRED — the campaign audience is PHI-adjacent and the BAA reads pending. Execute the BAA flow, re-read it with GET /compliance/baa, then relaunch.

When nothing fits

If a 422 persists past the posture fix above, or the code does not appear in the table, open a ticket carrying the code, the HTTP status, the meta.request_id from the envelope, the referencing asset id (sender id / campaign id / contact id), and the destination country. The full send-gate error-code routing surface — including the sender-identity, regional, policy-scan, and compliance-profile codes that are not blocked-class — is on Compliance send-gate error codes.

Cross-reference map

Each per-code topic here links out to the page that owns the gate’s full lifecycle:
  • Opt-Out & Suppression Lists — suppression entry points, channel scopes, bulk CSV import, and the re-opt-in.
  • Consent Management & Receipts — the consent record, the lookup status states, double opt-in, and the GDPR evidence fields.
  • Quiet-Hours Preview — advisory vs. enforced, the voice enforcement forks, and the next_allowed_at semantics.
  • DNC Scrubbing — sources, freshness, the check endpoint, and the fail-open caveat.
  • RND Scrub — the FCC Reassigned Numbers Database, the opt-in flag, and the batch pre-campaign flow.
  • HIPAA and BAA — the BAA state machine, the e-sign flow, and the PHI vocabulary.
  • Send Gates — the canonical pre-send gate inventory.