import * as plugins from '../../plugins.js'; import * as paths from '../../paths.js'; import { Email } from '../core/classes.email.js'; import { EmailSignJob } from './classes.emailsignjob.js'; // Email delivery status export var DeliveryStatus; (function (DeliveryStatus) { DeliveryStatus["PENDING"] = "pending"; DeliveryStatus["SENDING"] = "sending"; DeliveryStatus["DELIVERED"] = "delivered"; DeliveryStatus["FAILED"] = "failed"; DeliveryStatus["DEFERRED"] = "deferred"; // Temporary failure, will retry })(DeliveryStatus || (DeliveryStatus = {})); export class EmailSendJob { emailServerRef; email; mxServers = []; currentMxIndex = 0; options; deliveryInfo; constructor(emailServerRef, emailArg, options = {}) { this.email = emailArg; this.emailServerRef = emailServerRef; // Set default options this.options = { maxRetries: options.maxRetries || 3, retryDelay: options.retryDelay || 30000, // 30 seconds connectionTimeout: options.connectionTimeout || 60000, // 60 seconds tlsOptions: options.tlsOptions || {}, debugMode: options.debugMode || false }; // Initialize delivery info this.deliveryInfo = { status: DeliveryStatus.PENDING, attempts: 0, logs: [] }; } /** * Send the email to its recipients */ async send() { try { // Check if the email is valid before attempting to send this.validateEmail(); // Resolve MX records for the recipient domain await this.resolveMxRecords(); // Try to send the email return await this.attemptDelivery(); } catch (error) { this.log(`Critical error in send process: ${error.message}`); this.deliveryInfo.status = DeliveryStatus.FAILED; this.deliveryInfo.error = error; // Save failed email for potential future retry or analysis await this.saveFailed(); return DeliveryStatus.FAILED; } } /** * Validate the email before sending */ validateEmail() { if (!this.email.to || this.email.to.length === 0) { throw new Error('No recipients specified'); } if (!this.email.from) { throw new Error('No sender specified'); } const fromDomain = this.email.getFromDomain(); if (!fromDomain) { throw new Error('Invalid sender domain'); } } /** * Resolve MX records for the recipient domain */ async resolveMxRecords() { const domain = this.email.getPrimaryRecipient()?.split('@')[1]; if (!domain) { throw new Error('Invalid recipient domain'); } this.log(`Resolving MX records for domain: ${domain}`); try { const addresses = await this.resolveMx(domain); // Sort by priority (lowest number = highest priority) addresses.sort((a, b) => a.priority - b.priority); this.mxServers = addresses.map(mx => mx.exchange); this.log(`Found ${this.mxServers.length} MX servers: ${this.mxServers.join(', ')}`); if (this.mxServers.length === 0) { throw new Error(`No MX records found for domain: ${domain}`); } } catch (error) { this.log(`Failed to resolve MX records: ${error.message}`); throw new Error(`MX lookup failed for ${domain}: ${error.message}`); } } /** * Attempt to deliver the email with retries */ async attemptDelivery() { while (this.deliveryInfo.attempts < this.options.maxRetries) { this.deliveryInfo.attempts++; this.deliveryInfo.lastAttempt = new Date(); this.deliveryInfo.status = DeliveryStatus.SENDING; try { this.log(`Delivery attempt ${this.deliveryInfo.attempts} of ${this.options.maxRetries}`); // Try each MX server in order of priority while (this.currentMxIndex < this.mxServers.length) { const currentMx = this.mxServers[this.currentMxIndex]; this.deliveryInfo.mxServer = currentMx; try { this.log(`Attempting delivery to MX server: ${currentMx}`); await this.connectAndSend(currentMx); // If we get here, email was sent successfully this.deliveryInfo.status = DeliveryStatus.DELIVERED; this.deliveryInfo.deliveryTime = new Date(); this.log(`Email delivered successfully to ${currentMx}`); // Record delivery for sender reputation monitoring this.recordDeliveryEvent('delivered'); // Save successful email record await this.saveSuccess(); return DeliveryStatus.DELIVERED; } catch (error) { this.log(`Failed to deliver to ${currentMx}: ${error.message}`); this.currentMxIndex++; // If this MX server failed, try the next one if (this.currentMxIndex >= this.mxServers.length) { throw error; // No more MX servers to try } } } throw new Error('All MX servers failed'); } catch (error) { this.deliveryInfo.error = error; // Check if this is a permanent failure if (this.isPermanentFailure(error)) { this.log('Permanent failure detected, not retrying'); this.deliveryInfo.status = DeliveryStatus.FAILED; // Record permanent failure for bounce management this.recordDeliveryEvent('bounced', true); await this.saveFailed(); return DeliveryStatus.FAILED; } // This is a temporary failure if (this.deliveryInfo.attempts < this.options.maxRetries) { this.log(`Temporary failure, will retry in ${this.options.retryDelay}ms`); this.deliveryInfo.status = DeliveryStatus.DEFERRED; this.deliveryInfo.nextAttempt = new Date(Date.now() + this.options.retryDelay); // Record temporary failure for monitoring this.recordDeliveryEvent('deferred'); // Reset MX server index for next retry this.currentMxIndex = 0; // Wait before retrying await this.delay(this.options.retryDelay); } } } // If we get here, all retries failed this.deliveryInfo.status = DeliveryStatus.FAILED; await this.saveFailed(); return DeliveryStatus.FAILED; } /** * Connect to a specific MX server and send the email using SmtpClient */ async connectAndSend(mxServer) { this.log(`Connecting to ${mxServer}:25`); try { // Check if IP warmup is enabled and get an IP to use let localAddress = undefined; try { const fromDomain = this.email.getFromDomain(); const bestIP = this.emailServerRef.getBestIPForSending({ from: this.email.from, to: this.email.getAllRecipients(), domain: fromDomain, isTransactional: this.email.priority === 'high' }); if (bestIP) { this.log(`Using warmed-up IP ${bestIP} for sending`); localAddress = bestIP; // Record the send for warm-up tracking this.emailServerRef.recordIPSend(bestIP); } } catch (error) { this.log(`Error selecting IP address: ${error.message}`); } // Get SMTP client from UnifiedEmailServer const smtpClient = this.emailServerRef.getSmtpClient(mxServer, 25); // Sign the email with DKIM if available let signedEmail = this.email; try { const fromDomain = this.email.getFromDomain(); if (fromDomain && this.emailServerRef.hasDkimKey(fromDomain)) { // Convert email to RFC822 format for signing const emailMessage = this.email.toRFC822String(); // Create sign job with proper options const emailSignJob = new EmailSignJob(this.emailServerRef, { domain: fromDomain, selector: 'default', // Using default selector headers: {}, // Headers will be extracted from emailMessage body: emailMessage }); // Get the DKIM signature header const signatureHeader = await emailSignJob.getSignatureHeader(emailMessage); // Add the signature to the email if (signatureHeader) { // For now, we'll use the email as-is since SmtpClient will handle DKIM this.log(`Email ready for DKIM signing for domain: ${fromDomain}`); } } } catch (error) { this.log(`Failed to prepare DKIM: ${error.message}`); } // Send the email using SmtpClient const result = await smtpClient.sendMail(signedEmail); if (result.success) { this.log(`Email sent successfully: ${result.response}`); // Record the send for reputation monitoring this.recordDeliveryEvent('delivered'); } else { throw new Error(result.error?.message || 'Failed to send email'); } } catch (error) { this.log(`Failed to send email via ${mxServer}: ${error.message}`); throw error; } } /** * Record delivery event for monitoring */ recordDeliveryEvent(eventType, isHardBounce = false) { try { const domain = this.email.getFromDomain(); if (domain) { if (eventType === 'delivered') { this.emailServerRef.recordDelivery(domain); } else if (eventType === 'bounced') { // Get the receiving domain for bounce recording let receivingDomain = null; const primaryRecipient = this.email.getPrimaryRecipient(); if (primaryRecipient) { receivingDomain = primaryRecipient.split('@')[1]; } if (receivingDomain) { this.emailServerRef.recordBounce(domain, receivingDomain, isHardBounce ? 'hard' : 'soft', this.deliveryInfo.error?.message || 'Unknown error'); } } } } catch (error) { this.log(`Failed to record delivery event: ${error.message}`); } } /** * Check if an error represents a permanent failure */ isPermanentFailure(error) { const permanentFailurePatterns = [ 'User unknown', 'No such user', 'Mailbox not found', 'Invalid recipient', 'Account disabled', 'Account suspended', 'Domain not found', 'No such domain', 'Invalid domain', 'Relay access denied', 'Access denied', 'Blacklisted', 'Blocked', '550', // Permanent failure SMTP code '551', '552', '553', '554' ]; const errorMessage = error.message.toLowerCase(); return permanentFailurePatterns.some(pattern => errorMessage.includes(pattern.toLowerCase())); } /** * Resolve MX records for a domain */ resolveMx(domain) { return new Promise((resolve, reject) => { plugins.dns.resolveMx(domain, (err, addresses) => { if (err) { reject(err); } else { resolve(addresses || []); } }); }); } /** * Log a message with timestamp */ log(message) { const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] ${message}`; this.deliveryInfo.logs.push(logEntry); if (this.options.debugMode) { console.log(`[EmailSendJob] ${logEntry}`); } } /** * Save successful email to storage */ async saveSuccess() { try { // Use the existing email storage path const emailContent = this.email.toRFC822String(); const fileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_success.eml`; const filePath = plugins.path.join(paths.sentEmailsDir, fileName); await plugins.smartfs.directory(paths.sentEmailsDir).recursive().create(); await plugins.smartfs.file(filePath).write(emailContent); // Also save delivery info const infoFileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_info.json`; const infoPath = plugins.path.join(paths.sentEmailsDir, infoFileName); await plugins.smartfs.file(infoPath).write(JSON.stringify(this.deliveryInfo, null, 2)); this.log(`Email saved to ${fileName}`); } catch (error) { this.log(`Failed to save email: ${error.message}`); } } /** * Save failed email to storage */ async saveFailed() { try { // Use the existing email storage path const emailContent = this.email.toRFC822String(); const fileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_failed.eml`; const filePath = plugins.path.join(paths.failedEmailsDir, fileName); await plugins.smartfs.directory(paths.failedEmailsDir).recursive().create(); await plugins.smartfs.file(filePath).write(emailContent); // Also save delivery info with error details const infoFileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_error.json`; const infoPath = plugins.path.join(paths.failedEmailsDir, infoFileName); await plugins.smartfs.file(infoPath).write(JSON.stringify(this.deliveryInfo, null, 2)); this.log(`Failed email saved to ${fileName}`); } catch (error) { this.log(`Failed to save failed email: ${error.message}`); } } /** * Delay for specified milliseconds */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbHNlbmRqb2IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL2RlbGl2ZXJ5L2NsYXNzZXMuZW1haWxzZW5kam9iLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDakQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBY3pELHdCQUF3QjtBQUN4QixNQUFNLENBQU4sSUFBWSxjQU1YO0FBTkQsV0FBWSxjQUFjO0lBQ3hCLHFDQUFtQixDQUFBO0lBQ25CLHFDQUFtQixDQUFBO0lBQ25CLHlDQUF1QixDQUFBO0lBQ3ZCLG1DQUFpQixDQUFBO0lBQ2pCLHVDQUFxQixDQUFBLENBQUMsZ0NBQWdDO0FBQ3hELENBQUMsRUFOVyxjQUFjLEtBQWQsY0FBYyxRQU16QjtBQWNELE1BQU0sT0FBTyxZQUFZO0lBQ3ZCLGNBQWMsQ0FBcUI7SUFDM0IsS0FBSyxDQUFRO0lBQ2IsU0FBUyxHQUFhLEVBQUUsQ0FBQztJQUN6QixjQUFjLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLE9BQU8sQ0FBb0I7SUFDNUIsWUFBWSxDQUFlO0lBRWxDLFlBQVksY0FBa0MsRUFBRSxRQUFlLEVBQUUsVUFBNkIsRUFBRTtRQUM5RixJQUFJLENBQUMsS0FBSyxHQUFHLFFBQVEsQ0FBQztRQUN0QixJQUFJLENBQUMsY0FBYyxHQUFHLGNBQWMsQ0FBQztRQUVyQyxzQkFBc0I7UUFDdEIsSUFBSSxDQUFDLE9BQU8sR0FBRztZQUNiLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVSxJQUFJLENBQUM7WUFDbkMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxVQUFVLElBQUksS0FBSyxFQUFFLGFBQWE7WUFDdEQsaUJBQWlCLEVB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