2025-05-08 01:13:54 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
import * as paths from '../../paths.js';
|
|
|
|
import { Email } from '../core/classes.email.js';
|
2025-03-15 16:21:37 +00:00
|
|
|
import { EmailSignJob } from './classes.emailsignjob.js';
|
2025-05-21 00:12:49 +00:00
|
|
|
import type { UnifiedEmailServer } from '../routing/classes.unified.email.server.js';
|
2025-05-27 14:06:22 +00:00
|
|
|
import type { SmtpClient } from './smtpclient/smtp-client.js';
|
|
|
|
import type { ISmtpSendResult } from './smtpclient/interfaces.js';
|
2024-02-16 13:28:40 +01:00
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
// Configuration options for email sending
|
|
|
|
export interface IEmailSendOptions {
|
|
|
|
maxRetries?: number;
|
|
|
|
retryDelay?: number; // in milliseconds
|
|
|
|
connectionTimeout?: number; // in milliseconds
|
|
|
|
tlsOptions?: plugins.tls.ConnectionOptions;
|
|
|
|
debugMode?: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Email delivery status
|
|
|
|
export enum DeliveryStatus {
|
|
|
|
PENDING = 'pending',
|
|
|
|
SENDING = 'sending',
|
|
|
|
DELIVERED = 'delivered',
|
|
|
|
FAILED = 'failed',
|
|
|
|
DEFERRED = 'deferred' // Temporary failure, will retry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detailed information about delivery attempts
|
|
|
|
export interface DeliveryInfo {
|
|
|
|
status: DeliveryStatus;
|
|
|
|
attempts: number;
|
|
|
|
error?: Error;
|
|
|
|
lastAttempt?: Date;
|
|
|
|
nextAttempt?: Date;
|
|
|
|
mxServer?: string;
|
|
|
|
deliveryTime?: Date;
|
|
|
|
logs: string[];
|
|
|
|
}
|
|
|
|
|
2024-02-16 13:28:40 +01:00
|
|
|
export class EmailSendJob {
|
2025-05-21 00:12:49 +00:00
|
|
|
emailServerRef: UnifiedEmailServer;
|
2024-02-16 13:28:40 +01:00
|
|
|
private email: Email;
|
2025-03-15 13:45:29 +00:00
|
|
|
private mxServers: string[] = [];
|
|
|
|
private currentMxIndex = 0;
|
|
|
|
private options: IEmailSendOptions;
|
|
|
|
public deliveryInfo: DeliveryInfo;
|
2024-02-16 13:28:40 +01:00
|
|
|
|
2025-05-21 00:12:49 +00:00
|
|
|
constructor(emailServerRef: UnifiedEmailServer, emailArg: Email, options: IEmailSendOptions = {}) {
|
2024-02-16 13:28:40 +01:00
|
|
|
this.email = emailArg;
|
2025-05-21 00:12:49 +00:00
|
|
|
this.emailServerRef = emailServerRef;
|
2025-03-15 13:45:29 +00:00
|
|
|
|
|
|
|
// Set default options
|
|
|
|
this.options = {
|
|
|
|
maxRetries: options.maxRetries || 3,
|
2025-05-27 14:06:22 +00:00
|
|
|
retryDelay: options.retryDelay || 30000, // 30 seconds
|
|
|
|
connectionTimeout: options.connectionTimeout || 60000, // 60 seconds
|
|
|
|
tlsOptions: options.tlsOptions || {},
|
2025-03-15 13:45:29 +00:00
|
|
|
debugMode: options.debugMode || false
|
|
|
|
};
|
|
|
|
|
|
|
|
// Initialize delivery info
|
|
|
|
this.deliveryInfo = {
|
|
|
|
status: DeliveryStatus.PENDING,
|
|
|
|
attempts: 0,
|
|
|
|
logs: []
|
|
|
|
};
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Send the email to its recipients
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
async send(): Promise<DeliveryStatus> {
|
2024-02-16 13:28:40 +01:00
|
|
|
try {
|
2025-03-15 13:45:29 +00:00
|
|
|
// 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();
|
2024-02-16 13:28:40 +01:00
|
|
|
} catch (error) {
|
2025-03-15 13:45:29 +00:00
|
|
|
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;
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
/**
|
|
|
|
* Validate the email before sending
|
|
|
|
*/
|
|
|
|
private validateEmail(): void {
|
|
|
|
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');
|
|
|
|
}
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
/**
|
|
|
|
* Resolve MX records for the recipient domain
|
|
|
|
*/
|
|
|
|
private async resolveMxRecords(): Promise<void> {
|
|
|
|
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}`);
|
|
|
|
}
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
/**
|
|
|
|
* Attempt to deliver the email with retries
|
|
|
|
*/
|
|
|
|
private async attemptDelivery(): Promise<DeliveryStatus> {
|
|
|
|
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}`);
|
|
|
|
|
2025-05-07 20:20:17 +00:00
|
|
|
// Record delivery for sender reputation monitoring
|
|
|
|
this.recordDeliveryEvent('delivered');
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
// Save successful email record
|
|
|
|
await this.saveSuccess();
|
|
|
|
return DeliveryStatus.DELIVERED;
|
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed to deliver to ${currentMx}: ${error.message}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
this.currentMxIndex++;
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// If this MX server failed, try the next one
|
|
|
|
if (this.currentMxIndex >= this.mxServers.length) {
|
|
|
|
throw error; // No more MX servers to try
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
2025-03-15 13:45:29 +00:00
|
|
|
|
|
|
|
throw new Error('All MX servers failed');
|
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.deliveryInfo.error = error;
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
// Check if this is a permanent failure
|
|
|
|
if (this.isPermanentFailure(error)) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log('Permanent failure detected, not retrying');
|
2025-03-15 13:45:29 +00:00
|
|
|
this.deliveryInfo.status = DeliveryStatus.FAILED;
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// Record permanent failure for bounce management
|
|
|
|
this.recordDeliveryEvent('bounced', true);
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
await this.saveFailed();
|
|
|
|
return DeliveryStatus.FAILED;
|
|
|
|
}
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we get here, all retries failed
|
|
|
|
this.deliveryInfo.status = DeliveryStatus.FAILED;
|
|
|
|
await this.saveFailed();
|
|
|
|
return DeliveryStatus.FAILED;
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
|
|
|
|
2025-03-15 13:45:29 +00:00
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Connect to a specific MX server and send the email using SmtpClient
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private async connectAndSend(mxServer: string): Promise<void> {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Connecting to ${mxServer}:25`);
|
|
|
|
|
|
|
|
try {
|
2025-05-07 20:20:17 +00:00
|
|
|
// Check if IP warmup is enabled and get an IP to use
|
|
|
|
let localAddress: string | undefined = undefined;
|
2025-05-21 00:12:49 +00:00
|
|
|
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;
|
2025-05-07 20:20:17 +00:00
|
|
|
|
2025-05-21 00:12:49 +00:00
|
|
|
// Record the send for warm-up tracking
|
|
|
|
this.emailServerRef.recordIPSend(bestIP);
|
2025-05-07 20:20:17 +00:00
|
|
|
}
|
2025-05-21 00:12:49 +00:00
|
|
|
} catch (error) {
|
|
|
|
this.log(`Error selecting IP address: ${error.message}`);
|
2025-05-07 20:20:17 +00:00
|
|
|
}
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// Get SMTP client from UnifiedEmailServer
|
|
|
|
const smtpClient = this.emailServerRef.getSmtpClient(mxServer, 25);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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();
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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
|
|
|
|
});
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// Get the DKIM signature header
|
|
|
|
const signatureHeader = await emailSignJob.getSignatureHeader(emailMessage);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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}`);
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed to prepare DKIM: ${error.message}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// Send the email using SmtpClient
|
|
|
|
const result: ISmtpSendResult = await smtpClient.sendMail(signedEmail);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
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;
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
2024-02-16 13:28:40 +01:00
|
|
|
}
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-07 20:20:17 +00:00
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Record delivery event for monitoring
|
2025-05-07 20:20:17 +00:00
|
|
|
*/
|
|
|
|
private recordDeliveryEvent(
|
2025-05-27 14:06:22 +00:00
|
|
|
eventType: 'delivered' | 'bounced' | 'deferred',
|
2025-05-07 20:20:17 +00:00
|
|
|
isHardBounce: boolean = false
|
|
|
|
): void {
|
|
|
|
try {
|
|
|
|
const domain = this.email.getFromDomain();
|
2025-05-27 14:06:22 +00:00
|
|
|
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'
|
|
|
|
);
|
|
|
|
}
|
2025-05-07 20:20:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed to record delivery event: ${error.message}`);
|
2025-05-07 20:20:17 +00:00
|
|
|
}
|
|
|
|
}
|
2025-03-15 13:45:29 +00:00
|
|
|
|
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Check if an error represents a permanent failure
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private isPermanentFailure(error: Error): boolean {
|
|
|
|
const permanentFailurePatterns = [
|
2025-05-27 14:06:22 +00:00
|
|
|
'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'
|
2025-03-15 13:45:29 +00:00
|
|
|
];
|
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
const errorMessage = error.message.toLowerCase();
|
|
|
|
return permanentFailurePatterns.some(pattern =>
|
|
|
|
errorMessage.includes(pattern.toLowerCase())
|
|
|
|
);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve MX records for a domain
|
|
|
|
*/
|
|
|
|
private resolveMx(domain: string): Promise<plugins.dns.MxRecord[]> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
plugins.dns.resolveMx(domain, (err, addresses) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
2025-05-27 14:06:22 +00:00
|
|
|
resolve(addresses || []);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Log a message with timestamp
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private log(message: string): void {
|
|
|
|
const timestamp = new Date().toISOString();
|
|
|
|
const logEntry = `[${timestamp}] ${message}`;
|
|
|
|
this.deliveryInfo.logs.push(logEntry);
|
|
|
|
|
|
|
|
if (this.options.debugMode) {
|
2025-05-27 14:06:22 +00:00
|
|
|
console.log(`[EmailSendJob] ${logEntry}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Save successful email to storage
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private async saveSuccess(): Promise<void> {
|
|
|
|
try {
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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.smartfile.fs.ensureDir(paths.sentEmailsDir);
|
|
|
|
await plugins.smartfile.memory.toFs(emailContent, filePath);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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.smartfile.memory.toFs(JSON.stringify(this.deliveryInfo, null, 2), infoPath);
|
|
|
|
|
|
|
|
this.log(`Email saved to ${fileName}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed to save email: ${error.message}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Save failed email to storage
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private async saveFailed(): Promise<void> {
|
|
|
|
try {
|
2025-05-27 14:06:22 +00:00
|
|
|
// 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.smartfile.fs.ensureDir(paths.failedEmailsDir);
|
|
|
|
await plugins.smartfile.memory.toFs(emailContent, filePath);
|
|
|
|
|
|
|
|
// 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.smartfile.memory.toFs(JSON.stringify(this.deliveryInfo, null, 2), infoPath);
|
2025-03-15 13:45:29 +00:00
|
|
|
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed email saved to ${fileName}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
} catch (error) {
|
2025-05-27 14:06:22 +00:00
|
|
|
this.log(`Failed to save failed email: ${error.message}`);
|
2025-03-15 13:45:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-27 14:06:22 +00:00
|
|
|
* Delay for specified milliseconds
|
2025-03-15 13:45:29 +00:00
|
|
|
*/
|
|
|
|
private delay(ms: number): Promise<void> {
|
|
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
}
|
|
|
|
}
|