pm-email-validator
v1.0.1
Published
The explainable email intelligence library. Not just valid/invalid - understand WHY with provider detection, canonical normalization, risk signals, and confidence scoring.
Maintainers
Readme
pm-email-validator
Deliverability-first email validation for Node.js with explainable results.
pm-email-validator is built for real product flows where syntax-only checks are not enough. It combines syntax validation, typo detection, disposable-domain checks, DNS signals, optional SMTP probing, and a transparent scoring model.
Why This Package
Most validators answer only one question: "Does this look like an email?"
This package answers the questions teams actually care about:
- Is this likely deliverable?
- Is this disposable/temp mail?
- Is this risky for B2B outreach or user quality?
- Why did we classify it this way?
Documentation
- Quick usage and API: this README
- Full code-flow guide:
docs/ARCHITECTURE.md
Features
- Lean default response for fast production usage
- Optional full detail mode for intelligence/risk analysis
- Deterministic scoring with configurable weights and thresholds
- DNS checks: MX, SPF, DMARC, A/AAAA existence
- Optional SMTP probing (rate-limited, best-effort)
- Catch-all detection (optional)
- Role-email and suspicious pattern detection
- Batch validation helpers and stream API
- CLI for one-off and file-based validation
- Debug API with step-by-step trace
Install
npm install pm-email-validatorNode Version
- Node.js
>= 18
Quick Start
import { verifyEmail } from "pm-email-validator";
const result = await verifyEmail("[email protected]");
console.log(result.verdict);
console.log(result.reasons);Output Modes
Default: output: "basic"
verifyEmail() returns a lean payload by default.
import { verifyEmail } from "pm-email-validator";
const result = await verifyEmail("[email protected]");
// lean responseDetailed: output: "full"
Use this when you need intelligence and score internals.
const detailed = await verifyEmail("[email protected]", { output: "full" });
// includes intelligence, risks, scoreBreakdownTrace: debugVerifyEmail()
Use this for debugging and education.
import { debugVerifyEmail } from "pm-email-validator";
const { result, trace } = await debugVerifyEmail("[email protected]");
console.log(result.verdict);
console.log(trace);Result Types
PMEmailResult (lean)
export interface PMEmailResult {
email: string;
normalizedEmail: string;
verdict: "valid" | "risky" | "unknown" | "invalid";
confidence: number;
reasons: string[];
checks: {
syntax: { ok: boolean; mode: "basic" | "rfc"; reason?: string };
typo: { ok: boolean; suggestedEmail?: string };
disposable: { ok: boolean; source?: string };
dns: {
domainExists?: boolean;
mx?: boolean;
spf?: boolean;
dmarc?: boolean;
mxHosts?: string[];
};
smtp: { enabled: boolean; status: "valid" | "invalid" | "unknown"; code?: string };
catchAll?: { checked: boolean; isCatchAll: boolean; confidence: string };
roleEmail: { isRole: boolean; prefix?: string; category?: string };
patterns: { detected: boolean; signals: string[] };
};
}PMEmailDetailedResult (full)
export interface PMEmailDetailedResult extends PMEmailResult {
intelligence: Intelligence;
risks: RiskItem[];
scoreBreakdown: ScoreBreakdownItem[];
}Validation Flow
The internal pipeline is deterministic and ordered:
- Normalize input
- Syntax validation (
basicorrfc) - DNS checks
- Provider detection
- Canonicalization
- Role-email detection
- Pattern detection
- Typo detection
- Disposable-domain detection
- DNS score impacts
- Optional SMTP probe
- Optional catch-all check
- Final score and verdict
Configuration
import { verifyEmail } from "pm-email-validator";
const result = await verifyEmail("[email protected]", {
output: "basic", // "basic" (default) | "full"
mode: "basic", // "basic" | "rfc"
typoSuggestions: true,
disposable: { enabled: true },
dns: { mx: true, spf: true, dmarc: true, timeoutMs: 4000 },
smtp: { enabled: false, timeoutMs: 4000, maxConnectionsPerDomainPerMinute: 10 },
catchAll: { enabled: false, timeoutMs: 5000 },
patterns: { enabled: true },
roleEmail: { enabled: true },
scoring: {
weights: {
typo: 25,
disposable: 60,
noMx: 50,
noSpf: 10,
noDmarc: 10,
smtpInvalid: 80,
},
thresholds: { valid: 85, risky: 60, unknown: 30 },
},
cache: { dnsTtlMs: 10 * 60 * 1000, maxEntries: 2000 },
});Disposable / Temp-Mail Handling
This package treats temp mail as disposable mail.
Built-in list
By default, disposable checks use the bundled list.
Custom updatable list from file
Use a file-based provider for easy maintenance:
import { verifyEmail, loadDisposableProviderFromFile } from "pm-email-validator";
const provider = loadDisposableProviderFromFile("data/disposable_domains.txt");
const result = await verifyEmail("[email protected]", {
disposable: { enabled: true, provider },
});- File format: one domain per line
- Comment lines start with
# - Recommended source file in this repo:
data/disposable_domains.txt
Presets
Available presets:
quickstandardthoroughb2bstrict
import { getPresetOptions, verifyEmail } from "pm-email-validator";
const options = getPresetOptions("standard", {
output: "basic",
});
const result = await verifyEmail("[email protected]", options);API Reference (Core)
verifyEmail(email, options?)->Promise<PMEmailResult | PMEmailDetailedResult>debugVerifyEmail(email, options?)->Promise<DebugResult>verifyEmailBatch(emails, options?)verifyEmailStream(emails, options?)validateSyntax(email, mode?)suggestTypo(email)checkDisposable(domain, provider?)checkDNS(domain, opts, cache?)
See src/index.ts for full export surface.
CLI Usage
Single email:
pm-email-validator "[email protected]"File mode (first CSV column is email):
pm-email-validator --file emails.csv --out report.json --concurrency 20Local HTTP API (Development)
Start API:
npm run dev:apiHealth check:
GET http://localhost:3000/healthValidate:
POST http://localhost:3000/verify
Content-Type: application/json
{
"email": "[email protected]",
"options": {
"output": "basic",
"mode": "basic"
}
}Debug trace:
POST http://localhost:3000/verify/debug
Content-Type: application/json
{
"email": "[email protected]",
"options": {
"output": "full",
"mode": "rfc"
}
}Benchmark
Run local throughput benchmark:
npm run benchWith flags:
npm run bench -- --iterations=5000 --concurrency=50 --output=basic
npm run bench -- --iterations=2000 --concurrency=20 --output=fullPerformance Notes
For production throughput:
- Use
output: "basic"unless you need full intelligence payload - Keep SMTP disabled unless your use case requires mailbox probing
- Keep DNS cache enabled (default)
- Use batch APIs for large imports
Comparison
| Approach | Fast | Disposable Detection | DNS Signals | SMTP Option | Explainable Scoring |
|---|---:|---:|---:|---:|---:|
| Regex-only validator | Yes | No | No | No | No |
| Syntax + MX checker | Yes | No | Partial | No | Limited |
| External black-box API | Varies | Varies | Varies | Varies | Usually limited |
| pm-email-validator | Yes (basic mode) | Yes | Yes | Yes (opt-in) | Yes |
Security Notes
SMTP probing is opt-in and can be considered intrusive by some providers.
- Respect provider policies and legal requirements
- Expect ambiguous
unknownoutcomes (greylisting, catch-all, policy blocks)
Roadmap
- Improve typo intelligence with curated typo-to-canonical mapping
- Add optional provider metadata packs (industry/domain enrichment)
- Add benchmark profiles for network-heavy scenarios
- Expand test matrix with real-world anonymized email fixtures
- Add optional plugin hooks for custom risk signals
Development
Install:
npm installBuild:
npm run buildTest:
npm testTypecheck:
npm run typecheckLint:
npm run lintContributing
Contributions are welcome.
- Fork the repository
- Create a feature branch
- Add or update tests for your change
- Run
npm testandnpm run typecheck - Open a PR with:
- what changed
- why it changed
- any compatibility notes
Contribution Guidelines
- Keep public API changes explicit in PR description
- Prefer deterministic behavior over opaque heuristics
- Keep default path fast (
output: "basic") - Add tests for new scoring or classification logic
License
MIT
