spam-score
v1.1.0
Published
Score any message or email for spam probability using weighted rule analysis. No API key needed. Works on email, Discord messages, comments, and more.
Maintainers
Readme
spam-score
Score any message for spam probability using weighted rule analysis. No API key. No dependencies.
What it catches
| Category | Examples | |---|---| | 📣 Caps & punctuation | ALL CAPS text, triple exclamation marks | | 💰 Money / free offers | "Earn $500/day", "FREE gift", "guaranteed profit" | | ⏰ Urgency tactics | "Act now", "Expires today", "Last chance" | | 🔗 Suspicious links | IP-address links, URL shorteners, link display-text mismatches | | 🎭 Obfuscation | Zero-width chars, Cyrillic lookalikes, word salad | | 📧 Email header anomalies | Missing DKIM, SPF fail, Reply-To mismatch | | 📝 Known spam phrases | Nigerian prince, prize winner, pharmacy spam, investment alerts | | ✅ Ham signals (negative score) | DKIM present, SPF pass, plain-text format |
Installation
npm install spam-scoreNode ≥ 18 required. Zero runtime dependencies.
Quick Start
import { scoreSpam } from 'spam-score';
const result = scoreSpam(
'CONGRATULATIONS!!! You have WON $1000!!! Click here NOW to claim your prize before it EXPIRES!!!'
);
console.log(result.score); // 9.5
console.log(result.probability); // 0.927
console.log(result.verdict); // 'definitely_spam'
console.log(result.triggeredRules); // [{id:'WINNER_CLAIM', score:3}, {id:'EXCESSIVE_CAPS',...}, ...]API
scoreSpam(body, options?)
Score a message body for spam.
import { scoreSpam } from 'spam-score';
const r = scoreSpam(messageBody, {
headers: { from: '[email protected]', subject: 'Hello', 'dkim-signature': '...' },
isHtml: false,
whitelistDomains: ['mycompany.com'],
});Returns SpamResult:
{
score: number; // Total weighted score (higher = more spam)
probability: number; // 0.0–1.0 spam probability
verdict: SpamVerdict; // 'ham' | 'maybe_spam' | 'spam' | 'definitely_spam'
triggeredRules: SpamRule[]; // Rules that fired
allRules: SpamRule[]; // All rules evaluated
categories: SpamCategory[]; // High-level categories triggered
poweredBy: string; // "@tanmaahy's spam-score package"
}SpamRule:
{
id: string; // e.g. 'EXCESSIVE_CAPS'
description: string; // human-readable
score: number; // contribution (negative = ham signal)
triggered: boolean;
}Verdict thresholds
| Score | Verdict |
|---|---|
| < 2.5 | ham — clean message |
| 2.5 – 5.0 | maybe_spam — review recommended |
| 5.0 – 8.0 | spam — block or quarantine |
| > 8.0 | definitely_spam — high confidence spam |
Options
| Option | Type | Description |
|---|---|---|
| headers | EmailHeaders | Email headers for header-based rules (DKIM, SPF, From, Subject, etc.) |
| isHtml | boolean | Force HTML mode (auto-detected if omitted) |
| whitelistDomains | string[] | Sender domains to always treat as ham |
EmailHeaders accepts any of:
from, to, replyTo, subject, date, receivedSpf, dkimSignature, xMailer, contentType — or any raw header key.
Rule list
| Rule ID | Score | Description |
|---|---|---|
| EXCESSIVE_CAPS | +2.5 | >40% uppercase characters |
| ALL_CAPS_SUBJECT | +2.0 | Subject line entirely uppercase |
| EXCESSIVE_EXCLAMATION | +1.5 | 3+ exclamation marks |
| FREE_OFFER | +2.0 | "free" + offer keyword |
| MONEY_PROMISE | +2.5 | Guaranteed income/profit |
| WINNER_CLAIM | +3.0 | Congratulations + winner |
| CASINO_SPAM | +2.5 | Casino/jackpot promotion |
| ACT_NOW | +2.0 | "Act now", "limited time" |
| URGENT_SUBJECT | +1.5 | Urgent/action-required in subject |
| WORD_SALAD | +1.5 | Obfuscating nonsense words |
| ZERO_WIDTH_CHARS | +3.0 | Invisible Unicode characters |
| LOOKALIKE_CHARS | +2.5 | Mixed Cyrillic + Latin |
| MANY_LINKS | +1.5 | More than 5 links |
| LINK_MISMATCH | +2.5 | Display URL ≠ actual href |
| IP_LINK | +3.0 | Raw IP address in link |
| URL_SHORTENER | +1.5 | bit.ly, tinyurl, etc. |
| NIGERIAN_PRINCE | +4.0 | Advance-fee fraud patterns |
| CLICK_BELOW | +1.0 | "Click here to claim/verify" |
| UNSUBSCRIBE_DECEPTIVE | +1.5 | Commercial email without unsubscribe |
| PHARMACY_SPAM | +3.5 | Viagra/Cialis/online pharmacy |
| INVESTMENT_SPAM | +2.5 | Hot stock pick / trading signal |
| VERY_SHORT_BODY | +1.0 | <20 words with a link |
| DECEPTIVE_SUBJECT | +1.5 | Fake Re:/Fwd: |
| HAS_DKIM | -1.5 | Valid DKIM signature (ham) |
| SPF_PASS | -1.0 | SPF record passes (ham) |
| HAS_PLAIN_TEXT | -0.5 | Plain text (not HTML-only) (ham) |
Real-world examples
Express middleware — block spam comments
import { scoreSpam } from 'spam-score';
app.post('/comments', (req, res, next) => {
const r = scoreSpam(req.body.text);
if (r.verdict === 'spam' || r.verdict === 'definitely_spam') {
return res.status(400).json({ error: 'Message blocked as spam', score: r.score });
}
next();
});Discord bot — auto-delete spam
import { scoreSpam } from 'spam-score';
client.on('messageCreate', async (msg) => {
if (msg.author.bot) return;
const r = scoreSpam(msg.content);
if (r.probability > 0.85) {
await msg.delete();
await msg.channel.send(`🚫 Message removed (spam score: ${r.score.toFixed(1)})`);
}
});Email processing pipeline
import { scoreSpam } from 'spam-score';
function processEmail(raw: { body: string; headers: Record<string, string> }) {
const r = scoreSpam(raw.body, {
headers: raw.headers,
whitelistDomains: ['mycompany.com', 'trusted-partner.com'],
});
if (r.verdict === 'ham') return 'inbox';
if (r.verdict === 'maybe_spam') return 'review';
return 'spam';
}Get a breakdown of why something was flagged
const r = scoreSpam(suspiciousEmail);
console.log('Triggered rules:');
for (const rule of r.triggeredRules) {
console.log(` [${rule.score > 0 ? '+' : ''}${rule.score}] ${rule.id}: ${rule.description}`);
}Privacy
100% local — no data leaves your machine. No network requests, no telemetry.
License
MIT © spam-score contributors
