> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Spam-Keyword Deliverability Classifier

> How the SpamAssassin-style spam-keyword classifier scores your message content, the 0–100 rubric behind pass/warn/block verdicts, and how the compose-time linter and send-time hook consume the result.

# Spam-Keyword Deliverability Classifier

Carrier filters and email inbox providers score every message against a
vocabulary of classic spam tells — "FREE!", "click here", "act now", "\$\$\$",
prize claims, and all-caps subject lines. A message stacked with these
triggers gets blackholed, junked, or rate-limited before it reaches your
recipient.

Orbit runs a SpamAssassin-style classifier over your content to catch those
triggers **before** the carrier does. Every outbound message on SMS,
WhatsApp, email, and RCS is scanned in line with the send, and the same scan
runs live as you type in the composer. This page explains what the classifier
checks, how the score is computed, and what to do when a verdict comes back
as warn or block.

All endpoints below are rooted at `https://api.orbit.devotel.io`.

<Note>
  The classifier is a **tenant-owned hint, not a platform mandate**. A warn
  or block verdict tells you carrier filters are likely to hurt
  deliverability on this content; your organization's
  [policy scan mode](/compliance/policy-scanner) decides whether a block
  verdict stops the send or is only recorded. Orbit never uses the classifier
  to gate your traffic globally. This page is not legal advice — for the
  legal spam statutes in your send regions, see [AU Spam Act](/compliance/au-spam-act),
  [CAN-SPAM](/compliance/can-spam), and [CASL](/compliance/casl-canada-anti-spam).
</Note>

***

## Deliverability scan vs. legal compliance scan

Two different pre-send scans run on your content, and they answer different
questions:

* The **spam-keyword classifier** (this page) scores carrier-deliverability
  and inbox-reputation vocabulary — the words and patterns that make carrier
  filters and mail providers treat a message as junk.
* The **policy scanner** ([Pre-Send Policy Scanner](/compliance/policy-scanner))
  covers the legal and regulatory lanes: TCPA quiet hours, SHAFT content,
  missing opt-out phrases, GDPR sender identity, country sender registration,
  and DLP (payment card numbers, SSNs, IBANs in the body).

The spam-keyword classifier is the scoring engine behind one row of the
policy scanner's rule table; both run together on every send, and a high spam
score appears alongside the legal findings in the same verdict payload.

## Channels covered

The classifier runs on **SMS, WhatsApp, email, and RCS**. Push notifications
are excluded — that content is delivered through the OS provider, whose own
filtering is upstream of Orbit.

For email, the **subject line is scanned in addition to the body**, and
subject hits carry extra weight (see [How the score is computed](#how-the-score-is-computed))
because the subject is the gatekeeper for inbox placement.

## How the score is computed

The classifier evaluates your content against a table of keyword and pattern
rules derived from SpamAssassin's public rule set and mainstream mailbox
deliverability guidance. Scoring works like this:

1. **Each rule carries a weight from 1 to 25.** Higher-signal patterns
   (prize/winner language, pharmacy spam, account-verification phishing)
   weigh more than lower-signal ones (aggressive percent-off claims).
2. **The weights of every matched rule are summed.** Extra matches inside the
   same rule add a smaller bonus each, so repeating "click here" five times
   does not stack five full weights.
3. **Subject hits count 1.5×** (email only) — the subject drives inbox
   placement more than any body line.
4. **SMS and RCS hits count 1.25×** — US carrier 10DLC filters are stricter
   about urgency, "FREE", and call-to-action language than email providers,
   so the same vocabulary is penalised harder on those channels. WhatsApp is
   kept at 1× because Meta's own filtering is upstream.
5. **The total is clamped to 0–100** and rounded to a whole number.

The score maps to a verdict:

| Score  | Verdict | What happens                                                                                                                                                                                                                                |
| ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0–59   | `pass`  | The send proceeds normally.                                                                                                                                                                                                                 |
| 60–79  | `warn`  | The send proceeds; the elevated score and matched rules are recorded on the message and returned in the send response.                                                                                                                      |
| 80–100 | `block` | Treated as a blocking finding by the [policy scanner](/compliance/policy-scanner). Whether the send actually stops depends on your organization's policy scan mode (`warn` records it, `strict` rejects it, `off` skips the scan entirely). |

A single flashy phrase ("you've won") never blocks by itself. A stack of
mid-signal phrases ("FREE!" + "click here" + "act now" + "\$\$") is what
crosses the warn and block thresholds — the same shape carrier filters
penalise.

## What a hit looks like

Every matched rule is returned as a structured hit, so you can pinpoint the
exact words to rephrase:

| Field      | Meaning                                                                                                                         |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `rule`     | Stable rule identifier (safe to key analytics on).                                                                              |
| `label`    | Human-readable rule name, e.g. "Urgency pressure".                                                                              |
| `category` | The bucket the rule falls under: `commerce`, `urgency`, `financial`, `adult`, `scam`, `health`, `cta`, `phishing`, or `format`. |
| `weight`   | The base weight the rule contributed.                                                                                           |
| `matched`  | The exact substring(s) that fired the rule — the tokens the composer underlines.                                                |
| `location` | `body` or `subject` (subject is email only).                                                                                    |

Rule identifiers are kept stable across classifier revisions, so a trend line
on `urgency_act_now` remains comparable over time.

## Rule taxonomy

The rules fall into broad families:

* **Urgency / pressure** — "act now", "limited time", "expires today",
  "final notice".
* **Free-offer language** — "100% free", "no obligation", "free gift", plus
  a dedicated rule for a shouted `FREE!`.
* **Money claims** — multiple dollar signs (`$$$`), "earn money", "work from
  home", "passive income".
* **Prize / winner language** — "you've won", "claim your prize",
  "congratulations", lottery and sweepstakes terms.
* **Discount pressure** — "save big", "lowest price", and a `% off` rule that
  only fires at 50% and above (ordinary retail discounts like "20% off" are
  ignored).
* **Generic click-here calls to action** — "click here", "tap to open".
* **Spam denial** — "this is not spam", "we honor all removal". Denials are
  one of the strongest spam tells there is.
* **Health / pharmacy** — weight-loss claims and drug-name vocabulary.
* **Adult / dating** vocabulary.
* **Investment / crypto pump** — "guaranteed returns", "next bitcoin".
* **Credit / loans / debt** — "bad credit", "pre-approved", "payday loan".
* **Account-verification phishing** — "verify your account", "unusual
  activity". Legitimate transactional alerts from your own service are rarely
  phrased this way; if your traffic needs these words, use your own
  verified sender identity and avoid urgency framing.

For **email**, the subject line additionally runs four format rules:

| Rule                        | What fires it                                                                            |
| --------------------------- | ---------------------------------------------------------------------------------------- |
| Subject in ALL CAPS         | A run of 5+ uppercase letters. Short initialisms like "FYI:" are ignored.                |
| Excessive punctuation       | Three or more `!` or `?` in a row.                                                       |
| Fake Re:/Fwd: prefix        | A `Re:`/`Fw:`/`Fwd:` prefix on a message your system composes — a classic phishing tell. |
| Currency symbols in subject | `€`, `£`, `¥`, or a literal `$X` amount.                                                 |

## Compose-time live linter

You don't have to send to see the score. The composer calls
`POST /messages/lint` — the same endpoint that runs the full policy scanner —
as you type (debounced), and it renders the classifier's output inline:

* A **score gauge** shows the 0–100 score with the pass/warn/block bucket.
* Each matched substring in `spam_hits` is **underlined in your draft body
  or subject**, labelled with its rule name and category.
* Rewriting the flagged phrase re-scores the draft on the next debounce, so
  you can watch the score drop below 60 before you send.

The lint endpoint accepts `channel`, `body`, and an optional `subject` (for
email), so the live scan sees the same inputs the send-time scan will.

## Send-time verdict

At send time, every outbound message on the covered channels is scanned by
the same engine as part of the pre-send policy scan. The result lands in the
send response and on the message metadata:

* `spam_score` — the 0–100 score.
* `spam_hits` — the structured list of matched rules, in the shape above.
* A violation entry (`spam_score_elevated` for 60–79, `spam_score_high` for
  ≥80) inside the `violations` array and the `X-Policy-Violations` response
  header.

Whether a `block` verdict actually stops the send is governed by your
organization's **policy scan mode** (`warn`, `strict`, or `off` — default
`warn`, which records but never stops). See
[Pre-Send Policy Scanner](/compliance/policy-scanner) for the mode table and
the `POLICY_VIOLATION` error shape.

## Worked example

An email marketing draft:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/lint \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "email",
    "subject": "FREE TRIAL — ACT NOW!!!",
    "body": "Congratulations! Click here to claim your free trial — 80% off, act now, this deal expires today."
  }'
```

```json 200 theme={null}
{
  "verdict": "warn",
  "spam_score": 69,
  "spam_hits": [
    {
      "rule": "money_winner",
      "label": "Prize/winner language",
      "category": "scam",
      "weight": 18,
      "matched": ["Congratulations"],
      "location": "body"
    },
    {
      "rule": "click_here",
      "label": "Generic click-here",
      "category": "cta",
      "weight": 8,
      "matched": ["Click here"],
      "location": "body"
    },
    {
      "rule": "free_offers",
      "label": "Free-offer language",
      "category": "commerce",
      "weight": 10,
      "matched": ["free trial"],
      "location": "body"
    },
    {
      "rule": "discount_percent",
      "label": "Aggressive percent-off",
      "category": "commerce",
      "weight": 4,
      "matched": ["80% off"],
      "location": "body"
    },
    {
      "rule": "urgency_act_now",
      "label": "Urgency pressure",
      "category": "urgency",
      "weight": 12,
      "matched": ["act now", "expires today"],
      "location": "body"
    },
    {
      "rule": "subject_excessive_punct",
      "label": "Excessive punctuation in subject",
      "category": "format",
      "weight": 10,
      "matched": ["!!!"],
      "location": "subject"
    }
  ]
}
```

Rephrasing to "Start your trial today — save 20% on annual plans" with a
calm subject line removes the prize, click-here, free, aggressive-discount,
and punctuation hits and brings the draft back under the `pass` threshold.

## Posture: tenant-owned hint, contrasted with the legal pages

The classifier is deliberately a **deliverability hint, not a regulation**.
Nothing here decides whether you are legally allowed to send — it predicts
how carrier and inbox filters will treat the content you are sending. The
enforcement decision (record, block, or skip) lives in your own policy scan
mode setting.

Contrast this with the per-country spam statute pages —
[AU Spam Act](/compliance/au-spam-act), [CAN-SPAM](/compliance/can-spam),
[CASL](/compliance/casl-canada-anti-spam) — which document the legal
obligations (consent, identification, unsubscribe handling) that apply
independently of any scoring. A message can pass the classifier with a score
of 0 and still violate CASL if consent is missing, and a legally compliant
message can still get junked if it reads like spam. Check both.

## Related pages

* [Pre-Send Policy Scanner](/compliance/policy-scanner) — the legal-lane
  scanner (TCPA, SHAFT, GDPR, country gates, DLP) and the mode that decides
  what a block verdict does
* [AI brand-safety scan](/compliance/ai-brand-safety-scan) — the opt-in LLM
  pass for impersonation and phishing patterns layered on the lint endpoint
* [DLP — sensitive-data scanning](/compliance/dlp-scanner) — payment card,
  SSN, and IBAN detection with redact mode
* [Send gates](/compliance/send-gates) — quiet-hours, DNC, and RND gates that
  run alongside the scanner
* [Troubleshooting compliance error codes](/compliance/troubleshooting-compliance-error-codes) —
  `POLICY_VIOLATION` and scanner-outage error surfaces
