import * as plugins from '../../plugins.js'; import * as paths from '../../paths.js'; import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js'; import { LRUCache } from 'lru-cache'; /** * Bounce types for categorizing the reasons for bounces */ export var BounceType; (function (BounceType) { // Hard bounces (permanent failures) BounceType["INVALID_RECIPIENT"] = "invalid_recipient"; BounceType["DOMAIN_NOT_FOUND"] = "domain_not_found"; BounceType["MAILBOX_FULL"] = "mailbox_full"; BounceType["MAILBOX_INACTIVE"] = "mailbox_inactive"; BounceType["BLOCKED"] = "blocked"; BounceType["SPAM_RELATED"] = "spam_related"; BounceType["POLICY_RELATED"] = "policy_related"; // Soft bounces (temporary failures) BounceType["SERVER_UNAVAILABLE"] = "server_unavailable"; BounceType["TEMPORARY_FAILURE"] = "temporary_failure"; BounceType["QUOTA_EXCEEDED"] = "quota_exceeded"; BounceType["NETWORK_ERROR"] = "network_error"; BounceType["TIMEOUT"] = "timeout"; // Special cases BounceType["AUTO_RESPONSE"] = "auto_response"; BounceType["CHALLENGE_RESPONSE"] = "challenge_response"; BounceType["UNKNOWN"] = "unknown"; })(BounceType || (BounceType = {})); /** * Hard vs soft bounce classification */ export var BounceCategory; (function (BounceCategory) { BounceCategory["HARD"] = "hard"; BounceCategory["SOFT"] = "soft"; BounceCategory["AUTO_RESPONSE"] = "auto_response"; BounceCategory["UNKNOWN"] = "unknown"; })(BounceCategory || (BounceCategory = {})); /** * Manager for handling email bounces */ export class BounceManager { // Retry strategy with exponential backoff retryStrategy = { maxRetries: 5, initialDelay: 15 * 60 * 1000, // 15 minutes maxDelay: 24 * 60 * 60 * 1000, // 24 hours backoffFactor: 2 }; // Store of bounced emails bounceStore = []; // Cache of recently bounced email addresses to avoid sending to known bad addresses bounceCache; // Suppression list for addresses that should not receive emails suppressionList = new Map(); storageManager; // StorageManager instance constructor(options) { // Set retry strategy with defaults if (options?.retryStrategy) { this.retryStrategy = { ...this.retryStrategy, ...options.retryStrategy }; } // Initialize bounce cache with LRU (least recently used) caching this.bounceCache = new LRUCache({ max: options?.maxCacheSize || 10000, ttl: options?.cacheTTL || 30 * 24 * 60 * 60 * 1000, // 30 days default }); // Store storage manager reference this.storageManager = options?.storageManager; // Load suppression list from storage // Note: This is async but we can't await in constructor // The suppression list will be loaded asynchronously this.loadSuppressionList().catch(error => { logger.log('error', `Failed to load suppression list on startup: ${error.message}`); }); } /** * Process a bounce notification * @param bounceData Bounce data to process * @returns Processed bounce record */ async processBounce(bounceData) { try { // Add required fields if missing const bounce = { id: bounceData.id || plugins.uuid.v4(), recipient: bounceData.recipient, sender: bounceData.sender, domain: bounceData.domain || bounceData.recipient.split('@')[1], subject: bounceData.subject, bounceType: bounceData.bounceType || BounceType.UNKNOWN, bounceCategory: bounceData.bounceCategory || BounceCategory.UNKNOWN, timestamp: bounceData.timestamp || Date.now(), smtpResponse: bounceData.smtpResponse, diagnosticCode: bounceData.diagnosticCode, statusCode: bounceData.statusCode, headers: bounceData.headers, processed: false, originalEmailId: bounceData.originalEmailId, retryCount: bounceData.retryCount || 0, nextRetryTime: bounceData.nextRetryTime }; // Determine bounce type and category via Rust bridge if not provided if (!bounceData.bounceType || bounceData.bounceType === BounceType.UNKNOWN) { const bridge = RustSecurityBridge.getInstance(); const rustResult = await bridge.detectBounce({ smtpResponse: bounce.smtpResponse, diagnosticCode: bounce.diagnosticCode, statusCode: bounce.statusCode, }); bounce.bounceType = rustResult.bounce_type; bounce.bounceCategory = rustResult.category; } // Process the bounce based on category switch (bounce.bounceCategory) { case BounceCategory.HARD: // Handle hard bounce - add to suppression list await this.handleHardBounce(bounce); break; case BounceCategory.SOFT: // Handle soft bounce - schedule retry if eligible await this.handleSoftBounce(bounce); break; case BounceCategory.AUTO_RESPONSE: // Handle auto-response - typically no action needed logger.log('info', `Auto-response detected for ${bounce.recipient}`); break; default: // Unknown bounce type - log for investigation logger.log('warn', `Unknown bounce type for ${bounce.recipient}`, { bounceType: bounce.bounceType, smtpResponse: bounce.smtpResponse }); break; } // Store the bounce record bounce.processed = true; this.bounceStore.push(bounce); // Update the bounce cache this.updateBounceCache(bounce); // Log the bounce logger.log(bounce.bounceCategory === BounceCategory.HARD ? 'warn' : 'info', `Email bounce processed: ${bounce.bounceCategory} bounce for ${bounce.recipient}`, { bounceType: bounce.bounceType, domain: bounce.domain, category: bounce.bounceCategory }); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: bounce.bounceCategory === BounceCategory.HARD ? SecurityLogLevel.WARN : SecurityLogLevel.INFO, type: SecurityEventType.EMAIL_VALIDATION, message: `Email bounce detected: ${bounce.bounceCategory} bounce for recipient`, domain: bounce.domain, details: { recipient: bounce.recipient, bounceType: bounce.bounceType, smtpResponse: bounce.smtpResponse, diagnosticCode: bounce.diagnosticCode, statusCode: bounce.statusCode }, success: false }); return bounce; } catch (error) { logger.log('error', `Error processing bounce: ${error.message}`, { error: error.message, bounceData }); throw error; } } /** * Process an SMTP failure as a bounce * @param recipient Recipient email * @param smtpResponse SMTP error response * @param options Additional options * @returns Processed bounce record */ async processSmtpFailure(recipient, smtpResponse, options = {}) { // Create bounce data from SMTP failure const bounceData = { recipient, sender: options.sender || '', domain: recipient.split('@')[1], smtpResponse, statusCode: options.statusCode, headers: options.headers, originalEmailId: options.originalEmailId, timestamp: Date.now() }; // Process as a regular bounce return this.processBounce(bounceData); } /** * Process a bounce notification email * @param bounceEmail The email containing bounce information * @returns Processed bounce record or null if not a bounce */ async processBounceEmail(bounceEmail) { try { // Check if this is a bounce notification const subject = bounceEmail.getSubject(); const body = bounceEmail.getBody(); // Check for common bounce notification subject patterns const isBounceSubject = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject); if (!isBounceSubject) { // Not a bounce notification based on subject return null; } // Extract original recipient from the body or headers let recipient = ''; let originalMessageId = ''; // Extract recipient from common bounce formats const recipientMatch = body.match(/(?:failed recipient|to[:=]\s*|recipient:|delivery failed:)\s*?/i); if (recipientMatch && recipientMatch[1]) { recipient = recipientMatch[1]; } // Extract diagnostic code let diagnosticCode = ''; const diagnosticMatch = body.match(/diagnostic(?:-|\\s+)code:\s*(.+)(?:\n|$)/i); if (diagnosticMatch && diagnosticMatch[1]) { diagnosticCode = diagnosticMatch[1].trim(); } // Extract SMTP status code let statusCode = ''; const statusMatch = body.match(/status(?:-|\\s+)code:\s*([0-9.]+)/i); if (statusMatch && statusMatch[1]) { statusCode = statusMatch[1].trim(); } // If recipient not found in standard patterns, try DSN (Delivery Status Notification) format if (!recipient) { // Look for DSN format with Original-Recipient or Final-Recipient fields const originalRecipientMatch = body.match(/original-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i); const finalRecipientMatch = body.match(/final-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i); if (originalRecipientMatch && originalRecipientMatch[1]) { recipient = originalRecipientMatch[1]; } else if (finalRecipientMatch && finalRecipientMatch[1]) { recipient = finalRecipientMatch[1]; } } // If still no recipient, can't process as bounce if (!recipient) { logger.log('warn', 'Could not extract recipient from bounce notification', { subject, sender: bounceEmail.from }); return null; } // Extract original message ID if available const messageIdMatch = body.match(/original[ -]message[ -]id:[ \t]*]+)>?/i); if (messageIdMatch && messageIdMatch[1]) { originalMessageId = messageIdMatch[1].trim(); } // Create bounce data const bounceData = { recipient, sender: bounceEmail.from, domain: recipient.split('@')[1], subject: bounceEmail.getSubject(), diagnosticCode, statusCode, timestamp: Date.now(), headers: {} }; // Process as a regular bounce return this.processBounce(bounceData); } catch (error) { logger.log('error', `Error processing bounce email: ${error.message}`); return null; } } /** * Handle a hard bounce by adding to suppression list * @param bounce The bounce record */ async handleHardBounce(bounce) { // Add to suppression list permanently (no expiry) this.addToSuppressionList(bounce.recipient, `Hard bounce: ${bounce.bounceType}`, undefined); // Increment bounce count in cache this.updateBounceCache(bounce); // Save to permanent storage await this.saveBounceRecord(bounce); // Log hard bounce for monitoring logger.log('warn', `Hard bounce for ${bounce.recipient}: ${bounce.bounceType}`, { domain: bounce.domain, smtpResponse: bounce.smtpResponse, diagnosticCode: bounce.diagnosticCode }); } /** * Handle a soft bounce by scheduling a retry if eligible * @param bounce The bounce record */ async handleSoftBounce(bounce) { // Check if we've exceeded max retries if (bounce.retryCount >= this.retryStrategy.maxRetries) { logger.log('warn', `Max retries exceeded for ${bounce.recipient}, treating as hard bounce`); // Convert to hard bounce after max retries bounce.bounceCategory = BounceCategory.HARD; await this.handleHardBounce(bounce); return; } // Calculate next retry time with exponential backoff const delay = Math.min(this.retryStrategy.initialDelay * Math.pow(this.retryStrategy.backoffFactor, bounce.retryCount), this.retryStrategy.maxDelay); bounce.retryCount++; bounce.nextRetryTime = Date.now() + delay; // Add to suppression list temporarily (with expiry) this.addToSuppressionList(bounce.recipient, `Soft bounce: ${bounce.bounceType}`, bounce.nextRetryTime); // Log the retry schedule logger.log('info', `Scheduled retry ${bounce.retryCount} for ${bounce.recipient} at ${new Date(bounce.nextRetryTime).toISOString()}`, { bounceType: bounce.bounceType, retryCount: bounce.retryCount, nextRetry: bounce.nextRetryTime }); } /** * Add an email address to the suppression list * @param email Email address to suppress * @param reason Reason for suppression * @param expiresAt Expiration timestamp (undefined for permanent) */ addToSuppressionList(email, reason, expiresAt) { this.suppressionList.set(email.toLowerCase(), { reason, timestamp: Date.now(), expiresAt }); // Save asynchronously without blocking this.saveSuppressionList().catch(error => { logger.log('error', `Failed to save suppression list after adding ${email}: ${error.message}`); }); logger.log('info', `Added ${email} to suppression list`, { reason, expiresAt: expiresAt ? new Date(expiresAt).toISOString() : 'permanent' }); } /** * Remove an email address from the suppression list * @param email Email address to remove */ removeFromSuppressionList(email) { const wasRemoved = this.suppressionList.delete(email.toLowerCase()); if (wasRemoved) { // Save asynchronously without blocking this.saveSuppressionList().catch(error => { logger.log('error', `Failed to save suppression list after removing ${email}: ${error.message}`); }); logger.log('info', `Removed ${email} from suppression list`); } } /** * Check if an email is on the suppression list * @param email Email address to check * @returns Whether the email is suppressed */ isEmailSuppressed(email) { const lowercaseEmail = email.toLowerCase(); const suppression = this.suppressionList.get(lowercaseEmail); if (!suppression) { return false; } // Check if suppression has expired if (suppression.expiresAt && Date.now() > suppression.expiresAt) { this.suppressionList.delete(lowercaseEmail); // Save asynchronously without blocking this.saveSuppressionList().catch(error => { logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`); }); return false; } return true; } /** * Get suppression information for an email * @param email Email address to check * @returns Suppression information or null if not suppressed */ getSuppressionInfo(email) { const lowercaseEmail = email.toLowerCase(); const suppression = this.suppressionList.get(lowercaseEmail); if (!suppression) { return null; } // Check if suppression has expired if (suppression.expiresAt && Date.now() > suppression.expiresAt) { this.suppressionList.delete(lowercaseEmail); // Save asynchronously without blocking this.saveSuppressionList().catch(error => { logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`); }); return null; } return suppression; } /** * Save suppression list to disk */ async saveSuppressionList() { try { const suppressionData = JSON.stringify(Array.from(this.suppressionList.entries())); if (this.storageManager) { // Use storage manager await this.storageManager.set('/email/bounces/suppression-list.json', suppressionData); } else { // Fall back to filesystem await plugins.smartfs.file(plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json')).write(suppressionData); } } catch (error) { logger.log('error', `Failed to save suppression list: ${error.message}`); } } /** * Load suppression list from disk */ async loadSuppressionList() { try { let entries = null; let needsMigration = false; if (this.storageManager) { // Try to load from storage manager first const suppressionData = await this.storageManager.get('/email/bounces/suppression-list.json'); if (suppressionData) { entries = JSON.parse(suppressionData); } else { // Check if data exists in filesystem and migrate const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json'); if (plugins.fs.existsSync(suppressionPath)) { const data = plugins.fs.readFileSync(suppressionPath, 'utf8'); entries = JSON.parse(data); needsMigration = true; logger.log('info', 'Migrating suppression list from filesystem to StorageManager'); } } } else { // No storage manager, use filesystem directly const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json'); if (plugins.fs.existsSync(suppressionPath)) { const data = plugins.fs.readFileSync(suppressionPath, 'utf8'); entries = JSON.parse(data); } } if (entries) { this.suppressionList = new Map(entries); // Clean expired entries const now = Date.now(); let expiredCount = 0; for (const [email, info] of this.suppressionList.entries()) { if (info.expiresAt && now > info.expiresAt) { this.suppressionList.delete(email); expiredCount++; } } if (expiredCount > 0 || needsMigration) { logger.log('info', `Cleaned ${expiredCount} expired entries from suppression list`); await this.saveSuppressionList(); } logger.log('info', `Loaded ${this.suppressionList.size} entries from suppression list`); } } catch (error) { logger.log('error', `Failed to load suppression list: ${error.message}`); } } /** * Save bounce record to disk * @param bounce Bounce record to save */ async saveBounceRecord(bounce) { try { const bounceData = JSON.stringify(bounce, null, 2); if (this.storageManager) { // Use storage manager await this.storageManager.set(`/email/bounces/records/${bounce.id}.json`, bounceData); } else { // Fall back to filesystem const bouncePath = plugins.path.join(paths.dataDir, 'emails', 'bounces', `${bounce.id}.json`); // Ensure directory exists const bounceDir = plugins.path.join(paths.dataDir, 'emails', 'bounces'); await plugins.smartfs.directory(bounceDir).recursive().create(); await plugins.smartfs.file(bouncePath).write(bounceData); } } catch (error) { logger.log('error', `Failed to save bounce record: ${error.message}`); } } /** * Update bounce cache with new bounce information * @param bounce Bounce record to update cache with */ updateBounceCache(bounce) { const email = bounce.recipient.toLowerCase(); const existing = this.bounceCache.get(email); if (existing) { // Update existing cache entry existing.lastBounce = bounce.timestamp; existing.count++; existing.type = bounce.bounceType; existing.category = bounce.bounceCategory; } else { // Create new cache entry this.bounceCache.set(email, { lastBounce: bounce.timestamp, count: 1, type: bounce.bounceType, category: bounce.bounceCategory }); } } /** * Check bounce history for an email address * @param email Email address to check * @returns Bounce information or null if no bounces */ getBounceInfo(email) { return this.bounceCache.get(email.toLowerCase()) || null; } /** * Get all known hard bounced addresses * @returns Array of hard bounced email addresses */ getHardBouncedAddresses() { const hardBounced = []; for (const [email, info] of this.bounceCache.entries()) { if (info.category === BounceCategory.HARD) { hardBounced.push(email); } } return hardBounced; } /** * Get suppression list * @returns Array of suppressed email addresses */ getSuppressionList() { return Array.from(this.suppressionList.keys()); } /** * Clear old bounce records (for maintenance) * @param olderThan Timestamp to remove records older than * @returns Number of records removed */ clearOldBounceRecords(olderThan) { let removed = 0; this.bounceStore = this.bounceStore.filter(bounce => { if (bounce.timestamp < olderThan) { removed++; return false; } return true; }); return removed; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ib3VuY2VtYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvbWFpbC9jb3JlL2NsYXNzZXMuYm91bmNlbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw4Q0FBOEMsQ0FBQztBQUNsRixPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBR3JDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksVUFxQlg7QUFyQkQsV0FBWSxVQUFVO0lBQ3BCLG9DQUFvQztJQUNwQyxxREFBdUMsQ0FBQTtJQUN2QyxtREFBcUMsQ0FBQTtJQUNyQywyQ0FBNkIsQ0FBQTtJQUM3QixtREFBcUMsQ0FBQTtJQUNyQyxpQ0FBbUIsQ0FBQTtJQUNuQiwyQ0FBNkIsQ0FBQTtJQUM3QiwrQ0FBaUMsQ0FBQTtJQUVqQyxvQ0FBb0M7SUFDcEMsdURBQXlDLENBQUE7SUFDekMscURBQXVDLENBQUE7SUFDdkMsK0NBQWlDLENBQUE7SUFDakMsNkNBQStCLENBQUE7SUFDL0IsaUNBQW1CLENBQUE7SUFFbkIsZ0JBQWdCO0lBQ2hCLDZDQUErQixDQUFBO0lBQy9CLHVEQUF5QyxDQUFBO0lBQ3pDLGlDQUFtQixDQUFBO0FBQ3JCLENBQUMsRUFyQlcsVUFBVSxLQUFWLFVBQVUsUUFxQnJCO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQU4sSUFBWSxjQUtYO0FBTEQsV0FBWSxjQUFjO0lBQ3hCLCtCQUFhLENBQUE7SUFDYiwrQkFBYSxD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