Skip to main content

Deliverability Lab

Carrier filtering and regulatory compliance are two different problems. The policy scanner gates on legal rules — TCPA, SHAFT-content, GDPR, India DLT — and it can pass a campaign that carriers still silently filter for looking like spam. The Deliverability Lab fills that gap: it scores the draft campaign body for filtering risk and projects delivery rates per destination carrier from your own history, so “was this filtered?” is a measurement you take before launch, not an inference you make after the fact. The lab runs on POST /api/v1/campaigns/deliverability-lab. It is read-only: no campaign row is required, nothing sends, no provider is called, and the wallet is untouched. Pass the draft body in the request; re-score on every edit while you compose.

1. The two signals in one report

Each call returns both signals plus a warning roll-up the composer renders directly. Signal one — content filtering-risk lint (content_risk). The body is scanned twice:
  • Spam-trigger vocabulary. The shared spam-keyword classifier (@devotel/compliance’s scanning package — the same engine behind the composer’s live underlines) matches promotional, urgency, and financial vocabulary against the channel’s filtering profile. Email campaigns pass the subject line too.
  • URL and link heuristics. Layers on top of the keyword scan, because carriers also filter on link shape: public URL shorteners (bit.ly, t.co, goo.gl, and friends), bare IP-address links, plain http:// links, and link-stuffing (three or more links in one message).
The two are blended into a 0–100 score, a bucketed risk_level (low / medium / high / critical), and a verdict of pass, warn, or block. Each finding carries a severity, the matched fragments, and a remediation suggestion. Signal two — predicted per-carrier delivery (predicted_delivery). The controller aggregates your tenant’s own terminal-status sends per destination operator (per MCCMNC for SMS/MMS, joined to the shared operator catalogue; a single account-level bucket for other channels) over a 7-, 30-, or 90-day window. Each carrier’s observed delivery rate is discounted by a content-risk penalty that scales with the lint score, up to 35 percentage points at a content-risk score of 100. Rates are sample-weighted, so a low-volume carrier cannot swing the headline number; samples under 30 sends are flagged low confidence. When you have no history yet, the lab says so instead of inventing a number. The roll-up. blocking_warnings lists anything you should fix before launch (a block verdict, or a predicted overall delivery rate at or below 50%); advisories lists non-blocking nudges (a warn verdict, a predicted rate at or below 75%, or carriers projected under the block floor with medium/high confidence). ready_to_send is true when the blocking list is empty. campaigns:read scope is sufficient.

2. Not the policy scanner

The regulatory policy scanner answers “is this send legal?” — TCPA consent, SHAFT-content categories, GDPR lawful basis, India DLT template rules. The Deliverability Lab answers “will carriers let this through?” — vocabulary and link shape vs. your observed per-carrier history. A pre-flight gate firing in the composer can be either one; the report tells you which lens flagged it, so a clean legal posture doesn’t settle the filtering question and a filtering flag isn’t a legal verdict. And the lab’s counterpart for measured (not predicted) truth is the seed lab: register your own test handsets on POST /api/v1/campaigns/deliverability-seed-lab and score actual terminal deliveries to them. Predict from history with this endpoint; measure with the seed roster; gate legality with the policy scanner.

3. Where it lives

The campaigns create wizard’s Review step runs the lab as part of pre-flight, and the composer re-scores on edit — the same shape as the dry-run (which checks audience, cost, and quiet hours) and audience preview, but scoped to content and carrier risk. Every read in the flow below accepts campaigns:read; only launch needs campaigns:write.

4. Call it from the API

Running the body above returns a blocking result driven by the shared shortener and the urgency/discount vocabulary:
Fix the content where the penalties come from. Replace the shared shortener with a branded link domain (see short links and click tracking), move to https://, and pull the countdown vocabulary:
The lint score drops to the single digits, the verdict flips to pass, blocking_warnings empties, ready_to_send returns true, and the predicted per-carrier rates climb back toward their historical baselines. That round-trip — flag, fix, re-score — is the operating loop the lab is built for. Penalty cheatsheet: