NO-SEND-GATE — Messaging Vendor Hard Gate v1

Purpose: This document defines the 11 mandatory pre-send gate checks that every new messaging vendor must pass before any live production traffic is routed through them. No vendor sends more than 50 live messages until every gate check shows PASS. This document is the canonical authority for the gate; the messaging_compliance_gate skill enforces it at runtime.


Why this gate exists

Past SMS incidents at RERI have included duplicate sends to 374 contacts (Supabase pagination bug), blast sends on the wrong inbox (Woodale incident: 205 msgs on wrong number), and untracked carrier failures. The no-send gate prevents these classes of failure by requiring evidence artifacts before any volume traffic.

This gate is not optional. It is not subject to “we’ll get to it later.” Every vendor onboarding skips no checks.


Gate authority

  • Maintained by: Henry Hill (owner), Claude Code (enforcer)
  • Enforced by: messaging_compliance_gate skill + tools/messaging-compliance-gate.js
  • Override authority: Henry only, in writing, for a named vendor + specific date window
  • Sandbox vs live: Gate checks 3-11 MUST pass in sandbox first. Gate checks 1-2 (10DLC) require production accounts per carrier policy.

The 11 gate checks

Each check requires a named evidence artifact before it is considered PASS. “I believe it works” is not an artifact.


Check 1: 10DLC brand approved (SMS vendors)

FieldValue
Check IDG1
Applies toSMS vendors: Telnyx, Bandwidth, sent.dm, Bird, SalesMsg
Does NOT apply toiMessage-only vendors: LoopMessage, Linq, BlueBubbles
Pass criteriaTCR brand status = APPROVED
Evidence artifactScreenshot of TCR portal showing APPROVED status + TCR Brand ID stored in messaging_providers.tcr_brand_id column
Sandbox workaroundNone. 10DLC registration requires a production carrier account. Begin registration early (typical: 5-10 business days).
Failure consequenceNo live SMS sends on that vendor. Period.

What to check: Log into the vendor’s portal or TCR Brand Registry portal. Confirm brand status = APPROVED. Copy the Brand ID into the messaging_providers row for this vendor.


Check 2: 10DLC campaign approved (SMS vendors)

FieldValue
Check IDG2
Applies toSMS vendors (same as G1)
Pass criteriaTCR campaign status = APPROVED
Evidence artifactScreenshot of campaign status = APPROVED + campaign ID stored in messaging_campaigns.tcr_campaign_id
Sandbox workaroundNone. Campaign registration requires production. Submit early.
Failure consequenceNo marketing SMS sends, even on approved numbers.

Note for sent.dm: TCR campaign submitted 2026-04-24. Awaiting TCR approval (10-15 business days from submission). SMS with DRAFT templates is technically available (per Appendix D.3 of the plan) but gate G2 must be PASS before Phase 1 live smoke.


Check 3: Outbound API test

FieldValue
Check IDG3
Applies toAll vendors
Pass criteriaPOST to send endpoint returns HTTP 200 or 202, AND message is delivered to the recipient handset
Evidence artifactAPI response body logged + screenshot of received message on recipient phone
Sandbox modeUse vendor sandbox/trial; recipient = sandbox test number or Henry’s internal test number
Failure consequenceBlock; investigate auth, phone number provisioning, or template approval issues

Common failure modes: wrong auth header format (Bearer vs AccessKey vs Basic), phone number not provisioned, template not approved (sent.dm), missing MESSAGING_PROFILE_ID (Telnyx).


Check 4: Inbound webhook verified

FieldValue
Check IDG4
Applies toAll vendors
Pass criteriaA reply from the test recipient triggers the vendor’s webhook, which reaches the local handler, which writes a row to messaging_inbound_messages
Evidence artifactDatabase row in messaging_inbound_messages with correct from_phone, body, received_at + raw webhook body in webhook_audit_log
Sandbox modeMost vendors support sandbox webhooks. Confirm sandbox inbound path works.
Failure consequenceBlock; without confirmed inbound, reply tracking and STOP processing are broken

How to test: After sending a message in G3, reply from the recipient phone. Verify the reply appears in the database within 30 seconds.


Check 5: Delivery receipt (DLR) verified

FieldValue
Check IDG5
Applies toAll SMS vendors (Telnyx, Bandwidth, sent.dm, Bird, SalesMsg, Twilio)
Pass criteriaDelivery status update from vendor triggers webhook, which writes a row to messaging_delivery_events
Evidence artifactRow in messaging_delivery_events with status = 'delivered' + occurred_at timestamp
Sandbox modeMost sandbox environments emit synthetic DLRs. Verify.
Failure consequenceBlock; without DLR tracking, delivery rate metrics for the scorecard are unreliable

Check 6: STOP suppression enforced

FieldValue
Check IDG6
Applies toAll vendors
Pass criteria(a) Replying STOP from test number writes a row to messaging_suppression_events with reason = 'stop_keyword', AND (b) attempting a subsequent send to that number is blocked by the compliance gate with reason = 'suppressed' in the gate log
Evidence artifactSuppression row + gate log entry showing blocked send
Sandbox modeTest with test numbers. Verify both the suppression write and the blocked-send behavior.
Failure consequenceHard block. Sending to STOP-opted contacts is a TCPA violation. This check is non-negotiable.

Implementation note: The compliance gate at tools/messaging-compliance-gate.js queries messaging_suppression_events before every send. The gate must be in the send path before G6 can PASS.


Check 7: Quiet-hours enforcement

FieldValue
Check IDG7
Applies toAll vendors
Pass criteriaA send attempted after 22:00 local time (recipient’s timezone, defaulting to PT) is blocked with reason = 'quiet_hours' in the gate log
Evidence artifactGate log entry with reason = quiet_hours, attempt_time, recipient_tz
Sandbox modeFake the timestamp in the gate logic test suite; no need to wait until 22:00
Failure consequenceBlock; off-hours sends generate opt-outs and carrier complaints

Time window enforced: no sends from 22:00 to 08:00 recipient local time. Carriers and TCPA enforcement treat quiet-hours violations seriously.


Check 8: Per-phone cooldown enforced

FieldValue
Check IDG8
Applies toAll vendors
Pass criteriaA second send to the same phone number within the cooldown window is blocked with reason = 'cooldown' in the gate log
Evidence artifactGate log entry with reason = cooldown, last_sent_at, cooldown_minutes
Cooldown default60 minutes for marketing, 5 minutes for 2FA/transactional
Sandbox modeAdjust cooldown to 1 minute for testing; restore before live
Failure consequenceBlock; double-sends cause opt-outs and erode sender reputation

Check 9: Dedup via processed_webhook_events

FieldValue
Check IDG9
Applies toAll vendors (inbound webhook handlers)
Pass criteriaReplaying the same webhook payload (same event ID) short-circuits: the handler returns 200 immediately without re-processing, and a log entry shows duplicate = true
Evidence artifactHandler log showing duplicate=true for the replayed request
How to testSend the same raw webhook body twice (use cURL or test script) and observe log
Failure consequenceBlock; without dedup, retry storms from the vendor cause double-writes and possible double-replies

Check 10: webhook_audit_log verified + local-file fallback

FieldValue
Check IDG10
Applies toAll vendors
Pass criteria(a) Every inbound webhook event writes a row to webhook_audit_log within 5 seconds. (b) BRIN index is active on occurred_at. (c) When Supabase is unreachable (simulated), the handler writes to local fallback file at /tmp/openclaw/webhook-audit-fallback-<date>.ndjson instead of crashing
Evidence artifactSample rows in webhook_audit_log + disk fallback file with entries from the simulated-outage test
How to simulate Supabase outagePoint SUPABASE_URL to a non-existent host for one test run
Failure consequenceBlock; audit trail is required for TCPA compliance and debugging. A handler that crashes on DB outage drops messages silently.

Check 11: Dead-letter + cost tracking

FieldValue
Check IDG11
Applies toAll vendors
Pass criteria(a) Failed sends persist in messaging_outbound_messages with status = 'failed' and are not silently dropped. (b) cost_cents column is populated for delivered messages. (c) Failed messages appear in the vendor’s dead-letter file at workspace/reports/dead-letter/<vendor>-<date>.ndjson
Evidence artifactRow in messaging_outbound_messages with status=failed + cost row + dead-letter file entry
How to testSend to an invalid number or a number in DND mode; confirm the row shows failure, not absence
Failure consequenceBlock; without cost tracking the Phase 2 scorecard cannot compute cost-per-qualified-reply

Gate check matrix (summary)

#Check nameSMS vendorsiMessage vendorsSandbox-first?Evidence table / log
G110DLC brand approvedRequiredNot applicableNo (production only)messaging_providers.tcr_brand_id
G210DLC campaign approvedRequiredNot applicableNo (production only)messaging_campaigns.tcr_campaign_id
G3Outbound API testRequiredRequiredYesAPI response + handset screenshot
G4Inbound webhookRequiredRequiredYesmessaging_inbound_messages row
G5Delivery receiptRequiredSMS onlyYesmessaging_delivery_events row
G6STOP suppressionRequiredRequiredYesmessaging_suppression_events + gate log
G7Quiet-hours enforcementRequiredRequiredYesGate log reason=quiet_hours
G8Per-phone cooldownRequiredRequiredYesGate log reason=cooldown
G9Dedup via processed_webhook_eventsRequiredRequiredYesHandler log duplicate=true
G10webhook_audit_log + fallbackRequiredRequiredYeswebhook_audit_log rows + disk file
G11Dead-letter + cost trackingRequiredRequiredYesmessaging_outbound_messages.status=failed + cost row

Sandbox-first rule (non-negotiable)

Gate checks G3 through G11 MUST pass in the vendor’s sandbox/trial environment before any live paid message is sent. The only exceptions are G1 and G2, which require production carrier accounts per TCR policy.

Confirmed sandbox availability (from plan Section B.2):

VendorSandbox availableNotes
TelnyxYes ($2 free credit)Full API surface
BandwidthYes (sandbox account via sales)Full API surface
sent.dmProvisional (sandbox: true flag in API)sandbox: true in POST body validates without sending
BirdYes (auto trial credit)Full API + WhatsApp sandbox
LoopMessagePartial (5 verified contacts)G3-G11 possible; real recipient limited
LinqYes (dev/test tier)API audit + gate testing
BlueBubblesYes (self-hosted)Already proven

Dry-run modes in bulk_sms_split_test skill

The split-test skill supports three operating modes that map to this gate’s phases:

  • --dry-run=plan: outputs group assignment + template selection per contact without calling any vendor API. No network calls. Used to verify the population query, group hash logic, and template mapping.
  • --dry-run=sandbox: routes all sends through each vendor’s sandbox (where available). Real API calls; no paid sends. Used to validate gate checks G3-G11 before live traffic.
  • --live: actual production traffic. Requires Phase 1 Approval C on record before this flag is valid. Gate checks G1-G11 must ALL be PASS for the vendor before --live is accepted.

How to record gate check results

For each vendor, create an evidence packet file at:

workspace/reports/vendor-evidence/<vendor>-gates-<date>.md

Each file must contain one row per gate check:

- PASS: G1 10DLC Brand — TCR Brand ID: BXXXXXXXXX (screenshot: /reports/vendor-evidence/<vendor>-brand-screenshot.png)
- PASS: G3 Outbound API — HTTP 202, message_id=abc123 (2026-05-01 14:32 PT)
- FAIL: G4 Inbound webhook — no row in messaging_inbound_messages after 120s
...

All 11 checks must show PASS before the vendor is marked smoke_test_passed in messaging_providers.status.


Escalation path for gate failures

  1. G1/G2 (10DLC failures): Contact vendor’s compliance team directly. Timeline is carrier-controlled. Do not attempt workarounds.
  2. G3-G5 (API/delivery failures): Check vendor KB at workspace/knowledge-base/<vendor>/API.md. Re-read auth type. Check rate limits. Try with explicit timeout.
  3. G6-G8 (compliance logic failures): These indicate bugs in tools/messaging-compliance-gate.js. Fix the gate code; do not bypass.
  4. G9 (dedup failure): Check processed_webhook_events table for the event ID. Ensure the handler is inserting before processing.
  5. G10-G11 (audit/cost failures): Check Supabase connection. Check local fallback path permissions.

If any gate check fails and the vendor is in the Phase 1 smoke critical path, escalate to Henry with a written remediation plan before attempting to advance to Phase 2.


Change log

DateChangeAuthor
2026-04-24Initial document created from plan Section BClaude Code

Version: v1 Owner: Henry Hill Last updated: 2026-04-24 Sourced from: messaging-vendor-phase-0-1-2026-04-23 plan Section B