import * as plugins from '../../plugins.js'; import { logger } from '../../logger.js'; import { LRUCache } from 'lru-cache'; /** * Advanced email validator class using smartmail's capabilities */ export class EmailValidator { validator; dnsCache; constructor(options) { this.validator = new plugins.smartmail.EmailAddressValidator(); // Initialize LRU cache for DNS records this.dnsCache = new LRUCache({ // Default to 1000 entries (reasonable for most applications) max: options?.maxCacheSize || 1000, // Default TTL of 1 hour (DNS records don't change frequently) ttl: options?.cacheTTL || 60 * 60 * 1000, // Optional cache monitoring allowStale: false, updateAgeOnGet: true, // Add logging for cache events in production environments disposeAfter: (value, key) => { logger.log('debug', `DNS cache entry expired for domain: ${key}`); }, }); } /** * Validates an email address using comprehensive checks * @param email The email to validate * @param options Validation options * @returns Validation result with details */ async validate(email, options = {}) { try { const result = { 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 */ async getMxRecords(domain) { // Check cache first const cachedRecords = this.dnsCache.get(domain); if (cachedRecords) { logger.log('debug', `Using cached MX records for domain: ${domain}`); return cachedRecords; } try { // Use smartmail's getMxRecords method const records = await this.validator.getMxRecords(domain); // Store in cache (TTL is handled by the LRU cache configuration) this.dnsCache.set(domain, records); logger.log('debug', `Cached MX records for domain: ${domain}`); 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 */ async validateBatch(emails, options = {}) { const results = {}; 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 */ isValidFormat(email) { return this.validator.isValidEmailFormat(email); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbHZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvY29yZS9jbGFzc2VzLmVtYWlsdmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFpQnJDOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFDakIsU0FBUyxDQUEwQztJQUNuRCxRQUFRLENBQTZCO0lBRTdDLFlBQVksT0FHWDtRQUNDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFFL0QsdUNBQXVDO1FBQ3ZDLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxRQUFRLENBQW1CO1lBQzdDLDZEQUE2RDtZQUM3RCxHQUFHLEVBQUUsT0FBTyxFQUFFLFlBQVksSUFBSSxJQUFJO1lBQ2xDLDhEQUE4RDtZQUM5RCxHQUFHLEVBQUUsT0FBTyxFQUFFLFFBQVEsSUFBSSxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUk7WUFDeEMsNEJBQTRCO1lBQzVCLFVBQVUsRUFBRSxLQUFLO1lBQ2pCLGNBQWMsRUFBRSxJQUFJO1lBQ3BCLDBEQUEwRDtZQUMxRCxZQUFZLEVBQUUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUU7Z0JBQzNCLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLHVDQUF1QyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQ3BFLENBQUM7U0FDRixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsUUFBUSxDQUNuQixLQUFhLEVBQ2IsVUFLSSxFQUFFO1FBRU4sSUFBSSxDQUFDO1lBQ0gsTUFB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