import * as plugins from '../plugins.js'; import { logger } from '../logger.js'; export interface IEmailValidationResult { isValid: boolean; hasMx: boolean; hasSpamMarkings: boolean; score: number; details?: { formatValid?: boolean; mxRecords?: string[]; disposable?: boolean; role?: boolean; spamIndicators?: string[]; errorMessage?: string; }; } /** * Advanced email validator class using smartmail's capabilities */ export class EmailValidator { private validator: plugins.smartmail.EmailAddressValidator; private dnsCache: Map = new Map(); constructor() { this.validator = new plugins.smartmail.EmailAddressValidator(); } /** * Validates an email address using comprehensive checks * @param email The email to validate * @param options Validation options * @returns Validation result with details */ public async validate( email: string, options: { checkMx?: boolean; checkDisposable?: boolean; checkRole?: boolean; checkSyntaxOnly?: boolean; } = {} ): Promise { try { const result: IEmailValidationResult = { isValid: false, hasMx: false, hasSpamMarkings: false, score: 0, details: { formatValid: false, spamIndicators: [] } }; // Always check basic format result.details.formatValid = this.validator.isValidEmailFormat(email); if (!result.details.formatValid) { result.details.errorMessage = 'Invalid email format'; return result; } // If syntax-only check is requested, return early if (options.checkSyntaxOnly) { result.isValid = true; result.score = 0.5; return result; } // Get domain for additional checks const domain = email.split('@')[1]; // Check MX records if (options.checkMx !== false) { try { const mxRecords = await this.getMxRecords(domain); result.details.mxRecords = mxRecords; result.hasMx = mxRecords && mxRecords.length > 0; if (!result.hasMx) { result.details.spamIndicators.push('No MX records'); result.details.errorMessage = 'Domain has no MX records'; } } catch (error) { logger.log('error', `Error checking MX records: ${error.message}`); result.details.errorMessage = 'Unable to check MX records'; } } // Check if domain is disposable if (options.checkDisposable !== false) { result.details.disposable = await this.validator.isDisposableEmail(email); if (result.details.disposable) { result.details.spamIndicators.push('Disposable email'); } } // Check if email is a role account if (options.checkRole !== false) { result.details.role = this.validator.isRoleAccount(email); if (result.details.role) { result.details.spamIndicators.push('Role account'); } } // Calculate spam score and final validity result.hasSpamMarkings = result.details.spamIndicators.length > 0; // Calculate a score between 0-1 based on checks let scoreFactors = 0; let scoreTotal = 0; // Format check (highest weight) scoreFactors += 0.4; if (result.details.formatValid) scoreTotal += 0.4; // MX check (high weight) if (options.checkMx !== false) { scoreFactors += 0.3; if (result.hasMx) scoreTotal += 0.3; } // Disposable check (medium weight) if (options.checkDisposable !== false) { scoreFactors += 0.2; if (!result.details.disposable) scoreTotal += 0.2; } // Role account check (low weight) if (options.checkRole !== false) { scoreFactors += 0.1; if (!result.details.role) scoreTotal += 0.1; } // Normalize score based on factors actually checked result.score = scoreFactors > 0 ? scoreTotal / scoreFactors : 0; // Email is valid if score is above 0.7 (configurable threshold) result.isValid = result.score >= 0.7; return result; } catch (error) { logger.log('error', `Email validation error: ${error.message}`); return { isValid: false, hasMx: false, hasSpamMarkings: true, score: 0, details: { formatValid: false, errorMessage: `Validation error: ${error.message}`, spamIndicators: ['Validation error'] } }; } } /** * Gets MX records for a domain with caching * @param domain Domain to check * @returns Array of MX records */ private async getMxRecords(domain: string): Promise { if (this.dnsCache.has(domain)) { return this.dnsCache.get(domain); } try { // Use smartmail's getMxRecords method const records = await this.validator.getMxRecords(domain); this.dnsCache.set(domain, records); // Cache expires after 1 hour setTimeout(() => { this.dnsCache.delete(domain); }, 60 * 60 * 1000); return records; } catch (error) { logger.log('error', `Error fetching MX records for ${domain}: ${error.message}`); return []; } } /** * Validates multiple email addresses in batch * @param emails Array of emails to validate * @param options Validation options * @returns Object with email addresses as keys and validation results as values */ public async validateBatch( emails: string[], options: { checkMx?: boolean; checkDisposable?: boolean; checkRole?: boolean; checkSyntaxOnly?: boolean; } = {} ): Promise> { const results: Record = {}; for (const email of emails) { results[email] = await this.validate(email, options); } return results; } /** * Quick check if an email format is valid (synchronous, no DNS checks) * @param email Email to check * @returns Boolean indicating if format is valid */ public isValidFormat(email: string): boolean { return this.validator.isValidEmailFormat(email); } }