How Spaw computes the risk score

published · September 3, 2026

The risk score is a sum of published weights over documented flags, capped at 100, with undeliverable fixed at 100. Every weight, thresholds, examples.

Spaw's risk_score is not a model. It is the sum of fixed weights over the boolean flags already in the response, capped at 100, and an undeliverable verdict is always exactly 100. You can recompute it from the fields next to it, branch on the flags instead, or change the weights in your own code if your policy differs. This page lists what the code does, weight by weight.

Which flags carry weight?

Signal Field it comes from Points
Disposable domain disposable: true +80
Known typo-squat domain reason: "likely_typo" +60
Domain has no MX record, only the address-record fallback mx_implicit: true +30
Role address role: true +30
Catch-all server catch_all: true +30
Domain registered less than 30 days ago domain_age_days under 30 +30
Mailbox full smtp_reason: "mailbox_full" +20
Gibberish username is_gibberish: true +20
Domain registered less than 180 days ago domain_age_days under 180 +10
No SPF record on a business domain has_spf: false and free_provider: false +10
Mailbox not SMTP-verified smtp_checked: false +10
Plus-tag alias is_alias: true +5

The age rows are exclusive: a domain under 30 days gets 30, one between 30 and 179 days gets 10, older domains and unknown ages get nothing. The SPF row only applies to business domains, because a Gmail user's risk has nothing to do with Google's SPF record. The sum is capped at 100.

What are the levels?

risk_score risk_level
60 and above high
30 to 59 medium
0 to 29 low

An undeliverable verdict skips the sum entirely and reports 100 and high.

Worked examples

Address and situation Flags Score
[email protected], mailbox confirmed, old domain with SPF none 0, low
[email protected], mailbox confirmed role 30 30, medium
[email protected], catch-all server catch-all 30, alias 5 35, medium
[email protected], registered 20 days ago, no SPF, greylisted young domain 30, no SPF 10, unverified 10 50, medium
[email protected] typo-squat 60 60, high
[email protected] disposable 80, unverified 10 90, high
[email protected], catch-all server, no SPF gibberish 20, catch-all 30, no SPF 10 60, high
[email protected], server says no such user undeliverable 100, high

Disposable domains are never probed, so the unverified weight always joins the disposable one; that is why the disposable test address answers 90 rather than 80.

What counts as gibberish?

The check is deliberately conservative, because a false positive here insults a real person. A username is only considered when it has six or more letters. Anything the name dictionary recognises, a first name, a surname, first.last, an initial plus a surname, or the two run together, is never gibberish. Beyond that, a username is flagged if it contains no vowel at all, with y counted as a vowel to protect names like Krzysztof, or if it contains a run of six consonants. Digit-only usernames are never flagged; numeric IDs at large Chinese providers are ordinary.

How does the verdict interact with the score?

The verdict comes first and is decided by precedence, not by the score. Undeliverable reasons win over everything: null MX, unresolvable MX, no mail server, an impossible username, a disabled mailbox, a missing mailbox, in that order. Among risky reasons the order is typo-squat, disposable, implicit MX, mailbox full, role, catch-all. The score then adds up every flag that applies, so an address reported as role on a catch-all server carries both weights even though reason names only one.

curl https://spaw.co/api/v1/email \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'
{
  "success": true,
  "data": {
    "email": "[email protected]",
    "deliverable": "deliverable",
    "reason": null,
    "risk_score": 0,
    "risk_level": "low",
    "is_alias": false,
    "is_gibberish": false,
    "disposable": false,
    "role": false,
    "catch_all": false,
    "has_spf": true,
    "free_provider": false,
    "domain_age_days": 10402,
    "smtp_checked": true,
    "// 13 more fields": "see the endpoint reference"
  },
  "meta": { "credits_used": 1, "credits_remaining": 9, "cache_hit": false }
}

Every flag the score reads is in that response, which is what makes it recomputable.

How is mailbox_confidence different?

The risk score answers "how risky is sending here". mailbox_confidence answers a narrower question, "does this specific mailbox exist", and only when the handshake could not say: catch-all domains and unanswered probes. It is null for confirmed mailboxes and for undeliverable verdicts.

It starts at 50 and moves by fixed amounts.

Signal Adjustment
Your own delivery outcomes for the domain, five or more reported: bounce rate 50% or higher −20
Your own delivery outcomes for the domain, five or more reported: bounce rate 10% or lower +15
DMARC policy reject or quarantine +15
DMARC policy none +5
SPF record present +10
SPF record absent −10
Mail hosted at Google, Microsoft, Proofpoint, Mimecast, Barracuda, Zoho or Fastmail +10
Username recognised as a name +15
Username flagged as gibberish −25
Role address +5
Domain registered under 90 days ago −15
Domain registered two years ago or more +10
Plus-tag alias −10

The result is clamped to 0 to 100. Only your own account's delivery reports are used; nobody else's outcomes influence your answers.

How should you use the score?

Branch on the flags when you have a policy, and on the score when you need one threshold. A typical signup form rejects undeliverable, asks for another address on disposable, and accepts the rest. A typical campaign sends to scores under 30, holds 30 to 59 for a warm-up batch, and drops 60 and above. If your bounce data says the weights are wrong for your audience, recompute the score with your own weights from the same flags; nothing about the API assumes you use its number.

What to do next

Related

markdown version: /guides/how-spaw-computes-the-risk-score.md

Verify addresses the same way

The Spaw API runs every check described here on each lookup, with an SMTP handshake that never sends mail. 10 free lookups a month, no card required.

Get your API key

More guides