Easy to use email validation and deliverability checking for Node.js applications.
- ✅ Email Validation - Syntax validation, disposable domain detection, random email detection
- 🔍 Deliverability Checking - Verify if email addresses can receive mail
- 📊 Inbox Health - Check MX, SPF, DMARC, DKIM records, SSL certificates, and domain age
- 🚀 Batch Processing - Validate multiple emails efficiently with concurrency control
- 📈 Result Interpretation - Easy-to-use helpers for understanding validation results
- 🎯 Configuration Presets - Production, development, strict, and lenient presets
- 🛡️ Error Handling - Custom error classes with retry strategies
- 🔌 NeverBounce Integration - Optional enhanced validation and bulk verification
- 💪 TypeScript First - Full type safety with strict mode and branded types
- 📦 Dual Package - Works with both CommonJS and ESM
npm install @launchfirstagency/happy-mailimport { HappyEmailClient, isValid, isSafeToSend } from '@launchfirstagency/happy-mail';
// Create a client with a preset configuration
const client = new HappyEmailClient({ preset: 'production' });
// Validate an email
const result = await client.validateEmail('user@example.com');
// Check if valid using helper functions
if (isSafeToSend(result)) {
console.log('✅ Email is safe to use!');
console.log(`Quality Score: ${getScore(result)}/100`);
} else {
console.log('❌ Email should not be used');
console.log(getRejectionReasons(result));
}Validate email syntax, check for disposable domains, detect randomly generated addresses, and verify deliverability.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const result = await client.validateEmail('user@example.com');
console.log(result);
// {
// email: 'user@example.com',
// normalizedEmail: 'user@example.com',
// domain: { domain: 'example', tld: 'com', sub: '' },
// provider: 'OTHER',
// type: 'PERSONAL',
// risks: {
// validSyntax: true,
// disposableDomain: false,
// canReceive: 'SAFE',
// likelyRandomlyGenerated: false
// }
// }Use built-in helper functions to easily understand and act on validation results.
import {
HappyEmailClient,
isValid,
isSafeToSend,
shouldReject,
getRiskLevel,
getScore,
getRejectionReasons,
getRecommendations,
} from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const result = await client.validateEmail('test@example.com');
// Simple checks
console.log(isValid(result)); // true/false
console.log(isSafeToSend(result)); // true/false (stricter)
console.log(shouldReject(result)); // true/false
console.log(getRiskLevel(result)); // 'low' | 'medium' | 'high'
console.log(getScore(result)); // 0-100
// Get specific reasons and recommendations
console.log(getRejectionReasons(result));
// ['disposable_domain', 'invalid_syntax', 'cannot_receive', ...]
console.log(getRecommendations(result));
// ['Email appears valid and safe to use']Or use the wrapper class for a fluent API:
import { EmailValidationResult } from '@launchfirstagency/happy-mail';
const enhanced = new EmailValidationResult(result);
if (enhanced.isSafeToSend()) {
console.log(`Score: ${enhanced.getScore()}/100`);
console.log(`Risk: ${enhanced.getRiskLevel()}`);
console.log(enhanced.getRecommendations());
}Efficiently validate multiple emails with concurrency control and progress tracking.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const emails = ['user1@example.com', 'user2@gmail.com', 'user3@test.com'];
const results = await client.validateBatch(emails, {
concurrency: 5, // Process 5 at a time
skipBounceCheck: true, // Skip bounce checks for speed
onProgress: (done, total) => {
console.log(`Progress: ${done}/${total}`);
},
continueOnError: true, // Keep going if some fail
});
// Process results
results.forEach((result) => {
if (result.success && result.result) {
console.log(`✅ ${result.email}`);
} else {
console.log(`❌ ${result.email}: ${result.error?.message}`);
}
});Happy Mail provides convenient methods for common validation workflows.
Ideal for real-time form validation - skips bounce checking for speed.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
// Fast validation without bounce check
const result = await client.quickCheck('user@example.com');
if (!result.risks.validSyntax) {
return 'Invalid email format';
}
if (result.risks.disposableDomain) {
return 'Disposable emails not allowed';
}Ideal for high-value signups - performs all checks including bounce verification.
import { HappyEmailClient, isSafeToSend } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
// Comprehensive validation with all checks
const result = await client.fullCheck('user@example.com');
if (isSafeToSend(result)) {
await createAccount(result.email);
}Combines validation and normalization in one call - useful for duplicate detection.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const result = await client.validateAndNormalize('John.Doe+tag@Gmail.com');
if (result.isValid && result.normalized) {
// Check for duplicates using normalized email
const exists = await database.findByEmail(result.normalized);
// normalized: "johndoe@gmail.com"
}Check domain health metrics including MX records, SPF, DMARC, DKIM, SSL certificates, and domain age.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
// Check MX records
const mx = await client.checkMX('user@example.com');
console.log('MX Records:', mx.exists ? 'Found' : 'Not Found');
// Check SPF records
const spf = await client.checkSPF('user@example.com');
console.log('SPF:', spf.exists ? 'Configured' : 'Missing');
// Check DMARC records
const dmarc = await client.checkDMARC('user@example.com');
console.log('DMARC:', dmarc.exists ? 'Configured' : 'Missing');
// Check DKIM records
const dkim = await client.checkDKIM('user@example.com');
console.log('DKIM:', dkim.exists ? 'Configured' : 'Missing');
// Check SSL certificate
const ssl = await client.checkSSL('user@example.com');
console.log('SSL Valid:', ssl.exists);
// Get domain age
const age = await client.getDomainAge('user@example.com');
console.log('Domain Age:', age.age, 'years');Get comprehensive domain health information with a single call.
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const info = await client.getDomainInfo('user@example.com');
console.log(`Domain: ${info.domain}`);
console.log(`Health Score: ${info.healthScore}/100`);
console.log(`Assessment: ${info.assessment}`); // 'excellent' | 'good' | 'fair' | 'poor'
console.log(`Has MX: ${info.hasMX}`);
console.log(`MX Provider: ${info.mxProvider}`);
console.log(`Has SPF: ${info.hasSPF}`);
console.log(`Has DMARC: ${info.hasDMARC}`);
console.log(`Has SSL: ${info.hasSSL}`);
console.log(`Domain Age: ${info.domainAge} years`);
if (info.assessment === 'poor') {
console.log('Domain may have deliverability issues');
}For large-scale email verification, use NeverBounce's bulk API.
import { HappyEmailClient, InputLocationType } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({
neverBounceApiKey: 'your-api-key',
});
// Create a bulk verification job
const job = await client.createBulkJob({
input: [
{ uid: '1', email: 'user1@example.com' },
{ uid: '2', email: 'user2@example.com' },
],
inputLocation: InputLocationType.SUPPLIED,
autoParse: true,
autoStart: true,
});
// Check job status
const status = await client.getJobStatus(job.jobId);
// Get results when complete
if (status.jobStatus === 'complete') {
const results = await client.getJobResults(job.jobId);
console.log(results);
}Happy Mail provides four built-in presets for common use cases:
Optimized for production environments with strict validation and high accuracy.
const client = new HappyEmailClient({ preset: 'production' });Settings:
- Bounce checks: Enabled
- Entropy threshold: 4.0 (stricter)
- Min length for random check: 6
- Logging: Disabled
Use for: Production environments, high-value emails, critical signups
Optimized for development with fast validation and detailed logging.
const client = new HappyEmailClient({ preset: 'development' });Settings:
- Bounce checks: Disabled (faster)
- Entropy threshold: 5.5 (lenient)
- Min length for random check: 10
- Logging: Enabled
Use for: Development, testing, rapid iteration
Maximum accuracy with zero tolerance for risky emails.
const client = new HappyEmailClient({ preset: 'strict' });Settings:
- Bounce checks: Enabled
- Entropy threshold: 3.5 (very strict)
- Min length for random check: 5
- Logging: Disabled
Use for: High-value campaigns, critical email validation, zero tolerance scenarios
High acceptance rate with minimal false positives.
const client = new HappyEmailClient({ preset: 'lenient' });Settings:
- Bounce checks: Disabled
- Entropy threshold: 6.0 (very lenient)
- Min length for random check: 12
- Logging: Enabled
Use for: Quick signup flows, minimal friction, maximum acceptance
Override preset values or create completely custom configurations:
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({
// Start with a preset
preset: 'production',
// Override specific values
loggingEnabled: true,
neverBounceApiKey: 'your-api-key',
// Or configure from scratch
skipBounceCheckByDefault: false,
entropyThreshold: 4.5,
minLengthForRandomCheck: 8,
});All Configuration Options:
preset?: 'production' | 'development' | 'strict' | 'lenient'- Use a preset configurationneverBounceApiKey?: string- NeverBounce API key for enhanced validationskipBounceCheckByDefault?: boolean- Skip bounce checks by default (default: false)entropyThreshold?: number- Entropy threshold for random email detection (default: 4.5)minLengthForRandomCheck?: number- Minimum length for random email check (default: 8)loggingEnabled?: boolean- Enable/disable logging (default: true)emailVerificationService?: IEmailVerificationService- Custom email verification service
Happy Mail provides custom error classes for better error handling and recovery.
import {
HappyMailError,
ValidationError,
NetworkError,
APIError,
ConfigurationError,
ErrorCode,
isHappyMailError,
isRecoverableError,
} from '@launchfirstagency/happy-mail';
try {
const result = await client.validateEmail('user@example.com');
} catch (error) {
if (error instanceof NetworkError) {
// Network errors are recoverable - retry
console.log('Network issue, retrying...');
} else if (error instanceof APIError) {
// Check specific error codes
if (error.code === ErrorCode.RATE_LIMIT_EXCEEDED) {
console.log('Rate limited, waiting...');
}
} else if (error instanceof ValidationError) {
// Validation errors are not recoverable
console.log('Invalid input:', error.message);
} else if (error instanceof ConfigurationError) {
// Configuration errors need fixing
console.log('Configuration issue:', error.message);
}
}All Happy Mail errors include:
code: ErrorCode- Programmatic error codemessage: string- Human-readable error messageisRecoverable: boolean- Whether the error can be retriedtimestamp: Date- When the error occurredcause?: Error- Original error (if any)context?: Record<string, unknown>- Additional context
Validation Errors:
INVALID_EMAIL_SYNTAXINVALID_DOMAINDISPOSABLE_DOMAINVALIDATION_FAILED
Network Errors:
NETWORK_ERRORTIMEOUTCONNECTION_REFUSEDDNS_LOOKUP_FAILED
API Errors:
API_ERRORAPI_KEY_MISSINGAPI_KEY_INVALIDRATE_LIMIT_EXCEEDEDQUOTA_EXCEEDEDAPI_UNAVAILABLE
Configuration Errors:
INVALID_CONFIGURATIONMISSING_REQUIRED_OPTION
Automatically retry failed operations with configurable backoff strategy:
import { retryWithBackoff } from '@launchfirstagency/happy-mail';
const result = await retryWithBackoff(async () => client.validateEmail('user@example.com'), {
maxAttempts: 3,
initialDelay: 1000, // 1 second
maxDelay: 10000, // 10 seconds max
backoffMultiplier: 2, // Double delay each time
jitter: true, // Add randomness to prevent thundering herd
});The retry utility:
- ✅ Automatically retries recoverable errors (Network, API rate limits)
- ✅ Does not retry non-recoverable errors (Validation, Configuration)
- ✅ Uses exponential backoff to avoid overwhelming servers
- ✅ Adds jitter to prevent synchronized retries
Happy Mail is written in TypeScript with full strict mode and provides comprehensive type definitions.
Use branded types to ensure emails are validated at compile time:
import type { ValidatedEmail, SafeEmail, NormalizedEmail } from '@launchfirstagency/happy-mail';
// Type-safe function that only accepts validated emails
function sendEmail(email: ValidatedEmail) {
// TypeScript guarantees this email has been validated
emailService.send(email);
}
// Type-safe function for marketing emails (stricter)
function sendMarketingEmail(email: SafeEmail) {
// TypeScript guarantees this email is safe to send to
marketingService.send(email);
}
// Usage with type assertions (after validation)
const result = await client.validateEmail('user@example.com');
if (isValid(result)) {
await sendEmail(result.email as ValidatedEmail);
}
if (isSafeToSend(result)) {
await sendMarketingEmail(result.email as SafeEmail);
}import {
isValidatedEmail,
isSafeEmail,
assertValidatedEmail,
assertSafeEmail,
} from '@launchfirstagency/happy-mail';
// Type guards (runtime checks)
if (isValidatedEmail(email)) {
// TypeScript knows email is ValidatedEmail
}
// Assertions (throws if invalid)
assertValidatedEmail(email); // Throws if not validated
await sendEmail(email as ValidatedEmail); // Safe after assertionimport type {
// Client types
HappyEmailClientOptions,
BatchValidationOptions,
BatchValidationResult,
ValidateAndNormalizeResult,
DomainInfo,
// Response types
MailValidatorResponse,
MailBoxCanReceiveStatus,
EmailType,
MXHostType,
DomainParts,
// Result helper types
RiskLevel,
RejectionReason,
// Branded types
ValidatedEmail,
SafeEmail,
NormalizedEmail,
// Error types
ErrorCode,
RetryConfig,
// Bulk verification types
BulkJobRequest,
BulkJobResponse,
BulkJobStatus,
BulkJobResults,
InputLocationType,
// Preset types
PresetName,
} from '@launchfirstagency/happy-mail';new HappyEmailClient(options?: HappyEmailClientOptions)-
validateEmail(email: string, skipBounceCheck?: boolean): Promise<MailValidatorResponse>- Validate a single email address
-
validateBatch(emails: string[], options?: BatchValidationOptions): Promise<BatchValidationResult[]>- Validate multiple emails with concurrency control
-
quickCheck(email: string): Promise<MailValidatorResponse>- Fast validation without bounce checking
-
fullCheck(email: string): Promise<MailValidatorResponse>- Comprehensive validation with all checks
-
validateAndNormalize(email: string, skipBounceCheck?: boolean): Promise<ValidateAndNormalizeResult>- Validate and normalize in one call
checkMX(email: string): Promise<RecordLookup>checkSPF(email: string): Promise<RecordLookup>checkDMARC(email: string): Promise<RecordLookup>checkDKIM(email: string, dkimDomain?: string): Promise<RecordLookup>checkSSL(email: string): Promise<SSLCheckResult>getDomainAge(email: string): Promise<DomainAgeResult>getDomainInfo(email: string): Promise<DomainInfo>- Get aggregate domain health information
createBulkJob(request: BulkJobRequest): Promise<BulkJobResponse>listJobs(): Promise<BulkJobStatus[]>getJobStatus(jobId: number): Promise<BulkJobStatus>getJobResults(jobId: string, query?: JobResultsQuery): Promise<BulkJobResults>downloadJobResults(jobId: string, query?: JobResultsQuery): Promise<Buffer>deleteJob(jobId: string): Promise<{ status: string }>searchJobs(query?: JobSearchQuery): Promise<SearchResults>
-
isValid(result: MailValidatorResponse): boolean- Check if email passed basic validation
-
isSafeToSend(result: MailValidatorResponse): boolean- Check if email is safe to send to (stricter)
-
shouldReject(result: MailValidatorResponse): boolean- Check if email should be rejected
-
getRiskLevel(result: MailValidatorResponse): RiskLevel- Get risk classification: 'low' | 'medium' | 'high'
-
getScore(result: MailValidatorResponse): number- Get quality score (0-100)
-
getRejectionReasons(result: MailValidatorResponse): RejectionReason[]- Get specific rejection reasons
-
getRecommendations(result: MailValidatorResponse): string[]- Get actionable recommendations
Wrapper class providing a fluent API:
const result = new EmailValidationResult(rawResult);
result.isValid(); // boolean
result.isSafeToSend(); // boolean
result.shouldReject(); // boolean
result.getRiskLevel(); // 'low' | 'medium' | 'high'
result.getScore(); // 0-100
result.getRejectionReasons(); // string[]
result.getRecommendations(); // string[]
result.raw; // Access original response-
isValidEmail(email: string): boolean- Quick syntax validation
-
normalizeEmailAddress(email: string, provider: MXHostType): string- Normalize email based on provider rules
-
splitEmailDomain(email: string): DomainParts | false- Parse domain parts from email
-
calculateStringEntropy(str: string): number- Calculate string randomness
-
checkSpamList(domainOrIp: string): Promise<boolean>- Check if domain/IP is on spam lists
-
resolveMxRecords(domain: string): Promise<MxRecord[]>- Resolve MX records for domain
-
checkSSLCertificate(domain: string, port?: number): Promise<CertificateInfo>- Check SSL certificate
-
whois(domain: string): Promise<WhoisResult>- Get WHOIS information
-
retryWithBackoff<T>(fn: () => Promise<T>, config?: RetryConfig): Promise<T>- Retry function with exponential backoff
-
isHappyMailError(error: unknown): boolean- Check if error is a Happy Mail error
-
isRecoverableError(error: unknown): boolean- Check if error can be retried
-
getPreset(name: PresetName): Partial<HappyEmailClientOptions>- Get configuration preset by name
-
PRESETS: Record<PresetName, Partial<HappyEmailClientOptions>>- All available presets
-
PRESET_DESCRIPTIONS: Record<PresetName, string>- Descriptions of all presets
Validate email addresses during user signup or registration:
import { HappyEmailClient, isValidEmail, isValid } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({ preset: 'development' });
async function validateFormEmail(email: string) {
// Quick syntax check first
if (!isValidEmail(email)) {
return { valid: false, message: 'Invalid email format' };
}
// Fast validation without bounce check
const result = await client.quickCheck(email);
if (!isValid(result)) {
return { valid: false, message: 'Email cannot be used' };
}
if (result.risks.disposableDomain) {
return { valid: false, message: 'Disposable emails not allowed' };
}
return { valid: true };
}Clean and validate existing email lists:
import { HappyEmailClient, isSafeToSend, getScore } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({ preset: 'production' });
const results = await client.validateBatch(emailList, {
concurrency: 10,
onProgress: (done, total) => console.log(`${done}/${total}`),
});
const safeEmails = results
.filter((r) => r.success && r.result && isSafeToSend(r.result))
.map((r) => ({
email: r.email,
score: getScore(r.result!),
}));
console.log(`${safeEmails.length} safe emails out of ${emailList.length}`);Validate emails before sending marketing campaigns:
import { HappyEmailClient, getRiskLevel, getScore } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({ preset: 'strict' });
for (const email of campaignList) {
const result = await client.fullCheck(email);
const risk = getRiskLevel(result);
const score = getScore(result);
if (risk === 'low' && score >= 80) {
// Safe to send
await sendMarketingEmail(email);
} else {
console.log(`Skipping ${email}: ${risk} risk, score ${score}`);
}
}Check if a domain is properly configured for email:
import { HappyEmailClient } from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient();
const info = await client.getDomainInfo('user@example.com');
console.log(`Domain: ${info.domain}`);
console.log(`Health Score: ${info.healthScore}/100`);
console.log(`Assessment: ${info.assessment}`);
if (info.healthScore >= 75) {
console.log('✅ Domain is well configured');
} else {
console.log('⚠️ Domain may have issues:');
if (!info.hasMX) console.log(' - Missing MX records');
if (!info.hasSPF) console.log(' - Missing SPF record');
if (!info.hasDMARC) console.log(' - Missing DMARC record');
}Handle errors gracefully with automatic retries:
import {
HappyEmailClient,
retryWithBackoff,
NetworkError,
APIError,
ErrorCode,
} from '@launchfirstagency/happy-mail';
const client = new HappyEmailClient({ preset: 'production' });
try {
const result = await retryWithBackoff(async () => client.validateEmail('user@example.com'), {
maxAttempts: 3,
initialDelay: 1000,
backoffMultiplier: 2,
});
console.log('Validation successful:', result);
} catch (error) {
if (error instanceof NetworkError) {
console.error('Network issue persists after retries');
} else if (error instanceof APIError) {
if (error.code === ErrorCode.RATE_LIMIT_EXCEEDED) {
console.error('Rate limit exceeded');
} else if (error.code === ErrorCode.API_KEY_INVALID) {
console.error('Invalid API key');
}
}
}Complete working examples are available in the examples directory:
- Quick Start - Basic email validation
- Configuration Presets - Using configuration presets
- Result Interpretation - Using helper functions
- Batch Validation - Validating multiple emails
- Convenience Methods - quickCheck, fullCheck, etc.
- Error Handling - Custom errors and retry strategies
- Inbox Health - Domain health checking
- Branded Types - Type-safe email handling
- Utilities - Using utility functions
- Bulk Verification - NeverBounce bulk API
Run any example with:
npx ts-node examples/01-quick-start.ts- Node.js >= 20
- npm >= 10
Contributions are welcome! Please feel free to submit a Pull Request.
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run coverage
# Build
npm run build
# View coverage report
open coverage/lcov-report/index.htmlAGPL License - see LICENSE file for details
For issues, questions, or contributions, please visit our GitHub repository.
Made with ❤️ by Launch First Agency