Skip to main content

Troubleshooting: messaging pre-send gates

Before any SMS, MMS, or conversational-channel send hands off to the carrier, Orbit runs a short chain of hard pre-send gates. A send rejected at this stage never reached the softswitch — the structured 422/403 envelope names the gate, and each gate defends a different control. This page is the routing card for the four hardest and most visible of those gates: the org-side blocked-contact list, the quiet-hours window, the conversational channel’s session window, and the carrier deactivation scrub. Definitions live in the Error Code Reference; this page only owns the triage path. Distinguish these hard rejects from the campaign-level pre-send verdicts (blocked vs warned) covered on voice pre-send gate chain: the codes below stop a single send at the message or conversation layer, before any campaign list stage runs.

Section routing — which gate fired

CONTACT_BLOCKED — recipient on the blocked list

Cause. The contact matched by the send’s to address (phone or email) is flagged blocked at the organization level, so every outbound send to that contact is rejected before routing starts. Blocking is one of your own deliverability controls — typically applied after abuse, a complaint, or a manual block action in the dashboard. Read the surface. The 403 envelope carries details.contact_id, so you can open the exact row. Fix. Unblock the contact under Settings → Contacts → Blocked and retry the send. If the block was deliberate, remove the contact from the campaign audience instead — the gate keeps rejecting until either the row is unblocked or the send stops targeting it. Bulk audiences hit this one contact at a time: a blocked member fails its own send while the rest of the batch proceeds. Do not confuse with the suppression model — suppression scope covers opt-outs that filter imports, and pending erasure gates covers CONTACT_ERASURE_PENDING. A blocked contact is the org-side hard list, distinct from both.

QUIET_HOURS_BLOCKED / TCPA_QUIET_HOURS — the send-time window

Cause. The send landed outside the recipient-local allowed window. For non-voice channels (SMS / MMS / WhatsApp / RCS / Email / Messenger / LINE / and the rest) the QUIET_HOURS_BLOCKED gate checks your own quiet-hours posture under Settings → Compliance → Quiet hours — a tenant-owned control. Voice carries the TCPA-family codes (TCPA_QUIET_HOURS, plus the hard federal / state window family) instead. When the recipient timezone cannot be resolved, the non-voice equivalent QUIET_HOURS_TIMEZONE_UNKNOWN follows your unknown_timezone_policy (allow or deny), unlike the voice-side family which fails closed. Fix. Two ways to clear, both tenant-owned for the non-voice gate:
  1. Reschedule at the next_allowed_at timestamp returned in the error envelope — the DST-safe absolute instant the window reopens.
  2. Relax the posture — widen or disable the quiet-hours window in Settings → Compliance → Quiet hours, then retry.
Do not re-tread. The window semantics and the voice-side federal / state split are already covered: the TCPA-window troubleshoot page and the compliance FAQ quiet-hours section own them. What this page adds is only the routing pass: for a non-voice send, the window is yours to adjust; preview a rollout’s resolved window per destination with GET /compliance/quiet-hours/preview before launching.

OUTSIDE_SESSION_WINDOW — reply outside the channel window

Cause. A free-form reply went out on a session-based channel — WhatsApp’s 24-hour customer-service window, Messenger’s 24-hour messaging window, WeChat’s 48-hour window — after the window closed. The window opens on the contact’s most recent inbound message and counts down from it; once it closes, only template (WhatsApp) or tagged (Messenger) messages go through. Fix. Pick one of two paths:
  1. Send a template reply — on WhatsApp, dispatch an approved message template; on Messenger, send with an allowed messaging tag. Both reach the contact outside the free-form window.
  2. Wait for the window to reopen — every inbound from the contact (message, postback, button tap) reopens the window, and a free-form send then succeeds.
Do not re-tread. The Messenger/Instagram tag ladder and the MESSAGING_WINDOW_CLOSED envelope are worked end-to-end on messenger window closed — this page only routes the generalized OUTSIDE_SESSION_WINDOW code (which also covers WhatsApp and WeChat windows) onto those same two recovery paths.

MESSAGING_NUMBER_DEACTIVATED — recipient churned per the carrier feed

Cause. For NANP (+1) SMS/MMS destinations, Orbit scrubs the recipient against the carrier deactivation (churn) feed before the send leaves. The feed reported the number deactivated on or after your most recent recorded consent grant — meaning the number may be reassigned to a new subscriber or simply disconnected. Sending would risk delivering to the wrong person, so the send fails closed with a 422 and an audit-log entry for compliance review. Fix. Two valid resolutions:
  1. Remove the number from the list — the safest path when you have no fresh relationship with that destination.
  2. Refresh consent and retry — once the contact re-confirms their number (a new recorded grant), the next send’s scrub compares the feed against the newer grant date and passes.
The verification stack has the sibling guard VERIFY_NUMBER_DEACTIVATED on POST /verify/start — same carrier feed, verify-channel scope.

When to escalate

One of these codes persisting after the fix above is abnormal. Escalate to support with:
  1. Your organization ID — Settings → Organization, or from GET /api/v1/me.
  2. The exact code and meta.request_id from the rejected envelope.
  3. For quiet-hours: the recipient destination and your current send window settings.
  4. For deactivation: the destination number and your last recorded consent-grant timestamp for it.

See also