π Advanced email validation library for Node.js with MX record checking, SMTP verification, disposable email detection, and much more. Now with batch processing, advanced caching, and detailed error reporting.
- Features
- Use Cases
- API / Cloud Service
- License
- Installation
- Quick Start
- Migration Guide (to v3.x)
- API Reference
- Configuration
- Examples
- Command-line Tool (
email-validate) - Custom Cache Injection
- Verification Transcript
- Performance & Caching
- Email Provider Databases
- Testing
- Contributing
- β RFC-5321-compliant format & TLD validation
- β MX record lookup with cache
- β Live SMTP probe with multi-port walk (25 β 587 β 465), TLS, custom step sequences
- β Disposable + free-provider detection (10k+ domains shipped as JSON)
- β Domain typo detection + suggestions (Levenshtein + curated typo map)
- β Name extraction from local-part with composite-name support
- β WHOIS-driven domain age and registration status
- β Pluggable cache (in-memory LRU, Redis, or your own backend)
- β Verification transcript β opt-in structured per-step trace including the full SMTP wire-level transcript
- β
parseSmtpErrorβ public utility to classify a free-form SMTP error string - β Batch email verification with concurrency control + per-error classification
- β Serverless adapters for AWS Lambda, Vercel (Edge + Node), and Cloudflare Workers/Durable Objects
- β
Strict TypeScript types β zero
anyinsrc/
- Increase delivery rate of email campaigns by removing spam emails
- Increase email open rate and your marketing IPs reputation
- Protect your website from spam, bots and fake emails
- Protect your product signup form from fake emails
- Protect your website forms from fake emails
- Protect your self from fraud orders and accounts using fake emails
- Integrate email address verification into your website forms
- Integrate email address verification into your backoffice administration and order processing
We offer this email verification and validation and more advanced features in our Scalable Cloud API Service Offering - You could try it here Email Verification
email-validator-js is licensed under Business Source License 1.1.
| Use Case | Is a commercial license required? |
|---|---|
| Exploring email-validator-js for your own research, hobbies, and testing purposes | No |
| Using email-validator-js to build a proof-of-concept application | No |
| Using email-validator-js to build revenue-generating applications | Yes |
| Using email-validator-js to build software that is provided as a service (SaaS) | Yes |
| Forking email-validator-js for any production purposes | Yes |
π For commercial licensing, visit email-check.app/license/email-validator or contact us at [email protected].
bun add @emailcheck/email-validator-js
# or
npm install @emailcheck/email-validator-js
# or
pnpm add @emailcheck/email-validator-js- Node.js >= 22 (currently-supported LTS lines: 22 Maintenance, 24 Active)
- TypeScript >= 4.0 (for TypeScript users)
- Bun >= 1.3 (test runner, package manager, dev tooling)
- Node.js >= 24 only needed for
semantic-releaseduring the publish step
- Rollup builds CJS + ESM bundles for the main package and the serverless entry
bun testfor the unit + mocked-IO suite (no jest, no ts-jest)- Source data (common names, typo patterns, WHOIS servers) lives in
src/data/*.json
import { verifyEmail } from '@emailcheck/email-validator-js';
// Basic usage
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
smtpPerAttemptTimeoutMs: 3000, // bounds a single MX Γ port attempt
smtpTotalDeadlineMs: 8000, // bounds the entire SMTP probe (NEW in v5)
});
console.log(result.validFormat); // true
console.log(result.validMx); // true or false
console.log(result.validSmtp); // true or false
β οΈ Breaking Change in v3.x: Enum values and constants now usecamelCaseinstead ofSCREAMING_SNAKE_CASE. See Migration Guide for details.
Version 3.x introduces a breaking change to improve code consistency with TypeScript/JavaScript conventions. All enum values and constants now use camelCase instead of SCREAMING_SNAKE_CASE.
| Before (v2.x) | After (v3.x) |
|---|---|
EmailProvider.GMAIL |
EmailProvider.gmail |
EmailProvider.YAHOO |
EmailProvider.yahoo |
EmailProvider.HOTMAIL_B2C |
EmailProvider.hotmailB2c |
VerificationErrorCode.INVALID_FORMAT |
VerificationErrorCode.invalidFormat |
VerificationErrorCode.NO_MX_RECORDS |
VerificationErrorCode.noMxRecords |
SMTPStep.GREETING |
SMTPStep.greeting |
SMTPStep.EHLO |
SMTPStep.ehlo |
SMTPStep.MAIL_FROM |
SMTPStep.mailFrom |
| Before (v2.x) | After (v3.x) |
|---|---|
CHECK_IF_EMAIL_EXISTS_CONSTANTS.DEFAULT_TIMEOUT |
checkIfEmailExistsConstants.defaultTimeout |
CHECK_IF_EMAIL_EXISTS_CONSTANTS.GMAIL_DOMAINS |
checkIfEmailExistsConstants.gmailDomains |
WHOIS_SERVERS |
whoisServers |
// Before
import { EmailProvider, VerificationErrorCode, SMTPStep } from '@emailcheck/email-validator-js';
if (provider === EmailProvider.GMAIL) { /* ... */ }
if (error === VerificationErrorCode.INVALID_FORMAT) { /* ... */ }
const steps = [SMTPStep.GREETING, SMTPStep.EHLO, SMTPStep.MAIL_FROM];
// After
import { EmailProvider, VerificationErrorCode, SMTPStep } from '@emailcheck/email-validator-js';
if (provider === EmailProvider.gmail) { /* ... */ }
if (error === VerificationErrorCode.invalidFormat) { /* ... */ }
const steps = [SMTPStep.greeting, SMTPStep.ehlo, SMTPStep.mailFrom];// Before
import { CHECK_IF_EMAIL_EXISTS_CONSTANTS } from '@emailcheck/email-validator-js';
const timeout = CHECK_IF_EMAIL_EXISTS_CONSTANTS.DEFAULT_TIMEOUT;
const domains = CHECK_IF_EMAIL_EXISTS_CONSTANTS.GMAIL_DOMAINS;
// After
import { checkIfEmailExistsConstants } from '@emailcheck/email-validator-js';
const timeout = checkIfEmailExistsConstants.defaultTimeout;
const domains = checkIfEmailExistsConstants.gmailDomains;// Before
switch (provider) {
case EmailProvider.YAHOO:
// Handle Yahoo
break;
case EmailProvider.HOTMAIL_B2C:
// Handle Hotmail
break;
}
// After
switch (provider) {
case EmailProvider.yahoo:
// Handle Yahoo
break;
case EmailProvider.hotmailB2c:
// Handle Hotmail
break;
}-
String values remain unchanged: The underlying string values (e.g.,
'gmail','INVALID_FORMAT') are preserved. Only the property names changed. -
Runtime compatibility: If you're comparing enum values to strings from external sources, the string values still work:
// Still works in v3.x if (provider === 'gmail') { /* ... */ }
-
TypeScript strict mode: Ensure you update all references before compiling, or TypeScript will report errors.
-
Test your code: After updating, run your test suite to ensure all enum and constant references are updated correctly.
If you're using an IDE with refactoring support (like VS Code), you can use find-and-replace:
- Find all references to old enum values
- Replace with new camelCase versions
- Run TypeScript compiler to verify no errors
- π Check the API Reference for updated enum definitions
- π¬ Open an issue if you encounter problems
- π§ Contact [email protected]
Comprehensive email verification with detailed results and error codes.
Parameters:
emailAddress(string, required) β Email address to verify.verifyMx(boolean) β Resolve MX records (default:true).verifySmtp(boolean) β Run live SMTP probe (default:false).checkDisposable(boolean) β Disposable-provider list check (default:true).checkFree(boolean) β Free-provider list check (default:true).detectName(boolean) β Extract first/last name from local-part (default:false).suggestDomain(boolean) β Suggest a corrected domain on typos (default:true).checkDomainAge(boolean) β WHOIS creation-date lookup (default:false).checkDomainRegistration(boolean) β WHOIS registration / expiry / lock lookup (default:false).skipMxForDisposable(boolean) β Skip MX/SMTP for disposable addresses (default:false).skipDomainWhoisForDisposable(boolean) β Skip WHOIS for disposable addresses (default:false).smtpPort(number) β Force a specific port for the SMTP probe (overrides the[25, 587, 465]walk).- SMTP time-budget controls (NEW in v5):
smtpPerAttemptTimeoutMs(number) β Per-MX Γ port budget in ms (default:4000).smtpTotalDeadlineMs(number) β Hard cap on total wall-clock for the SMTP probe. Use this from a request handler with a tight latency budget. Default: unbounded.smtpMaxConsecutiveFailures(number) β Bail after N connection-class failures in a row (connection_error/connection_timeout/connection_closed). Default: unbounded.smtpMaxMxHosts(number) β Cap the MX walk to the first N hostnames. Default: unbounded.smtpRetry({ attempts, delayMs?, backoff? }) β Retry connection-class failures on the same MX Γ port. Default: no retries.
- SMTP envelope controls (anti-spam β recommended for production):
smtpSender(SMTPSenderStrategy) β Strategy for theMAIL FROM:envelope. The library default is<recipient@domain>, which blocklists key on as the textbook verification-probe fingerprint. Pick a deliberate strategy:{ kind: 'null-sender' }βMAIL FROM:<>(RFC 5321 Β§4.5.5; DSN-shaped, best on Gmail / Outlook).{ kind: 'fixed', address: '[email protected]' }β fixed real address (best with valid SPF / PTR / DMARC).{ kind: 'random-at-recipient', localPrefix? }β random local-part on the recipient's domain.{ kind: 'random-at-domain', domain, localPrefix? }β random local-part on a configured domain.{ kind: 'custom', build: r => ... }β full escape hatch.
smtpHeloHostname(string) β Hostname presented to the MX inEHLO/HELO. Default:'localhost'(a spam-bot signature from a public IP β override with a real FQDN in production).
whoisTimeoutMs(number) β Per-WHOIS-query timeout (default:5000).debug(boolean) β Per-lineconsole.debugtrace (default:false).captureTranscript(boolean) β Populateresult.transcriptwith a per-step structured trace (default:false).nameDetectionMethod(function) β Override the default name-detection heuristic.domainSuggestionMethod(function) β Override the default typo-suggestion heuristic.commonDomains(string[]) β Custom canonical-domain list for the typo suggester.cache(Cache) β Optional shared cache (MX, WHOIS, disposable / free, SMTP, domain results all stored).
Returns:
{
email: string;
validFormat: boolean;
validMx: boolean | null;
validSmtp: boolean | null;
isDisposable: boolean;
isFree: boolean;
detectedName?: DetectedName | null;
domainAge?: DomainAgeInfo | null;
domainRegistration?: DomainRegistrationInfo | null;
domainSuggestion?: DomainSuggestion | null;
metadata?: {
verificationTime: number;
cached: boolean;
error?: VerificationErrorCode;
};
}Verify multiple emails in parallel with concurrency control.
Parameters:
emailAddresses(string[], required): Array of emails to verifyconcurrency(number): Parallel processing limit (default: 5)detectName(boolean): Detect names from email addressessuggestDomain(boolean): Enable domain typo suggestions- Other parameters from
verifyEmail
Returns:
{
results: Map<string, VerificationResult>;
summary: {
total: number;
valid: number;
invalid: number;
errors: number;
processingTime: number;
};
}Detect first and last name from email address.
const name = detectName('[email protected]');
// Returns: { firstName: 'John', lastName: 'Doe', confidence: 0.9 }Detection Patterns:
- Dot separator:
john.doeβ John Doe (90% confidence) - Underscore:
jane_smithβ Jane Smith (80% confidence) - Hyphen:
mary-johnsonβ Mary Johnson (80% confidence) - CamelCase:
johnDoeβ John Doe (70% confidence) - Composite names:
mo1.test2β Mo1 Test2 (60% confidence) - Mixed alphanumeric:
user1.admin2β User1 Admin2 (60% confidence) - Smart number handling:
john.doe123β John Doe (80% confidence) - Contextual suffixes:
john.doe.devβ John Doe (70% confidence) - Single name:
aliceβ Alice (50% confidence)
Enhanced Features:
- Removes email aliases (text after +)
- Smart handling of numbers (preserves in composite names, removes trailing)
- Recognizes contextual suffixes (dev, company, sales, years)
- Handles complex multi-part names
- Proper name capitalization
- Filters out common non-name prefixes (admin, support, info, etc.)
Advanced name detection with custom method support.
const customMethod = (email: string) => {
// Your custom logic
return { firstName: 'Custom', lastName: 'Name', confidence: 1.0 };
};
const name = detectNameFromEmail({
email: '[email protected]',
customMethod: customMethod
});Parameters:
email(string): Email addresscustomMethod(function): Custom detection logic
The default name detection implementation, exported for custom extensions.
Clean a name by removing special characters (dots, underscores, asterisks). Specifically designed for Algorithm name processing.
import { cleanNameForAlgorithm } from '@emailcheck/email-validator-js';
const cleanedName = cleanNameForAlgorithm('john.doe_smith*');
// Returns: 'johndoesmith'
const cleanedName2 = cleanNameForAlgorithm('first_name.last');
// Returns: 'firstnamelast'Enhanced name detection for Algorithm with aggressive cleaning. Removes dots, underscores, and asterisks from detected names.
import { detectNameForAlgorithm } from '@emailcheck/email-validator-js';
const result = detectNameForAlgorithm('[email protected]');
// Returns: { firstName: 'John', lastName: 'Doesmith', confidence: 0.9025 }
// Compared to regular detection:
import { detectName } from '@emailcheck/email-validator-js';
const normalResult = detectName('[email protected]');
// Returns: { firstName: 'John', lastName: 'Doe_smith', confidence: 0.95 }Key Differences:
- Removes all dots (.), underscores (_), and asterisks (*) from detected names
- Slightly reduces confidence (95% of original) due to cleaning process
- Ideal for systems requiring clean, sanitized names without special characters
- Normalizes multiple spaces to single spaces
Detect and suggest corrections for misspelled email domains.
const suggestion = suggestEmailDomain('[email protected]');
// Returns: { original: 'user@gmial.com', suggested: '[email protected]', confidence: 0.95 }
// With custom domain list
const customDomains = ['company.com', 'enterprise.org'];
const customSuggestion = suggestEmailDomain('[email protected]', customDomains);Features:
- 70+ common email domains by default
- String similarity algorithm
- Known typo patterns (95% confidence)
- Smart thresholds based on domain length
- 24-hour caching for performance
Advanced domain suggestion with custom method support.
const suggestion = suggestDomain({
domain: 'gmial.com',
customMethod: myCustomMethod,
commonDomains: ['company.com']
});Parameters:
domain(string): Domain to checkcustomMethod(function): Custom suggestion logiccommonDomains(string[]): Custom domain list
The default domain suggestion implementation, exported for custom extensions.
Check if a domain is in the common domains list.
isCommonDomain('gmail.com'); // true
isCommonDomain('mycompany.com'); // false
// With custom list
isCommonDomain('mycompany.com', ['mycompany.com']); // trueCalculate similarity score between two domains (0-1).
getDomainSimilarity('gmail.com', 'gmial.com'); // 0.8
getDomainSimilarity('gmail.com', 'yahoo.com'); // 0.3Note: WHOIS functions use PSL (Public Suffix List) validation to ensure domain validity before performing lookups. Invalid domains or domains without valid TLDs will return
null.
Get domain age information via WHOIS lookup.
const ageInfo = await getDomainAge('mydomain.com');
// Returns:
// {
// domain: 'mydomain.com',
// creationDate: Date,
// ageInDays: 7890,
// ageInYears: 21.6,
// expirationDate: Date,
// updatedDate: Date
// }
// Works with email addresses and URLs too
await getDomainAge('[email protected]');
await getDomainAge('https://mydomain.com/path');Parameters:
domain(string): Domain, email, or URL to checktimeout(number): Timeout in milliseconds (default: 5000)
Returns: DomainAgeInfo object or null if lookup fails
getDomainRegistrationStatus(domain: string, timeout?: number): Promise<DomainRegistrationInfo | null>
Get detailed domain registration status via WHOIS.
const status = await getDomainRegistrationStatus('mydomain.com');
// Returns:
// {
// domain: 'mydomain.com',
// isRegistered: true,
// isAvailable: false,
// status: ['clientTransferProhibited'],
// registrar: 'Example Registrar',
// nameServers: ['ns1.mydomain.com', 'ns2.mydomain.com'],
// expirationDate: Date,
// isExpired: false,
// daysUntilExpiration: 365,
// isPendingDelete: false,
// isLocked: true
// }Parameters:
domain(string): Domain, email, or URL to checktimeout(number): Timeout in milliseconds (default: 5000)
Returns: DomainRegistrationInfo object or null if lookup fails
Features:
- Supports 50+ TLDs with specific WHOIS servers
- Automatic WHOIS server discovery for unknown TLDs
- Parses various WHOIS response formats
- Uses PSL (Public Suffix List) for domain validation
- 1-hour result caching
- Extracts domain from emails and URLs
isDisposableEmail(emailOrDomain: string, cache?: ICache, options?: { skipMxCheck?: boolean; skipDomain?: boolean }): boolean
Check if email uses a disposable provider.
// Basic usage
isDisposableEmail('[email protected]'); // true
isDisposableEmail('tempmail.com'); // true
isDisposableEmail('gmail.com'); // false
// With options
isDisposableEmail('[email protected]', null, {
skipMxCheck: true, // Skip MX record validation
skipDomain: true // Skip domain validation
}); // trueisFreeEmail(emailOrDomain: string, cache?: ICache, options?: { skipMxCheck?: boolean; skipDomain?: boolean }): boolean
Check if email uses a free provider.
// Basic usage
isFreeEmail('[email protected]'); // true
isFreeEmail('yahoo.com'); // true
isFreeEmail('corporate.com'); // false
// With options
isFreeEmail('[email protected]', null, {
skipMxCheck: true, // Skip MX record validation
skipDomain: true // Skip domain validation
}); // trueValidate email format (RFC 5321 compliant).
isValidEmail('[email protected]'); // true
isValidEmail('invalid.email'); // falseValidation Rules:
- Proper @ symbol placement
- Local part max 64 characters
- Domain max 253 characters
- No consecutive dots
- No leading/trailing dots
- Valid domain TLD
Validate if a domain has a valid TLD.
isValidEmailDomain('mydomain.com'); // true
isValidEmailDomain('example.invalid'); // falseimport { getDefaultCache, clearDefaultCache, resetDefaultCache } from '@emailcheck/email-validator-js';
// Get the default cache instance (singleton)
const defaultCache = getDefaultCache();
// Clear all entries from the default cache
clearDefaultCache();
// Reset to a fresh cache instance
resetDefaultCache();interface DetectedName {
firstName?: string;
lastName?: string;
confidence: number; // 0-1 scale
}interface DomainSuggestion {
original: string;
suggested: string;
confidence: number; // 0-1 scale
}type NameDetectionMethod = (email: string) => DetectedName | null;type DomainSuggestionMethod = (domain: string) => DomainSuggestion | null;interface DomainAgeInfo {
domain: string;
creationDate: Date;
ageInDays: number;
ageInYears: number;
expirationDate: Date | null;
updatedDate: Date | null;
}interface DomainRegistrationInfo {
domain: string;
isRegistered: boolean;
isAvailable: boolean;
status: string[];
registrar: string | null;
nameServers: string[];
expirationDate: Date | null;
isExpired: boolean;
daysUntilExpiration: number | null;
isPendingDelete?: boolean;
isLocked?: boolean;
}Array of 70+ common email domains used for typo detection.
import { COMMON_EMAIL_DOMAINS } from '@emailcheck/email-validator-js';
console.log(COMMON_EMAIL_DOMAINS);
// ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', ...]Includes:
- Popular free providers (Gmail, Yahoo, Outlook, etc.)
- Business email services (Google Workspace, Microsoft, etc.)
- Privacy-focused providers (ProtonMail, Tutanota, etc.)
- Regional providers (GMX, Yandex, QQ, etc.)
- Hosting services (GoDaddy, Namecheap, etc.)
enum VerificationErrorCode {
invalidFormat = 'INVALID_FORMAT',
invalidDomain = 'INVALID_DOMAIN',
noMxRecords = 'NO_MX_RECORDS',
smtpConnectionFailed = 'SMTP_CONNECTION_FAILED',
smtpTimeout = 'SMTP_TIMEOUT',
mailboxNotFound = 'MAILBOX_NOT_FOUND',
mailboxFull = 'MAILBOX_FULL',
networkError = 'NETWORK_ERROR',
disposableEmail = 'DISPOSABLE_EMAIL',
freeEmailProvider = 'FREE_EMAIL_PROVIDER'
}Set a timeout in milliseconds for the smtp connection. Default: 4000.
Enable or disable domain checking. This is done in two steps:
- Verify that the domain does indeed exist
- Verify that the domain has valid MX records
Default: false.
Enable or disable mailbox checking. Only a few SMTP servers allow this, and even then whether it works depends on your IP's reputation with those servers. This library performs a best effort validation:
- It returns
nullfor Yahoo addresses, for failed connections, for unknown SMTP errors - It returns
truefor valid SMTP responses - It returns
falsefor SMTP errors specific to the address's formatting or mailbox existence
Default: false.
Check if the email domain is a known disposable email provider. Default: false.
Check if the email domain is a known free email provider. Default: false.
Return detailed verification results with error codes. Default: false.
Number of retry attempts for transient failures. Default: 1.
import { verifyEmail } from '@emailcheck/email-validator-js';
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
smtpPerAttemptTimeoutMs: 3000,
});
console.log(result.validFormat); // true
console.log(result.validMx); // true
console.log(result.validSmtp); // trueimport { verifyEmail } from '@emailcheck/email-validator-js';
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
checkDisposable: true,
checkFree: true
});
// result.validFormat: true
// result.validMx: true
// result.validSmtp: true
// result.isDisposable: false
// result.isFree: false
// result.metadata.verificationTime: 125import { verifyEmailBatch } from '@emailcheck/email-validator-js';
const emails = ['[email protected]', '[email protected]', '[email protected]'];
const result = await verifyEmailBatch({
emailAddresses: emails,
concurrency: 5,
verifyMx: true,
checkDisposable: true,
checkFree: true
});
// result.summary.valid: 2
// result.summary.invalid: 1
// result.summary.processingTime: 234import { verifyMailboxSMTP, getDefaultCache } from '@emailcheck/email-validator-js';
// Direct SMTP probe β caller already has resolved MX records.
const { smtpResult, port, cached, portCached } = await verifyMailboxSMTP({
local: 'user',
domain: 'example.com',
mxRecords: ['mx.example.com'],
options: {
ports: [25, 587, 465], // Plain β STARTTLS-able β implicit-TLS
perAttemptTimeoutMs: 5000, // Per-MX Γ port budget (renamed from `timeout` in v5)
totalDeadlineMs: 12_000, // Hard cap on total wall-clock (NEW in v5)
maxConsecutiveFailures: 3, // Bail after N connection-class failures (NEW in v5)
cache: getDefaultCache(), // Per-isolate verdict + port cache
debug: false,
tlsConfig: { // Renamed from `tls` in v5
rejectUnauthorized: false,
minVersion: 'TLSv1.2',
},
heloHostname: 'your-domain.com', // EHLO/HELO identity (renamed from `hostname` in v5)
startTls: 'auto', // STARTTLS upgrade on plaintext ports (NEW in v5)
pipelining: 'auto', // Use SMTP PIPELINING when advertised
captureTranscript: false, // See "SMTP Transcript Capture" below
},
});
console.log(`SMTP result: ${smtpResult.isDeliverable} via port ${port}`);
console.log(`canConnectSmtp=${smtpResult.canConnectSmtp}, error=${smtpResult.error ?? 'none'}`);Don't want to think about timeouts and retries? Pick a preset that matches your deployment shape:
import { verifyEmail, VERIFY_EMAIL_PRESETS } from '@emailcheck/email-validator-js';
// Lambda / Vercel / Cloudflare-Workers handler
await verifyEmail({
emailAddress: '[email protected]',
verifySmtp: true,
...VERIFY_EMAIL_PRESETS.serverless,
});
// Long-running worker / dyno
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.dedicated });
// Bulk processing
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.batch });
// Form-autocomplete UX (sub-3s)
await verifyEmail({ ..., ...VERIFY_EMAIL_PRESETS.fast });| Preset | Per-attempt | Total deadline | Max consecutive failures | Max MX | Retry |
|---|---|---|---|---|---|
serverless |
2500 ms | 5 s | 3 | 2 | none β fail fast |
dedicated |
5000 ms | 30 s | unbounded | unbounded | 1 retry, 500 ms exp backoff |
batch |
10 000 ms | 60 s | unbounded | unbounded | 2 retries, 1 s exp backoff |
fast |
1500 ms | 3 s | 2 | 1 | none β fail fast |
SMTP_PRESETS is a parallel set with the unprefixed field names for verifyMailboxSMTP({ options }) callers β same values, same shape.
You can spread + override:
await verifyEmail({
emailAddress: '[email protected]',
...VERIFY_EMAIL_PRESETS.serverless,
smtpTotalDeadlineMs: 3000, // tighter than the preset's 5s
});The probe walks mxRecords Γ ports (worst-case 4 Γ 3 = 12 attempts at 3s each = 36s). Four orthogonal knobs let you bound that:
import { verifyEmail, verifyMailboxSMTP } from '@emailcheck/email-validator-js';
await verifyEmail({
emailAddress: '[email protected]',
verifySmtp: true,
smtpPerAttemptTimeoutMs: 3000, // Bound a single MX Γ port attempt
smtpTotalDeadlineMs: 5000, // Hard cap on total wall-clock
smtpMaxConsecutiveFailures: 3, // Bail after 3 connection failures in a row
smtpMaxMxHosts: 2, // Try only the first 2 MXes
smtpRetry: { // Retry connection-class failures
attempts: 1,
delayMs: 200,
backoff: 'exponential', // or 'fixed'
},
});
// On verifyMailboxSMTP directly, the same knobs are unprefixed:
await verifyMailboxSMTP({
local: 'alice', domain: 'example.com', mxRecords: [...],
options: {
perAttemptTimeoutMs: 3000,
totalDeadlineMs: 5000,
maxConsecutiveFailures: 3,
maxMxHosts: 2,
retry: { attempts: 1, delayMs: 200, backoff: 'exponential' },
},
});| Knob | Default | When to use |
|---|---|---|
(smtp)PerAttemptTimeoutMs |
4000 (verifyEmail) / 3000 (verifyMailboxSMTP) |
Per-MX Γ port budget. Bound a single attempt. |
(smtp)TotalDeadlineMs |
unbounded | Hard wall-clock cap. Use from a request handler with a tight latency budget. |
(smtp)MaxConsecutiveFailures |
unbounded | Cut off probes when the network path is dead. Counter resets on any non-connection-class outcome. |
(smtp)MaxMxHosts |
unbounded | Cap the MX walk regardless of how many DNS returned. |
(smtp)Retry |
no retries | Retry connection-class failures on the same MX Γ port. Definitive answers (250 / 550 / 552) are never retried. |
PerAttemptTimeout and TotalDeadline are orthogonal β use both when you have both a per-attempt SLO and a hard caller-side budget.
Override the default greeting β EHLO β MAIL FROM β RCPT TO walk for advanced cases:
import { verifyMailboxSMTP, SMTPStep } from '@emailcheck/email-validator-js';
const { smtpResult } = await verifyMailboxSMTP({
local: 'user',
domain: 'example.com',
mxRecords: ['mx.example.com'],
options: {
sequence: {
steps: [SMTPStep.greeting, SMTPStep.helo, SMTPStep.mailFrom, SMTPStep.rcptTo],
from: '<[email protected]>', // Custom MAIL FROM payload
},
ports: [587, 465],
},
});Set captureTranscript: true to get the full server reply log and command sequence on the result. Useful for debugging delivery quirks or building admin UIs:
import { verifyMailboxSMTP } from '@emailcheck/email-validator-js';
const { smtpResult } = await verifyMailboxSMTP({
local: 'user',
domain: 'example.com',
mxRecords: ['mx.example.com'],
options: { ports: [25, 587], perAttemptTimeoutMs: 5000, captureTranscript: true },
});
// Both arrays aggregate across every MX Γ port attempted, prefixed:
// "mx.example.com:25|s| 220 mx.example.com ESMTP"
// "25|c| EHLO localhost"
console.log(smtpResult.transcript);
console.log(smtpResult.commands);For verification across the entire pipeline (syntax / disposable / free / MX / SMTP / WHOIS / name / suggestion), enable captureTranscript on verifyEmail to get a structured per-step trace β see Verification Transcript below.
Examples are grouped by topic under examples/. See examples/README.md for the full index.
# Bun runs TS directly β no compilation step
bun run examples/smtp/usage.ts
bun run examples/smtp/enhanced.ts
bun run examples/cache/custom-memory.ts
bun run examples/high-level/advanced-usage.ts
bun run examples/integrations/algolia.tsAfter installation in your own project (Node 22+):
node --experimental-strip-types examples/smtp/usage.tsimport { detectName, verifyEmail } from '@emailcheck/email-validator-js';
// Standalone name detection - now with composite name support
const name = detectName('[email protected]');
// name: { firstName: 'John', lastName: 'Doe', confidence: 0.9 }
// Handle alphanumeric composite names
const composite = detectName('[email protected]');
// composite: { firstName: 'Mo1', lastName: 'Test2', confidence: 0.6 }
// Smart handling of numbers and suffixes
const withNumbers = detectName('[email protected]');
// withNumbers: { firstName: 'John', lastName: 'Doe', confidence: 0.8 }
const withSuffix = detectName('[email protected]');
// withSuffix: { firstName: 'Jane', lastName: 'Smith', confidence: 0.7 }
// Integrated with email verification
const result = await verifyEmail({
emailAddress: '[email protected]',
detectName: true
});
// result.detectedName: { firstName: 'Jane', lastName: 'Smith', confidence: 0.8 }
// Custom detection method
const customMethod = (email: string) => {
// Your custom logic here
return { firstName: 'Custom', lastName: 'Name', confidence: 1.0 };
};
const resultCustom = await verifyEmail({
emailAddress: '[email protected]',
detectName: true,
nameDetectionMethod: customMethod
});import { suggestEmailDomain, verifyEmail } from '@emailcheck/email-validator-js';
// Standalone domain suggestion
const suggestion = suggestEmailDomain('[email protected]');
// suggestion: { original: 'user@gmial.com', suggested: '[email protected]', confidence: 0.95 }
// Integrated with email verification (enabled by default in detailed mode)
const result = await verifyEmail({
emailAddress: '[email protected]',
suggestDomain: true // Default: true for detailed verification
});
// result.domainSuggestion: { original: 'john@yaho.com', suggested: '[email protected]', confidence: 0.9 }
// With custom domain list
const customDomains = ['company.com', 'enterprise.org'];
const resultCustom = await verifyEmail({
emailAddress: '[email protected]',
suggestDomain: true,
commonDomains: customDomains
});
// resultCustom.domainSuggestion: { suggested: 'user@company.com', confidence: 0.85 }When a domain does not exist or has no MX records:
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true
});
// result.validFormat: true (format is valid)
// result.validMx: false (no MX records)
// result.validSmtp: null (couldn't be performed)const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
checkDisposable: true,
checkFree: true
});
if (!result.validFormat) {
console.log('Invalid email format');
} else if (!result.validMx) {
console.log('Invalid domain - no MX records');
} else if (result.isDisposable) {
console.log('Disposable email detected');
} else if (result.metadata?.error) {
switch (result.metadata.error) {
case VerificationErrorCode.disposableEmail:
console.log('Rejected: Disposable email');
break;
case VerificationErrorCode.noMxRecords:
console.log('Rejected: Invalid domain');
break;
case VerificationErrorCode.mailboxNotFound:
console.log('Rejected: Mailbox does not exist');
break;
}
}const emails = [
'[email protected]',
'[email protected]',
'[email protected]',
// ... hundreds more
];
const batch = await verifyEmailBatch({
emailAddresses: emails,
concurrency: 10, // Process 10 emails simultaneously
verifyMx: true,
checkDisposable: true,
detailed: true
});
console.log(`Processed ${batch.summary.total} emails`);
console.log(`Valid: ${batch.summary.valid}`);
console.log(`Invalid: ${batch.summary.invalid}`);
console.log(`Time: ${batch.summary.processingTime}ms`);
// Filter out invalid emails
const validEmails = [];
for (const [email, result] of batch.results) {
if (result.validFormat) {
validEmails.push(email);
}
}// First verification - hits DNS and SMTP
const first = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true
});
// Takes ~500ms
// Second verification - uses cache
const second = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true
});
// Takes ~1ms (cached)
// Clear cache if needed
clearAllCaches();The package ships an email-validate binary that runs the full validation
pipeline against one address, captures a structured transcript, prints the
result to stdout, and saves the JSON result to ./logs/ by default.
# One-off check β pulls the latest published version, no install required
npx -p @emailcheck/email-validator-js email-validate [email protected]
# Same with bunx / pnpm dlx
bunx -p @emailcheck/email-validator-js email-validate [email protected]
pnpm dlx -p @emailcheck/email-validator-js email-validate [email protected]
# Pin a version (avoids npx caching surprises in CI)
npx -p @emailcheck/[email protected] email-validate [email protected]The
-p <package>form is the safest because the bin name (email-validate) differs from the package name. The shorthandnpx @emailcheck/email-validator-js [email protected]also works since the package has exactly one bin.
bun add -g @emailcheck/email-validator-js
# or: npm i -g @emailcheck/email-validator-js
# or: pnpm add -g @emailcheck/email-validator-js# Quick interactive check β full pipeline, pretty colored output
email-validate [email protected]
# Skip the SMTP probe (fast, just format / MX / lists / typos)
email-validate [email protected] --no-smtp
# Add WHOIS age + registration for full domain reputation picture
email-validate [email protected] --whois-age --whois-registration
# Pipe JSON through jq
email-validate [email protected] --format json --quiet --no-log-file | jq
# Use the exit code in shell scripts (0 = ok, 1 = undeliverable / invalid)
if email-validate "$EMAIL" --quiet --no-log-file > /dev/null; then
echo "good email"
fi
# Pin to a single SMTP port + custom HELO + custom log path
email-validate [email protected] --port 587 --hostname mta.acme.com --log-dir /var/log/email
# Debug a delivery quirk β full transcript + console logs
email-validate [email protected] --debug --format prettyThe CLI uses interactive-friendly defaults different from the library defaults (which favor speed over thoroughness for batch use):
| Flag | CLI default | Library default |
|---|---|---|
--smtp |
on | off |
--detect-name |
on | off |
--whois-age / --whois-registration |
off | off |
--captureTranscript |
on | off |
--log-dir |
./logs |
n/a |
The default config writes a JSON result to ./logs/email-validate-<timestamp>-<email>.json after every run.
The CLI parser, formatter, and runner are also exported as a module so you can
embed email-validate semantics in your own tooling:
import { parseArgs, run } from '@emailcheck/email-validator-js/cli';
const parsed = parseArgs(['[email protected]', '--no-smtp', '--format', 'json']);
if (parsed.kind === 'args') {
const exitCode = await run(parsed);
process.exit(exitCode);
}Run email-validate --help to see every flag, or read
examples/cli-usage.md for end-to-end recipes.
The library supports parameter-based cache injection, allowing you to use custom cache backends like Redis, Memcached, or any LRU-compatible cache implementation.
The library includes a built-in LRU cache for all operations. By default, it uses a lazy-loaded singleton cache instance.
import { verifyEmail } from '@emailcheck/email-validator-js';
// No cache setup needed - uses default LRU cache automatically
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true
});
// Subsequent calls with the same email will use cached results
const result2 = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true
});Create your own cache by implementing the ICache interface:
import { verifyEmail, type ICache, ICacheStore, DEFAULT_CACHE_OPTIONS } from '@emailcheck/email-validator-js';
import { LRUAdapter } from '@emailcheck/email-validator-js';
// Create custom cache with LRU adapters
const customCache: ICache = {
mx: new LRUAdapter<string[]>(DEFAULT_CACHE_OPTIONS.maxSize.mx, DEFAULT_CACHE_OPTIONS.ttl.mx),
disposable: new LRUAdapter<boolean>(DEFAULT_CACHE_OPTIONS.maxSize.disposable, DEFAULT_CACHE_OPTIONS.ttl.disposable),
free: new LRUAdapter<boolean>(DEFAULT_CACHE_OPTIONS.maxSize.free, DEFAULT_CACHE_OPTIONS.ttl.free),
domainValid: new LRUAdapter<boolean>(DEFAULT_CACHE_OPTIONS.maxSize.domainValid, DEFAULT_CACHE_OPTIONS.ttl.domainValid),
smtp: new LRUAdapter<boolean | null>(DEFAULT_CACHE_OPTIONS.maxSize.smtp, DEFAULT_CACHE_OPTIONS.ttl.smtp),
domainSuggestion: new LRUAdapter<{ suggested: string; confidence: number } | null>(
DEFAULT_CACHE_OPTIONS.maxSize.domainSuggestion,
DEFAULT_CACHE_OPTIONS.ttl.domainSuggestion
),
whois: new LRUAdapter<any>(DEFAULT_CACHE_OPTIONS.maxSize.whois, DEFAULT_CACHE_OPTIONS.ttl.whois),
};
// Use with email verification
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
cache: customCache // Pass the cache instance
});import { verifyEmail, type ICache, ICacheStore } from '@emailcheck/email-validator-js';
import { RedisAdapter } from '@emailcheck/email-validator-js';
import Redis from 'ioredis';
// Create Redis client
const redis = new Redis({
host: 'localhost',
port: 6379,
});
// Create Redis cache
const redisCache: ICache = {
mx: new RedisAdapter(redis, {
keyPrefix: 'email:mx:',
ttl: 1800000, // 30 minutes
}),
disposable: new RedisAdapter(redis, {
keyPrefix: 'email:disposable:',
ttl: 86400000, // 24 hours
}),
free: new RedisAdapter(redis, {
keyPrefix: 'email:free:',
ttl: 86400000, // 24 hours
}),
domainValid: new RedisAdapter(redis, {
keyPrefix: 'email:domain:',
ttl: 86400000, // 24 hours
}),
smtp: new RedisAdapter(redis, {
keyPrefix: 'email:smtp:',
ttl: 1800000, // 30 minutes
}),
domainSuggestion: new RedisAdapter(redis, {
keyPrefix: 'email:suggest:',
ttl: 86400000, // 24 hours
}),
whois: new RedisAdapter(redis, {
keyPrefix: 'email:whois:',
ttl: 3600000, // 1 hour
}),
};
// Use with batch verification
import { verifyEmailBatch } from '@emailcheck/email-validator-js';
const batchResult = await verifyEmailBatch({
emailAddresses: ['[email protected]', '[email protected]'],
verifyMx: true,
verifySmtp: true,
cache: redisCache,
concurrency: 10
});Create your own cache adapter by implementing the ICacheStore interface:
import { verifyEmail, type ICacheStore } from '@emailcheck/email-validator-js';
class MyCustomCache<T> implements ICacheStore<T> {
private store = new Map<string, { value: T; expiry: number }>();
async get(key: string): Promise<T | null> {
const item = this.store.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.store.delete(key);
return null;
}
return item.value;
}
async set(key: string, value: T, ttlMs?: number): Promise<void> {
const expiry = Date.now() + (ttlMs || 3600000);
this.store.set(key, { value, expiry });
}
async delete(key: string): Promise<boolean> {
return this.store.delete(key);
}
async has(key: string): Promise<boolean> {
const item = this.store.get(key);
if (!item) return false;
if (Date.now() > item.expiry) {
this.store.delete(key);
return false;
}
return true;
}
async clear(): Promise<void> {
this.store.clear();
}
size(): number {
return this.store.size;
}
}
// Use custom cache store
const customCache = {
mx: new MyCustomCache<string[]>(),
disposable: new MyCustomCache<boolean>(),
free: new MyCustomCache<boolean>(),
domainValid: new MyCustomCache<boolean>(),
smtp: new MyCustomCache<boolean | null>(),
domainSuggestion: new MyCustomCache<{ suggested: string; confidence: number } | null>(),
whois: new MyCustomCache<any>(),
};
const result = await verifyEmail({
emailAddress: '[email protected]',
cache: customCache
});Default cache TTL and size settings:
import { DEFAULT_CACHE_OPTIONS } from '@emailcheck/email-validator-js';
// TTL (Time To Live) in milliseconds
DEFAULT_CACHE_OPTIONS.ttl = {
mx: 3600000, // 1 hour
disposable: 86400000, // 24 hours
free: 86400000, // 24 hours
domainValid: 86400000, // 24 hours
smtp: 1800000, // 30 minutes
domainSuggestion: 86400000, // 24 hours
whois: 3600000, // 1 hour
};
// Maximum number of entries per cache type
DEFAULT_CACHE_OPTIONS.maxSize = {
mx: 500,
disposable: 1000,
free: 1000,
domainValid: 1000,
smtp: 500,
domainSuggestion: 1000,
whois: 200,
};The package ships a serverless build (@emailcheck/email-validator-js/serverless/*) that runs without node:net / node:dns / node:tls. It targets:
- AWS Lambda β API Gateway, direct invocation, routed handler
- GCP Cloud Functions (2nd gen) β Express-style
(req, res)on Cloud Run - Vercel β Edge Functions and Node.js runtime
- Cloudflare Workers β including KV write-through and Durable Objects
- Netlify Functions β Lambda-shaped event with redirect-aware path stripping
- Azure Functions (v4 model) β Web-API-shaped HTTP triggers
- Netlify Edge Functions / Deno Deploy β direct
validateEmailCoreuse
// Routed: GET /health, POST /validate, POST /validate/batch
export { handler } from '@emailcheck/email-validator-js/serverless/aws';Other shapes available: apiGatewayHandler (legacy, no path routing) and lambdaHandler (direct invocation).
// app/api/validate/route.ts
import { handler } from '@emailcheck/email-validator-js/serverless/vercel';
export const runtime = 'edge';
export async function POST(request: Request) { return handler(request); }Other shapes: edgeHandler (no routing) and nodeHandler (Express-style).
// src/worker.ts
export { default } from '@emailcheck/email-validator-js/serverless/cloudflare';Bind a EMAIL_CACHE KV namespace in wrangler.toml to get write-through caching across instances. Bind EMAIL_VALIDATOR as a Durable Object (class EmailValidatorDO, also exported) for stateful validation with /validate, /cache/clear, /cache/stats.
import { gcpHandler } from '@emailcheck/email-validator-js/serverless/gcp';
export const validateEmail = gcpHandler;Deploy with gcloud functions deploy --gen2 --runtime=nodejs20 --trigger-http. See SERVERLESS.md for the Functions Framework integration and Cloud Run usage.
// netlify/functions/validate.ts
export { netlifyHandler as handler } from '@emailcheck/email-validator-js/serverless/netlify';The adapter strips /.netlify/functions/<name> and /api/* prefixes automatically, so the same handler works whether you hit the raw function URL or a redirect.
import { app } from '@azure/functions';
import { azureHandler } from '@emailcheck/email-validator-js/serverless/azure';
app.http('validateEmail', {
methods: ['GET', 'POST', 'OPTIONS'],
route: '{*path}',
handler: azureHandler,
});A built-in DoHResolver ships with the package β works in any runtime with fetch (Cloudflare Workers, Vercel Edge, Deno, browsers, Node 22+):
import {
validateEmailCore,
DoHResolver,
} from '@emailcheck/email-validator-js/serverless/verifier';
const result = await validateEmailCore('[email protected]', {
validateMx: true,
dnsResolver: new DoHResolver(), // defaults to Cloudflare 1.1.1.1
});
// result.validators.mx === { valid: true, records: ['mx.example.com'] }Configurable: pass { endpoint, timeoutMs, fetch } to point at Google/NextDNS/self-hosted, tune the per-query timeout, or inject a custom fetch. Compatible with cf-doh β see SERVERLESS.md for the full breakdown.
| Capability | Edge | Notes |
|---|---|---|
| Syntax validation | β | RFC-pragmatic regex |
| Typo detection / suggestions | β | Same data as Node API |
| Disposable detection | β | Full list bundled |
| Free-provider detection | β | Full list bundled |
| MX records | β ΒΉ | Requires dnsResolver injection |
| SMTP probe | β | Needs raw TCP β Node-only |
| WHOIS lookups | β | Needs raw TCP β Node-only |
| Batch processing | β | validateEmailBatch (max 100 / call) |
| Built-in caching | β | EdgeCache (in-memory) + Cloudflare KV |
ΒΉ See the DNS resolver example above. Bring your own resolver β the serverless build doesn't bundle one.
For full docs (DNS resolver patterns, KV write-through, Durable Objects, Deno Deploy, bundle-size table, migration diff), see SERVERLESS.md.
Set captureTranscript: true on verifyEmail to get a structured per-step trace of everything the library did β what was looked up, what came back, how long each step took, and (for SMTP) the full wire-level transcript:
import { verifyEmail } from '@emailcheck/email-validator-js';
const result = await verifyEmail({
emailAddress: '[email protected]',
verifyMx: true,
verifySmtp: true,
checkDisposable: true,
checkFree: true,
detectName: true,
suggestDomain: true,
captureTranscript: true,
});
for (const step of result.transcript ?? []) {
console.log(`[${step.kind}] ${step.durationMs}ms ok=${step.ok}`, step.details);
}Each entry has:
interface VerificationStep {
kind:
| 'syntax' | 'domain-validation' | 'name-detection' | 'domain-suggestion'
| 'disposable' | 'free' | 'mx-lookup' | 'smtp-probe'
| 'whois-age' | 'whois-registration';
startedAt: number; // Date.now() at step start
durationMs: number;
ok: boolean; // false if the step threw
details: Record<string, unknown>; // step-specific structured data
}Step-specific details shapes:
kind |
Notable details fields |
|---|---|
mx-lookup |
domain, records, count |
smtp-probe |
port, verdict ('deliverable' | 'undeliverable' | 'indeterminate'), cacheHit, transcript, commands |
whois-age |
creationDate, ageInDays, ageInYears |
whois-registration |
isRegistered, isExpired, isLocked, isPendingDelete, daysUntilExpiration, status[] |
disposable / free |
domain, isDisposable / isFree |
name-detection |
detected ({ firstName, lastName, confidence } or null) |
domain-suggestion |
suggestion ({ original, suggested, confidence } or null) |
When captureTranscript is not set (the default), no recording happens and result.transcript is undefined β zero overhead.
If you have a stringified SMTP error in hand (e.g. from a logged bounce, or result.smtp.error), use parseSmtpError to get a structured verdict:
import { parseSmtpError } from '@emailcheck/email-validator-js';
const parsed = parseSmtpError('552 5.2.2 mailbox over quota');
// { isDisabled: false, hasFullInbox: true, isCatchAll: false, isInvalid: false }The four flags are orthogonal β a single message can fire multiple. See __tests__/0112-smtp-error-parser.test.ts for the full classification matrix.
verifyMailboxSMTP returns a coarse error reason (not_found / over_quota / temporary_failure / ambiguous / β¦) that's stable across MX implementations. When the MX includes an enhanced status code (RFC 3463) β e.g. 5.1.1 for "user unknown" vs. 5.7.1 for "policy block" β pipe both through refineReasonByEnhancedStatus to get a more specific reason:
import { refineReasonByEnhancedStatus, verifyMailboxSMTP } from '@emailcheck/email-validator-js';
const { smtpResult } = await verifyMailboxSMTP({
local: 'alice', domain: 'example.com', mxRecords: ['mx.example.com'],
});
const refined = refineReasonByEnhancedStatus(smtpResult.error, smtpResult.enhancedStatus);
// e.g. 'mailbox_does_not_exist' instead of 'not_found' when the MX returned 550 5.1.1Mapping (codes not in the table return the original reason unchanged):
| DSN code | Refined reason |
|---|---|
5.1.1 |
mailbox_does_not_exist |
5.1.2 |
bad_destination_system |
5.1.3 |
bad_destination_address |
5.1.6 |
mailbox_moved |
5.1.10 |
recipient_address_has_null_mx |
5.2.0 |
mailbox_status_other |
5.2.1 |
mailbox_disabled |
5.2.2 |
mailbox_full |
5.2.3 |
message_too_long |
5.2.4 |
mailing_list_expansion_problem |
4.4.1 |
no_answer_from_host |
4.4.2 |
bad_connection |
5.7.0 |
security_other |
5.7.1 |
delivery_not_authorized |
5.7.25 |
no_reverse_dns |
5.7.26 |
multiple_authentication_failures |
The library includes intelligent caching to improve performance:
| Cache Type | TTL | Description |
|---|---|---|
| MX Records | 1 hour | DNS MX record lookups |
| Disposable | 24 hours | Disposable email checks |
| Free Provider | 24 hours | Free email provider checks |
| Domain Valid | 24 hours | Domain validation results |
| SMTP | 30 minutes | SMTP verification results |
| Domain Suggestions | 24 hours | Domain typo suggestions |
- Use Batch Processing: For multiple emails, use
verifyEmailBatch()for parallel processing - Enable Caching: Caching is automatic and reduces repeated lookups by ~90%
- Adjust Timeouts: Lower timeouts for faster responses, higher for accuracy
- Skip SMTP: If you only need format/MX validation, skip SMTP for 10x faster results
- Domain Suggestions: Cached for 24 hours to avoid recalculating similarity scores
- Name Detection: Lightweight operation with minimal performance impact
View List - 5,000+ disposable email domains
View List - 1,000+ free email providers
Access the list of 70+ common email domains used for typo detection:
import { COMMON_EMAIL_DOMAINS } from '@emailcheck/email-validator-js';
console.log(COMMON_EMAIL_DOMAINS);
// ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', ...]
// Use with your own domain validation
const isCommon = COMMON_EMAIL_DOMAINS.includes('gmail.com'); // trueDefault suite (fast, deterministic β no network):
bun run testReal-network integration suite (INTEGRATION=1 is set automatically):
bun run test:integrationEverything:
bun run test:allLint:
bun run lint # check
bun run lint:fix # auto-fixTypecheck + build:
bun run typecheck
bun run build- β TypeScript Strict Mode: Full type safety with comprehensive type checking
- β Biome: Automated lint + format
- β bun:test: 720+ unit & mocked-IO tests, 0 jest, 0 sinon
- β CI/CD: Automated test + lint + build on all PRs
email-validator-js/
βββ src/ # Library sources
β βββ index.ts # Public entry β verifyEmail orchestrator
β βββ email-validator.ts # Format / TLD validation
β βββ smtp-verifier.ts # SMTP probe (class-based state machine)
β βββ smtp-error-parser.ts # parseSmtpError public utility
β βββ transcript.ts # Transcript collector for verifyEmail
β βββ mx-resolver.ts # DNS MX lookup with cache
β βββ whois.ts # WHOIS query pipeline
β βββ whois-parser.ts # TLD-specific WHOIS parsers
β βββ domain-suggester.ts # Typo / similarity suggestions
β βββ name-detector.ts # Local-part name extraction
β βββ is-spam-email.ts # Spam-pattern detection
β βββ batch-verifier.ts # Concurrent batch validator
β βββ cache.ts / cache-interface.ts # Pluggable cache surface
β βββ adapters/
β β βββ lru-adapter.ts # In-memory LRU
β β βββ redis-adapter.ts # Redis-backed (SCAN-safe clear)
β βββ data/ # Source-of-truth JSON tables
β β βββ common-{first,last}-names.json
β β βββ common-email-domains.json
β β βββ typo-patterns.json
β β βββ whois-servers.json
β βββ types.ts # Public type definitions
β βββ serverless/ # Edge-runtime variant
β βββ verifier.ts # No-Node-deps validator
β βββ _shared/ # Cross-platform helpers
β β βββ cors.ts
β β βββ dispatch.ts
β β βββ validation.ts
β βββ adapters/ # AWS, GCP, Vercel, Cloudflare, Netlify, Azure
βββ __tests__/
β βββ unit/ # Default suite β pure unit + mocked-IO
β βββ isolated/ # Tests using mock.module (own bun-test process)
β βββ integration/ # Real-network suite (INTEGRATION=1)
β βββ helpers/ # Shared fake-net + setup
β βββ utils/ # Shared test fixtures
βββ extras/check-if-email-exists/ # Out-of-scope module + its tests
βββ examples/
β βββ smtp/ # Direct SMTP-probe API usage
β βββ cache/ # Custom CacheStore implementations
β βββ high-level/ # verifyEmail orchestration / names / domains
β βββ integrations/ # Patterns for plugging into other tools
β βββ serverless/ # One folder per platform (AWS, GCP, ...)
βββ dist/ # Rollup output (CJS + ESM)
bun run build # Rollup CJS + ESM bundles
bun run test # Default: unit + isolated (~770 tests)
bun run test:unit # Just the unit suite
bun run test:isolated # Tests needing mock.module isolation
bun run test:integration # Real-network suite (INTEGRATION=1)
bun run test:extras # check-if-email-exists module (opt-in, ~200 tests)
bun run test:all # test + test:integration
# Domain-specific filters (glob over __tests__/unit/):
bun run test:smtp # 01xx
bun run test:cache # 02xx
bun run test:whois # 03xx
bun run test:names # 04xx
bun run test:serverless # 05xx + isolated/
bun run test:cli # 07xx
bun run lint # Biome check
bun run lint:fix # Biome check --write
bun run typecheck # tsc against src + tests + examplesWe welcome contributions! Please feel free to open an issue or create a pull request.
# Clone
git clone https://github.com/email-check-app/email-validator-js.git
cd email-validator-js
# Install with Bun
bun install
# Run the default (no-network) suite
bun run test
# Build
bun run buildFor issues, questions, or commercial licensing:
π Open an Issue π§ Email Support π Commercial License π Visit email-check.app
Business Source License 1.1 - see LICENSE file for details.
The BSL allows use only for non-production purposes. Here's a comprehensive guide to help you understand when you need a commercial license:
| Use Case | Commercial License Required? | Details |
|---|---|---|
| Personal & Learning | ||
| π¬ Exploring email-validator-js for research or learning | β No | Use freely for educational purposes |
| π¨ Personal hobby projects (non-commercial) | β No | Build personal tools and experiments |
| π§ͺ Testing and evaluation in development environment | β No | Test all features before purchasing |
| Development & Prototyping | ||
| π‘ Building proof-of-concept applications | β No | Create demos and prototypes |
| π οΈ Internal tools (not customer-facing) | β No | Use for internal development tools |
| π Open source projects (non-commercial) | β No | Contribute to the community |
| Commercial & Production Use | ||
| π° Revenue-generating applications | β Yes | Any app that generates income |
| βοΈ Software as a Service (SaaS) products | β Yes | Cloud-based service offerings |
| π¦ Distributed commercial software | β Yes | Software sold to customers |
| π’ Enterprise production systems | β Yes | Business-critical applications |
| π Forking for commercial purposes | β Yes | Creating derivative commercial products |
| π Production use in any form | β Yes | Live systems serving real users |
| Specific Scenarios | ||
| π Student projects and coursework | β No | Academic use is encouraged |
| ποΈ CI/CD pipelines (for commercial products) | β Yes | Part of commercial development |
| π§ Email validation in production APIs | β Yes | Production service usage |
| π E-commerce checkout validation | β Yes | Revenue-related validation |
| π± Mobile apps (free with ads or paid) | β Yes | Monetized applications |
Ask yourself these questions:
- Will real users interact with this in production? β You need a license
- Will this help generate revenue? β You need a license
- Is this for learning or testing only? β No license needed
- Is this an internal prototype or POC? β No license needed
β¨ Unlimited Usage - Use in all your production applications
π Priority Support - Direct support from our engineering team
π Regular Updates - Get the latest features and improvements
π‘οΈ Legal Protection - Full commercial rights and warranty
π’ Enterprise Ready - Suitable for large-scale deployments
Ready to use email-validator-js in production?
ποΈ Purchase a License - Simple pricing, instant activation
π§ Contact Sales - For enterprise or custom needs