Skip to main content

Deliverability Lab: pre-send scoring

Carrier filtering and legal compliance are different problems, and only one of them is covered by the regulatory scanner. The policy scanner answers “is this send legal” — TCPA consent, SHAFT content, GDPR basis, India DLT templates. The Deliverability Lab answers the other question: “will carriers let this content through, and how will it land on the specific destination carriers my audience uses?” It is a read-only pre-send scoring surface that runs two independent signals — a content filtering-risk lint and a per-carrier delivery prediction from your own history — and rolls them into a blocking/advisory verdict before you commit a single message.

Where the lab runs — before the campaign exists

The lab sits server-side, before persistence and before the pre-flight gate chain. You pass the draft body and channel in the request body; no campaign row is required to exist. That placement matters in two directions:
  • Pre-persist (compose time). The composer re-scores on every edit, before the campaign is saved. Reworking the copy at this stage is free — nothing is scheduled, nothing is charged.
  • Pre-launch (review time). The create wizard’s Review step re-runs the lab as part of launch verification, before the pre-flight gates (dry-run, audience preview, policy scanner) and before any of the runtime gates in the send-gating chain admit a message.
The lab never gates a gate: it scores content, it does not route a send. It makes no database writes, no provider calls, and no wallet touches — launching stays under your control. The endpoint is POST /api/v1/campaigns/deliverability-lab, accepting { channel, body, subject?, window? } (campaigns:read scope is sufficient) and returning content_risk, predicted_delivery, blocking_warnings, advisories, and a ready_to_send convenience flag.

1. Inputs

The call takes — and only takes — the draft itself and its shape:
  • channel — one of the campaign channels (sms, mms, whatsapp, email, rcs, viber, telegram, line, messenger, instagram, push, voice, fax). The channel decides which filtering model applies (see below) and whether a per-carrier projection is meaningful.
  • body — the draft message body (the campaign’s message template, up to 100,000 characters — the campaign body ceiling, so a full HTML email scores on the same Review screen it launches from).
  • subject — email subject line, scored alongside the body when set.
  • window — the historical lookback for the carrier delivery baseline: 7d, 30d (default), or 90d.
What the lab joins against your account: for SMS and MMS, your account’s past outbound sends bucketed per destination operator by MCCMNC code, labeled from the shared operator catalogue; for every other channel, a single account-level delivery baseline. The destination carrier list is inferred from your own send history, not taken as input.

2. Content filtering-risk lint

The lint evaluates only one axis: will carrier and inbox filters treat this as spam-shaped content. It deliberately does not score sender reputation, regulatory compliance, or recipient consent — those live elsewhere (sender warming and reputation covers the reputation axis). Scoring is channel-aware:
  • Keyword spam scan. The same spam-keyword classifier behind the composer’s live underlines matches promotional, urgency, and financial vocabulary against the filtering profile of the mapped channel. MMS, viber, telegram, line, messenger, and instagram borrow the SMS heuristic (they either share the SMS carrier-filter profile or behave like SMS marketing copy for filtering purposes); RCS, WhatsApp, and email have their own profiles; email also scores the subject line. Push, voice, and fax have no meaningful carrier content-filtering layer, so no keyword model is consulted for them.
  • URL and link heuristics. Layered on top, because carriers filter on link shape separately from vocabulary: public shared URL shorteners (bit.ly, t.co, goo.gl, tinyurl.com, and similar shared redirectors — each one is named with its fix), bare IP-address links (http://203.0.113.5/...), insecure http:// links, and link-stuffing (three or more distinct links in one message). Shared shorteners hide the destination from carrier link-reputation engines and are the single strongest SMS filtering trigger; the lab distinguishes them from branded/dedicated link domains, which are fine.
The two signals blend into a 0–100 score, a bucketed risk_level (low / medium / high / critical), and a verdict. Every finding — keyword spam_keywords or a URL heuristic (url_shortener, url_raw_ip, url_insecure, url_stuffing) — carries a severity (warn or block), the matched fragments, a remediation suggestion, and the weight it contributed to the score. URL heuristics are evaluated on every channel, because a raw-IP link is suspect even where no keyword model applies. For push, voice, and fax the lint reports applicable: false — URL heuristics still run and contribute to the score, but no keyword spam model exists for those channels, so the score is the URL-heuristic weight alone.

3. Per-carrier delivery prediction

The second signal answers: “given this content’s risk score, how would it land on the carriers my audience actually uses?” It is built from your account’s own delivery history, not platform-wide statistics:
  1. Aggregate your outbound sends for the channel over the window(s) you chose, per destination operator for SMS/MMS (bucketed by MCCMNC, labeled and country-tagged from the operator catalogue) or a single account-level bucket for other channels.
  2. Compute each carrier’s historical delivery rate on a terminal-status denominator: delivered-equivalent statuses over terminal statuses. This frames the rate against sends that actually concluded, so a pile of still-in-flight messages cannot flatten the denominator and flatter the rate.
  3. Apply a content-risk penalty that scales linearly with the lint score: at most 35 percentage points off the historical baseline at a content score of 100, clamped to the 0–100 band. A clean carrier is never predicted to zero on content alone — the carrier’s structural baseline still dominates; filtered content suppresses delivery meaningfully but proportionally.
Honesty about sample size is built in. Carriers below 30 terminal sends are flagged low confidence; the overall headline rate is sample-weighted, so a tiny-volume carrier cannot swing the projection. Confidence turns high at 500 sends. If your account has no terminal outbound history for the channel at all, has_history comes back false and overall_predicted_rate is null — the lab says “no data yet” rather than invent a number.

4. Blocking vs advisory warnings

The lab separates blocking findings (a launch you should reconsider) from advisories (information worth having, safe to launch over). The composer surfaces both; ready_to_send is true when the blocking list is empty, and the verdict remains yours — the lab is advisory at launch, not a hard stop. Warnings of either class do not stop the API response, and only the blocking set flips ready_to_send to false — a launch gate reads the blocking list and ignore advisories without enumerating every warning text.

5. When it runs, and how it differs from the send-gating chain

Three moments in a campaign’s life, three different lenses: The earlier content lens is the cheap one: a rewrite in the composer costs nothing, and it is the only one that catches “legally clean but carrier-filter-shaped” copy before it silently blackholes. The measured counterpart to this predicted lab is the seed lab — register your own test handsets via the campaigns seed-lab endpoints and score actual terminal deliveries to them. Predict from history with the lab; measure with the seed roster; gate legality with the policy scanner.

6. Worked example

A draft SMS campaign:
The lint maps sms to the SMS spam profile. The keyword scan matches urgency and promotional vocabulary (“SALE”, “50% off”, “today only”, “Claim now”) toward a spam score; the URL heuristics find a public shortener (bit.ly) and an insecure http:// link. The shortener is a block-severity finding with weight 30; the insecure link is a warn with weight 8; the keyword scan adds its own weight on top — pushing the blended score into the high/critical bands and the verdict to block. Suppose your 30-day history on the destination carrier (MCCMNC 28601, say) runs a healthy 92% historical delivery over a few hundred terminal sends. The content-risk penalty — the score scaled to at most 35 points — drags this carrier’s projection under the 50% block floor: a blocking warning on the prediction joins the blocking warning on the content. Meanwhile a second destination carrier with a strong baseline (99% historical) discounts the same content penalty and lands above 75% — no advisory raised there. The roll-up comes back with two blocking warnings (content verdict block; predicted overall delivery below 50%) and one advisory (the insecure http:// link). ready_to_send is false. Fix it in the composer by swapping the shortener for a branded link domain, moving to https://, and softening the urgency phrasing — re-score, and the block clears.

7. Limits — what the lab cannot predict

The lab is a heuristic over what it has seen, and two blind spots are structural:
  • Novel content. The keyword profiles and URL heuristics cover known carrier-filter triggers; a newly circulating spam pattern with no similarity to the profiled vocabulary scores low until carriers (and then the classifier) catch up. A 0–100 lint score is a filtering-risk estimate, not a guarantee.
  • A carrier you have never sent to. The prediction is built from your account’s own terminal history. A destination operator with no past sends contributes no baseline — the model does not borrow other tenants’ outcomes, and a low-confidence rate is labeled, not silenced. The only way to close the gap is to send (or use the seed lab to measure a test handset on that carrier).
Two more boundaries worth keeping: the lab is not the policy scanner — a clean lint verdict says nothing about TCPA consent or SHAFT content — and for push, voice, and fax (no carrier content-filtering layer to model) the report still returns URL heuristics, but the predicted_delivery block stays null because there is no meaningful per-carrier SMS-style delivery rate to project.

See also