UNPKG

@emailcheck/email-validator-js

Version:

Advanced email validation with MX records, SMTP verification, disposable email detection, batch processing, and caching. Production-ready with TypeScript support.

1,497 lines (1,189 loc) • 66.1 kB
# Advanced Email Validator [![NPM version](https://badgen.net/npm/v/@emailcheck/email-validator-js)](https://npm.im/@emailcheck/email-validator-js) [![Build Status](https://github.com/email-check-app/email-validator-js/workflows/CI/badge.svg)](https://github.com/email-check-app/email-validator-js/actions) [![Downloads](https://img.shields.io/npm/dm/@emailcheck/email-validator-js.svg)](https://www.npmjs.com/package/@emailcheck/email-validator-js) [![UNPKG](https://img.shields.io/badge/UNPKG-OK-179BD7.svg)](https://unpkg.com/browse/@emailcheck/email-validator-js@latest/) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![License](https://img.shields.io/badge/license-BSL%201.1-blue.svg)](LICENSE.md) šŸš€ **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**. ## šŸ“‹ Table of Contents - [Features](#features) - [Use Cases](#use-cases) - [API / Cloud Service](#api--cloud-hosted-service) - [License](#license) - [Installation](#installation) - [Quick Start](#quick-start) - [Migration Guide (to v3.x)](#migration-guide-to-v3x) - [API Reference](#api-reference) - [Configuration](#configuration-options) - [Examples](#examples) - [Command-line Tool (`email-validate`)](#-command-line-tool-email-validate) - [Custom Cache Injection](#-custom-cache-injection) - [Verification Transcript](#-verification-transcript) - [Performance & Caching](#-performance--caching) - [Email Provider Databases](#ļø-email-provider-databases) - [Testing](#testing) - [Contributing](#contributing) ## Features - āœ… 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 `any` in `src/` ## Use Cases - 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 ## API / Cloud Hosted Service 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](https://email-check.app/products/email) --- ## License email-validator-js is licensed under [Business Source License 1.1](LICENSE.md). ### Quick License Summary | 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](https://email-check.app/license/email-validator) or contact us at [sales@email-check.app](mailto:sales@email-check.app?subject=Interested%20in%20email-validator-js%20commercial%20license). --- ## Installation ```bash bun add @emailcheck/email-validator-js # or npm install @emailcheck/email-validator-js # or pnpm add @emailcheck/email-validator-js ``` ### Requirements (consumers) - Node.js >= 22 (currently-supported LTS lines: 22 Maintenance, 24 Active) - TypeScript >= 4.0 (for TypeScript users) ### Requirements (contributing) - Bun >= 1.3 (test runner, package manager, dev tooling) - Node.js >= 24 only needed for `semantic-release` during the publish step ### Build System - Rollup builds CJS + ESM bundles for the main package and the serverless entry - `bun test` for the unit + mocked-IO suite (no jest, no ts-jest) - Source data (common names, typo patterns, WHOIS servers) lives in `src/data/*.json` ## Quick Start ```typescript import { verifyEmail } from '@emailcheck/email-validator-js'; // Basic usage const result = await verifyEmail({ emailAddress: 'user@mydomain.com', 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 use `camelCase` instead of `SCREAMING_SNAKE_CASE`. See [Migration Guide](#migration-guide-to-v3x) for details. ## Migration Guide (to v3.x) ### Overview 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`. ### What Changed #### Enum Values | 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` | #### Constants | 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` | ### How to Migrate #### Step 1: Update Enum References ```typescript // 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]; ``` #### Step 2: Update Constant References ```typescript // 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; ``` #### Step 3: Update Switch Statements ```typescript // 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; } ``` ### Important Notes 1. **String values remain unchanged**: The underlying string values (e.g., `'gmail'`, `'INVALID_FORMAT'`) are preserved. Only the property names changed. 2. **Runtime compatibility**: If you're comparing enum values to strings from external sources, the string values still work: ```typescript // Still works in v3.x if (provider === 'gmail') { /* ... */ } ``` 3. **TypeScript strict mode**: Ensure you update all references before compiling, or TypeScript will report errors. 4. **Test your code**: After updating, run your test suite to ensure all enum and constant references are updated correctly. ### Automatic Migration If you're using an IDE with refactoring support (like VS Code), you can use find-and-replace: 1. Find all references to old enum values 2. Replace with new camelCase versions 3. Run TypeScript compiler to verify no errors ### Need Help? - šŸ“– Check the [API Reference](#api-reference) for updated enum definitions - šŸ’¬ [Open an issue](https://github.com/email-check-app/email-validator-js/issues) if you encounter problems - šŸ“§ Contact [support@email-check.app](mailto:support@email-check.app) ## API Reference ### Core Functions #### `verifyEmail(params: IVerifyEmailParams): Promise<VerificationResult>` 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. - `whoisTimeoutMs` (number) — Per-WHOIS-query timeout (default: `5000`). - `debug` (boolean) — Per-line `console.debug` trace (default: `false`). - `captureTranscript` (boolean) — Populate `result.transcript` with 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:** ```typescript { 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; }; } ``` #### `verifyEmailBatch(params: IBatchVerifyParams): Promise<BatchVerificationResult>` Verify multiple emails in parallel with concurrency control. **Parameters:** - `emailAddresses` (string[], required): Array of emails to verify - `concurrency` (number): Parallel processing limit (default: 5) - `detectName` (boolean): Detect names from email addresses - `suggestDomain` (boolean): Enable domain typo suggestions - Other parameters from `verifyEmail` **Returns:** ```typescript { results: Map<string, VerificationResult>; summary: { total: number; valid: number; invalid: number; errors: number; processingTime: number; }; } ``` ### Name Detection Functions #### `detectName(email: string): DetectedName | null` Detect first and last name from email address. ```typescript const name = detectName('john.doe@mydomain.com'); // 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.) #### `detectNameFromEmail(params: IDetectNameParams): DetectedName | null` Advanced name detection with custom method support. ```typescript const customMethod = (email: string) => { // Your custom logic return { firstName: 'Custom', lastName: 'Name', confidence: 1.0 }; }; const name = detectNameFromEmail({ email: 'user@mydomain.com', customMethod: customMethod }); ``` **Parameters:** - `email` (string): Email address - `customMethod` (function): Custom detection logic #### `defaultNameDetectionMethod(email: string): DetectedName | null` The default name detection implementation, exported for custom extensions. #### Algorithm-Specific Name Cleaning ##### `cleanNameForAlgorithm(name: string): string` Clean a name by removing special characters (dots, underscores, asterisks). Specifically designed for Algorithm name processing. ```typescript import { cleanNameForAlgorithm } from '@emailcheck/email-validator-js'; const cleanedName = cleanNameForAlgorithm('john.doe_smith*'); // Returns: 'johndoesmith' const cleanedName2 = cleanNameForAlgorithm('first_name.last'); // Returns: 'firstnamelast' ``` ##### `detectNameForAlgorithm(email: string): DetectedName | null` Enhanced name detection for Algorithm with aggressive cleaning. Removes dots, underscores, and asterisks from detected names. ```typescript import { detectNameForAlgorithm } from '@emailcheck/email-validator-js'; const result = detectNameForAlgorithm('john.doe_smith@company.com'); // Returns: { firstName: 'John', lastName: 'Doesmith', confidence: 0.9025 } // Compared to regular detection: import { detectName } from '@emailcheck/email-validator-js'; const normalResult = detectName('john.doe_smith@company.com'); // 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 ### Domain Suggestion Functions #### `suggestEmailDomain(email: string, commonDomains?: string[]): DomainSuggestion | null` Detect and suggest corrections for misspelled email domains. ```typescript const suggestion = suggestEmailDomain('user@gmial.com'); // Returns: { original: 'user@gmial.com', suggested: 'user@gmail.com', confidence: 0.95 } // With custom domain list const customDomains = ['company.com', 'enterprise.org']; const customSuggestion = suggestEmailDomain('user@compny.com', 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 #### `suggestDomain(params: ISuggestDomainParams): DomainSuggestion | null` Advanced domain suggestion with custom method support. ```typescript const suggestion = suggestDomain({ domain: 'gmial.com', customMethod: myCustomMethod, commonDomains: ['company.com'] }); ``` **Parameters:** - `domain` (string): Domain to check - `customMethod` (function): Custom suggestion logic - `commonDomains` (string[]): Custom domain list #### `defaultDomainSuggestionMethod(domain: string, commonDomains?: string[]): DomainSuggestion | null` The default domain suggestion implementation, exported for custom extensions. #### `isCommonDomain(domain: string, commonDomains?: string[]): boolean` Check if a domain is in the common domains list. ```typescript isCommonDomain('gmail.com'); // true isCommonDomain('mycompany.com'); // false // With custom list isCommonDomain('mycompany.com', ['mycompany.com']); // true ``` #### `getDomainSimilarity(domain1: string, domain2: string): number` Calculate similarity score between two domains (0-1). ```typescript getDomainSimilarity('gmail.com', 'gmial.com'); // 0.8 getDomainSimilarity('gmail.com', 'yahoo.com'); // 0.3 ``` ### WHOIS Functions > **Note:** 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`. #### `getDomainAge(domain: string, timeout?: number): Promise<DomainAgeInfo | null>` Get domain age information via WHOIS lookup. ```typescript 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('user@mydomain.com'); await getDomainAge('https://mydomain.com/path'); ``` **Parameters:** - `domain` (string): Domain, email, or URL to check - `timeout` (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. ```typescript 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 check - `timeout` (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 ### Utility Functions #### `isDisposableEmail(emailOrDomain: string, cache?: ICache, options?: { skipMxCheck?: boolean; skipDomain?: boolean }): boolean` Check if email uses a disposable provider. ```typescript // Basic usage isDisposableEmail('user@tempmail.com'); // true isDisposableEmail('tempmail.com'); // true isDisposableEmail('gmail.com'); // false // With options isDisposableEmail('user@tempmail.com', null, { skipMxCheck: true, // Skip MX record validation skipDomain: true // Skip domain validation }); // true ``` #### `isFreeEmail(emailOrDomain: string, cache?: ICache, options?: { skipMxCheck?: boolean; skipDomain?: boolean }): boolean` Check if email uses a free provider. ```typescript // Basic usage isFreeEmail('user@gmail.com'); // true isFreeEmail('yahoo.com'); // true isFreeEmail('corporate.com'); // false // With options isFreeEmail('user@gmail.com', null, { skipMxCheck: true, // Skip MX record validation skipDomain: true // Skip domain validation }); // true ``` #### `isValidEmail(emailAddress: string): boolean` Validate email format (RFC 5321 compliant). ```typescript isValidEmail('user@mydomain.com'); // true isValidEmail('invalid.email'); // false ``` **Validation Rules:** - Proper @ symbol placement - Local part max 64 characters - Domain max 253 characters - No consecutive dots - No leading/trailing dots - Valid domain TLD #### `isValidEmailDomain(emailOrDomain: string): boolean` Validate if a domain has a valid TLD. ```typescript isValidEmailDomain('mydomain.com'); // true isValidEmailDomain('example.invalid'); // false ``` #### Cache Management ```typescript import { 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(); ``` ### Types and Interfaces #### `DetectedName` ```typescript interface DetectedName { firstName?: string; lastName?: string; confidence: number; // 0-1 scale } ``` #### `DomainSuggestion` ```typescript interface DomainSuggestion { original: string; suggested: string; confidence: number; // 0-1 scale } ``` #### `NameDetectionMethod` ```typescript type NameDetectionMethod = (email: string) => DetectedName | null; ``` #### `DomainSuggestionMethod` ```typescript type DomainSuggestionMethod = (domain: string) => DomainSuggestion | null; ``` #### `DomainAgeInfo` ```typescript interface DomainAgeInfo { domain: string; creationDate: Date; ageInDays: number; ageInYears: number; expirationDate: Date | null; updatedDate: Date | null; } ``` #### `DomainRegistrationInfo` ```typescript 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; } ``` ### Constants #### `COMMON_EMAIL_DOMAINS` Array of 70+ common email domains used for typo detection. ```typescript 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.) ### Error Codes ```typescript 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' } ``` ## Configuration Options ### `timeout` Set a timeout in milliseconds for the smtp connection. Default: `4000`. ### `verifyMx` Enable or disable domain checking. This is done in two steps: 1. Verify that the domain does indeed exist 2. Verify that the domain has valid MX records Default: `false`. ### `verifySmtp` 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 `null` for Yahoo addresses, for failed connections, for unknown SMTP errors * It returns `true` for valid SMTP responses * It returns `false` for SMTP errors specific to the address's formatting or mailbox existence Default: `false`. ### `checkDisposable` (NEW) Check if the email domain is a known disposable email provider. Default: `false`. ### `checkFree` (NEW) Check if the email domain is a known free email provider. Default: `false`. ### `detailed` (NEW) Return detailed verification results with error codes. Default: `false`. ### `retryAttempts` (NEW) Number of retry attempts for transient failures. Default: `1`. ## Examples ### Basic Usage ```typescript import { verifyEmail } from '@emailcheck/email-validator-js'; const result = await verifyEmail({ emailAddress: 'foo@email.com', verifyMx: true, verifySmtp: true, smtpPerAttemptTimeoutMs: 3000, }); console.log(result.validFormat); // true console.log(result.validMx); // true console.log(result.validSmtp); // true ``` ### Detailed Verification (NEW) ```typescript import { verifyEmail } from '@emailcheck/email-validator-js'; const result = await verifyEmail({ emailAddress: 'foo@email.com', 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: 125 ``` ### Batch Verification (NEW) ```typescript import { verifyEmailBatch } from '@emailcheck/email-validator-js'; const emails = ['user1@gmail.com', 'user2@mydomain.com', 'invalid@fake.com']; 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: 234 ``` ### Enhanced SMTP Verification (NEW) ```typescript import { 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'}`); ``` ### Configuration presets (NEW in v5) Don't want to think about timeouts and retries? Pick a preset that matches your deployment shape: ```typescript import { verifyEmail, VERIFY_EMAIL_PRESETS } from '@emailcheck/email-validator-js'; // Lambda / Vercel / Cloudflare-Workers handler await verifyEmail({ emailAddress: 'alice@example.com', 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: ```typescript await verifyEmail({ emailAddress: 'alice@example.com', ...VERIFY_EMAIL_PRESETS.serverless, smtpTotalDeadlineMs: 3000, // tighter than the preset's 5s }); ``` ### Time-budget controls (NEW in v5) The probe walks `mxRecords Ɨ ports` (worst-case 4 Ɨ 3 = 12 attempts at 3s each = 36s). Four orthogonal knobs let you bound that: ```typescript import { verifyEmail, verifyMailboxSMTP } from '@emailcheck/email-validator-js'; await verifyEmail({ emailAddress: 'maria.hernandez+news@yahoo.com', 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. ### Custom SMTP step sequence Override the default `greeting → EHLO → MAIL FROM → RCPT TO` walk for advanced cases: ```typescript 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: '<noreply@yourdomain.com>', // Custom MAIL FROM payload }, ports: [587, 465], }, }); ``` ### SMTP Transcript Capture 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: ```typescript 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](#verification-transcript) below. ### Running Examples Examples are grouped by topic under `examples/`. See [examples/README.md](./examples/README.md) for the full index. ```bash # 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.ts ``` **After installation in your own project (Node 22+):** ```bash node --experimental-strip-types examples/smtp/usage.ts ``` ### Name Detection (ENHANCED) ```typescript import { detectName, verifyEmail } from '@emailcheck/email-validator-js'; // Standalone name detection - now with composite name support const name = detectName('john.doe@mydomain.com'); // name: { firstName: 'John', lastName: 'Doe', confidence: 0.9 } // Handle alphanumeric composite names const composite = detectName('mo1.test2@mydomain.com'); // composite: { firstName: 'Mo1', lastName: 'Test2', confidence: 0.6 } // Smart handling of numbers and suffixes const withNumbers = detectName('john.doe123@mydomain.com'); // withNumbers: { firstName: 'John', lastName: 'Doe', confidence: 0.8 } const withSuffix = detectName('jane.smith.dev@mydomain.com'); // withSuffix: { firstName: 'Jane', lastName: 'Smith', confidence: 0.7 } // Integrated with email verification const result = await verifyEmail({ emailAddress: 'jane_smith@mydomain.com', 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: 'user@mydomain.com', detectName: true, nameDetectionMethod: customMethod }); ``` ### Domain Typo Detection (NEW) ```typescript import { suggestEmailDomain, verifyEmail } from '@emailcheck/email-validator-js'; // Standalone domain suggestion const suggestion = suggestEmailDomain('user@gmial.com'); // suggestion: { original: 'user@gmial.com', suggested: 'user@gmail.com', confidence: 0.95 } // Integrated with email verification (enabled by default in detailed mode) const result = await verifyEmail({ emailAddress: 'john@yaho.com', suggestDomain: true // Default: true for detailed verification }); // result.domainSuggestion: { original: 'john@yaho.com', suggested: 'john@yahoo.com', confidence: 0.9 } // With custom domain list const customDomains = ['company.com', 'enterprise.org']; const resultCustom = await verifyEmail({ emailAddress: 'user@compny.com', suggestDomain: true, commonDomains: customDomains }); // resultCustom.domainSuggestion: { suggested: 'user@company.com', confidence: 0.85 } ``` ### Handling Different Validation Scenarios When a domain does not exist or has no MX records: ```typescript const result = await verifyEmail({ emailAddress: 'foo@bad-domain.com', verifyMx: true, verifySmtp: true }); // result.validFormat: true (format is valid) // result.validMx: false (no MX records) // result.validSmtp: null (couldn't be performed) ``` ### Using Detailed Verification for Better Insights ```typescript const result = await verifyEmail({ emailAddress: 'user@suspicious-domain.com', 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; } } ``` ### Batch Processing for Large Lists ```typescript const emails = [ 'valid@gmail.com', 'test@tempmail.com', 'user@company.com', // ... 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); } } ``` ### Performance Optimization with Caching ```typescript // First verification - hits DNS and SMTP const first = await verifyEmail({ emailAddress: 'cached@mydomain.com', verifyMx: true }); // Takes ~500ms // Second verification - uses cache const second = await verifyEmail({ emailAddress: 'cached@mydomain.com', verifyMx: true }); // Takes ~1ms (cached) // Clear cache if needed clearAllCaches(); ``` ## šŸ’» Command-line Tool (`email-validate`) `bun add -g @emailcheck/email-validator-js` (or the npm equivalent) installs an `email-validate` binary. It 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. ```bash # Quick interactive check — full pipeline, pretty colored output email-validate alice@example.com # Skip the SMTP probe (fast, just format / MX / lists / typos) email-validate alice@example.com --no-smtp # Add WHOIS age + registration for full domain reputation picture email-validate alice@example.com --whois-age --whois-registration # Pipe JSON through jq email-validate alice@example.com --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 alice@example.com --port 587 --hostname mta.acme.com --log-dir /var/log/email # Debug a delivery quirk — full transcript + console logs email-validate alice@example.com --debug --format pretty ``` ### Defaults The 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. ### Programmatic CLI The CLI parser, formatter, and runner are also exported as a module so you can embed `email-validate` semantics in your own tooling: ```typescript import { parseArgs, run } from '@emailcheck/email-validator-js/cli'; const parsed = parseArgs(['user@example.com', '--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](./examples/cli-usage.md) for end-to-end recipes. ## šŸš€ Custom Cache Injection The library supports parameter-based cache injection, allowing you to use custom cache backends like Redis, Memcached, or any LRU-compatible cache implementation. ### šŸ“¦ Performance & Caching The library includes a built-in LRU cache for all operations. By default, it uses a lazy-loaded singleton cache instance. #### Default Cache Usage ```typescript import { verifyEmail } from '@emailcheck/email-validator-js'; // No cache setup needed - uses default LRU cache automatically const result = await verifyEmail({ emailAddress: 'user@example.com', verifyMx: true, verifySmtp: true }); // Subsequent calls with the same email will use cached results const result2 = await verifyEmail({ emailAddress: 'user@example.com', verifyMx: true, verifySmtp: true }); ``` #### Custom Cache Implementation Create your own cache by implementing the `ICache` interface: ```typescript 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: 'user@mydomain.com', verifyMx: true, verifySmtp: true, cache: customCache // Pass the cache instance }); ``` #### Redis Cache Implementation ```typescript 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: ['user1@mydomain.com', 'user2@mydomain.com'], verifyMx: true, verifySmtp: true, cache: redisCache, concurrency: 10 }); ``` #### Custom Cache Store Implementation Create your own cache adapter by implementing the `ICacheStore` interface: ```typescript 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: 'user@example.com', cache: customCache }); ``` ### Cache Options Default cache TTL and size settings: ```typescript 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, }; ``` ## 🌐 Serverless Deployment 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 `validateEmailCore` use ### AWS Lambda (routed handler) ```typescript // 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). ### Vercel Edge Functions ```typescript // 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). ### Cloudflare Workers ```typescript // 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`. ### GCP Cloud Functions (2nd gen) ```typescript 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](SERVERLESS.md#gcp-cloud-functions-2nd-gen) for the Functions Framework integration and Cloud Run usage. ### Netlify Functions ```typescript // 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. ### Azure Functions (v4) ```typescript 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, }); ``` ### Edge MX support via the built-in DoH resolver A built-in `DoHResolver` ships with the package — works in any runtime with `fetch` (Cloudflare Workers, Vercel Edge, Deno, browsers, Node 22+): ```typescript import { validateEmailCore, DoHResolver, } from '@emailcheck/email-validator-js/serverless/verifier'; const result = await validateEmailCore('alice@example.com', { validateMx: true, dnsResolver: new DoHResolver(), // defaults to Cloudflare 1.1.1