update
This commit is contained in:
@@ -1,625 +0,0 @@
|
|||||||
import * as plugins from '../../plugins.js';
|
|
||||||
import { EmailService } from '../services/classes.emailservice.js';
|
|
||||||
import { logger } from '../../logger.js';
|
|
||||||
|
|
||||||
// Import MTA classes
|
|
||||||
import { MtaService } from './classes.mta.js';
|
|
||||||
import { Email as MtaEmail } from '../core/classes.email.js';
|
|
||||||
import { DeliveryStatus } from './classes.emailsendjob.js';
|
|
||||||
|
|
||||||
// Re-export for use in index.ts
|
|
||||||
export { DeliveryStatus };
|
|
||||||
|
|
||||||
// Import Email types
|
|
||||||
export interface IEmailOptions {
|
|
||||||
from: string;
|
|
||||||
to: string[];
|
|
||||||
cc?: string[];
|
|
||||||
bcc?: string[];
|
|
||||||
subject: string;
|
|
||||||
text?: string;
|
|
||||||
html?: string;
|
|
||||||
attachments?: IAttachment[];
|
|
||||||
headers?: { [key: string]: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Reuse the IAttachment interface
|
|
||||||
export interface IAttachment {
|
|
||||||
filename: string;
|
|
||||||
content: Buffer;
|
|
||||||
contentType: string;
|
|
||||||
contentId?: string;
|
|
||||||
encoding?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Email status details
|
|
||||||
*/
|
|
||||||
export interface IEmailStatusDetails {
|
|
||||||
/** Number of delivery attempts */
|
|
||||||
attempts?: number;
|
|
||||||
/** Timestamp of last delivery attempt */
|
|
||||||
lastAttempt?: Date;
|
|
||||||
/** Timestamp of next scheduled attempt */
|
|
||||||
nextAttempt?: Date;
|
|
||||||
/** Error message if delivery failed */
|
|
||||||
error?: string;
|
|
||||||
/** Message explaining the status */
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Email status response
|
|
||||||
*/
|
|
||||||
export interface IEmailStatusResponse {
|
|
||||||
/** Current status of the email */
|
|
||||||
status: DeliveryStatus | 'unknown' | 'error';
|
|
||||||
/** Additional status details */
|
|
||||||
details?: IEmailStatusDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for sending an email via MTA
|
|
||||||
*/
|
|
||||||
export interface ISendEmailOptions {
|
|
||||||
/** Whether to use MIME format conversion */
|
|
||||||
useMimeFormat?: boolean;
|
|
||||||
/** Whether to track clicks */
|
|
||||||
trackClicks?: boolean;
|
|
||||||
/** Whether to track opens */
|
|
||||||
trackOpens?: boolean;
|
|
||||||
/** Message priority (1-5, where 1 is highest) */
|
|
||||||
priority?: number;
|
|
||||||
/** Message scheduling options */
|
|
||||||
schedule?: {
|
|
||||||
/** Time to send the email */
|
|
||||||
sendAt?: Date | string;
|
|
||||||
/** Time the message expires */
|
|
||||||
expireAt?: Date | string;
|
|
||||||
};
|
|
||||||
/** DKIM signing options */
|
|
||||||
dkim?: {
|
|
||||||
/** Whether to sign the message */
|
|
||||||
sign?: boolean;
|
|
||||||
/** Domain to use for signing */
|
|
||||||
domain?: string;
|
|
||||||
/** Key selector to use */
|
|
||||||
selector?: string;
|
|
||||||
};
|
|
||||||
/** Additional headers */
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
/** Message tags for categorization */
|
|
||||||
tags?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MtaConnector {
|
|
||||||
public emailRef: EmailService;
|
|
||||||
private mtaService: MtaService;
|
|
||||||
|
|
||||||
constructor(emailRefArg: EmailService, mtaService?: MtaService) {
|
|
||||||
this.emailRef = emailRefArg;
|
|
||||||
this.mtaService = mtaService || this.emailRef.mtaService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email using the MTA service
|
|
||||||
* @param smartmail The email to send
|
|
||||||
* @param toAddresses Recipients (comma-separated or array)
|
|
||||||
* @param options Additional options
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email using the MTA service
|
|
||||||
* @param smartmail The email to send
|
|
||||||
* @param toAddresses Recipients (comma-separated or array)
|
|
||||||
* @param options Additional options
|
|
||||||
*/
|
|
||||||
public async sendEmail(
|
|
||||||
smartmail: plugins.smartmail.Smartmail<any>,
|
|
||||||
toAddresses: string | string[],
|
|
||||||
options: ISendEmailOptions = {}
|
|
||||||
): Promise<string> {
|
|
||||||
// Check if recipients are on the suppression list
|
|
||||||
const recipients = Array.isArray(toAddresses)
|
|
||||||
? toAddresses
|
|
||||||
: toAddresses.split(',').map(addr => addr.trim());
|
|
||||||
|
|
||||||
// Filter out suppressed recipients
|
|
||||||
const validRecipients = [];
|
|
||||||
const suppressedRecipients = [];
|
|
||||||
|
|
||||||
for (const recipient of recipients) {
|
|
||||||
if (this.emailRef.bounceManager.isEmailSuppressed(recipient)) {
|
|
||||||
suppressedRecipients.push(recipient);
|
|
||||||
} else {
|
|
||||||
validRecipients.push(recipient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log suppressed recipients
|
|
||||||
if (suppressedRecipients.length > 0) {
|
|
||||||
logger.log('warn', `Skipping ${suppressedRecipients.length} suppressed recipients`, {
|
|
||||||
suppressedRecipients
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all recipients are suppressed, throw error
|
|
||||||
if (validRecipients.length === 0) {
|
|
||||||
throw new Error('All recipients are on the suppression list');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue with valid recipients
|
|
||||||
try {
|
|
||||||
// Use filtered recipients - already an array, no need for toArray
|
|
||||||
|
|
||||||
// Add recipients to smartmail if they're not already added
|
|
||||||
if (!smartmail.options.to || smartmail.options.to.length === 0) {
|
|
||||||
for (const recipient of validRecipients) {
|
|
||||||
smartmail.addRecipient(recipient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle options
|
|
||||||
const emailOptions: Record<string, any> = { ...options };
|
|
||||||
|
|
||||||
// Check if we should use MIME format
|
|
||||||
const useMimeFormat = options.useMimeFormat !== false; // Default to true
|
|
||||||
|
|
||||||
if (useMimeFormat) {
|
|
||||||
// Use smartmail's MIME conversion for improved handling
|
|
||||||
try {
|
|
||||||
// Convert to MIME format
|
|
||||||
const mimeEmail = await smartmail.toMimeFormat(smartmail.options.creationObjectRef);
|
|
||||||
|
|
||||||
// Parse the MIME email to create an MTA Email
|
|
||||||
return this.sendMimeEmail(mimeEmail, validRecipients);
|
|
||||||
} catch (mimeError) {
|
|
||||||
logger.log('warn', `Failed to use MIME format, falling back to direct conversion: ${mimeError.message}`);
|
|
||||||
// Fall back to direct conversion
|
|
||||||
return this.sendDirectEmail(smartmail, validRecipients);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use direct conversion
|
|
||||||
return this.sendDirectEmail(smartmail, validRecipients);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Failed to send email via MTA: ${error.message}`, {
|
|
||||||
eventType: 'emailError',
|
|
||||||
provider: 'mta',
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if this is a bounce-related error
|
|
||||||
if (error.message.includes('550') || // Rejected
|
|
||||||
error.message.includes('551') || // User not local
|
|
||||||
error.message.includes('552') || // Mailbox full
|
|
||||||
error.message.includes('553') || // Bad mailbox name
|
|
||||||
error.message.includes('554') || // Transaction failed
|
|
||||||
error.message.includes('does not exist') ||
|
|
||||||
error.message.includes('unknown user') ||
|
|
||||||
error.message.includes('invalid recipient')) {
|
|
||||||
|
|
||||||
// Process as a bounce
|
|
||||||
for (const recipient of validRecipients) {
|
|
||||||
await this.emailRef.bounceManager.processSmtpFailure(
|
|
||||||
recipient,
|
|
||||||
error.message,
|
|
||||||
{
|
|
||||||
sender: smartmail.options.from,
|
|
||||||
statusCode: error.message.match(/\b([45]\d{2})\b/) ? error.message.match(/\b([45]\d{2})\b/)[1] : undefined
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a MIME-formatted email
|
|
||||||
* @param mimeEmail The MIME-formatted email content
|
|
||||||
* @param recipients The email recipients
|
|
||||||
*/
|
|
||||||
private async sendMimeEmail(mimeEmail: string, recipients: string[]): Promise<string> {
|
|
||||||
try {
|
|
||||||
// Parse the MIME email
|
|
||||||
const parsedEmail = await plugins.mailparser.simpleParser(mimeEmail);
|
|
||||||
|
|
||||||
// Extract necessary information for MTA Email
|
|
||||||
const mtaEmail = new MtaEmail({
|
|
||||||
from: parsedEmail.from?.text || '',
|
|
||||||
to: recipients,
|
|
||||||
subject: parsedEmail.subject || '',
|
|
||||||
text: parsedEmail.text || '',
|
|
||||||
html: parsedEmail.html || undefined,
|
|
||||||
attachments: parsedEmail.attachments?.map(attachment => ({
|
|
||||||
filename: attachment.filename || 'attachment',
|
|
||||||
content: attachment.content,
|
|
||||||
contentType: attachment.contentType || 'application/octet-stream',
|
|
||||||
contentId: attachment.contentId
|
|
||||||
})) || [],
|
|
||||||
headers: Object.fromEntries([...parsedEmail.headers].map(([key, value]) => [key, String(value)]))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send using MTA
|
|
||||||
const emailId = await this.mtaService.send(mtaEmail);
|
|
||||||
|
|
||||||
logger.log('info', `MIME email sent via MTA to ${recipients.join(', ')}`, {
|
|
||||||
eventType: 'sentEmail',
|
|
||||||
provider: 'mta',
|
|
||||||
emailId,
|
|
||||||
to: recipients
|
|
||||||
});
|
|
||||||
|
|
||||||
return emailId;
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Failed to send MIME email: ${error.message}`);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an email using direct conversion (fallback method)
|
|
||||||
* @param smartmail The Smartmail instance
|
|
||||||
* @param recipients The email recipients
|
|
||||||
*/
|
|
||||||
private async sendDirectEmail(
|
|
||||||
smartmail: plugins.smartmail.Smartmail<any>,
|
|
||||||
recipients: string[]
|
|
||||||
): Promise<string> {
|
|
||||||
// Map SmartMail attachments to MTA attachments with improved content type handling
|
|
||||||
const attachments: IAttachment[] = smartmail.attachments.map(attachment => {
|
|
||||||
// Try to determine content type from file extension if not explicitly set
|
|
||||||
let contentType = (attachment as any)?.contentType;
|
|
||||||
|
|
||||||
if (!contentType) {
|
|
||||||
const extension = attachment.parsedPath.ext.toLowerCase();
|
|
||||||
contentType = this.getContentTypeFromExtension(extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
filename: attachment.parsedPath.base,
|
|
||||||
content: Buffer.from(attachment.contentBuffer),
|
|
||||||
contentType: contentType || 'application/octet-stream',
|
|
||||||
// Add content ID for inline images if available
|
|
||||||
contentId: (attachment as any)?.contentId
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create MTA Email
|
|
||||||
const mtaEmail = new MtaEmail({
|
|
||||||
from: smartmail.options.from,
|
|
||||||
to: recipients,
|
|
||||||
subject: smartmail.getSubject(),
|
|
||||||
text: smartmail.getBody(false), // Plain text version
|
|
||||||
html: smartmail.getBody(true), // HTML version
|
|
||||||
attachments
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prepare arrays for CC and BCC recipients
|
|
||||||
let ccRecipients: string[] = [];
|
|
||||||
let bccRecipients: string[] = [];
|
|
||||||
|
|
||||||
// Add CC recipients if present
|
|
||||||
if (smartmail.options.cc?.length > 0) {
|
|
||||||
// Handle CC recipients - smartmail options may contain email objects
|
|
||||||
ccRecipients = smartmail.options.cc.map(r => {
|
|
||||||
if (typeof r === 'string') return r;
|
|
||||||
return typeof (r as any).address === 'string' ? (r as any).address :
|
|
||||||
typeof (r as any).email === 'string' ? (r as any).email : '';
|
|
||||||
});
|
|
||||||
mtaEmail.cc = ccRecipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add BCC recipients if present
|
|
||||||
if (smartmail.options.bcc?.length > 0) {
|
|
||||||
// Handle BCC recipients - smartmail options may contain email objects
|
|
||||||
bccRecipients = smartmail.options.bcc.map(r => {
|
|
||||||
if (typeof r === 'string') return r;
|
|
||||||
return typeof (r as any).address === 'string' ? (r as any).address :
|
|
||||||
typeof (r as any).email === 'string' ? (r as any).email : '';
|
|
||||||
});
|
|
||||||
mtaEmail.bcc = bccRecipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send using MTA
|
|
||||||
const emailId = await this.mtaService.send(mtaEmail);
|
|
||||||
|
|
||||||
logger.log('info', `Email sent via MTA to ${recipients.join(', ')}`, {
|
|
||||||
eventType: 'sentEmail',
|
|
||||||
provider: 'mta',
|
|
||||||
emailId,
|
|
||||||
to: recipients
|
|
||||||
});
|
|
||||||
|
|
||||||
return emailId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get content type from file extension
|
|
||||||
* @param extension The file extension (with or without dot)
|
|
||||||
* @returns The content type or undefined if unknown
|
|
||||||
*/
|
|
||||||
private getContentTypeFromExtension(extension: string): string | undefined {
|
|
||||||
// Remove dot if present
|
|
||||||
const ext = extension.startsWith('.') ? extension.substring(1) : extension;
|
|
||||||
|
|
||||||
// Common content types
|
|
||||||
const contentTypes: Record<string, string> = {
|
|
||||||
'pdf': 'application/pdf',
|
|
||||||
'jpg': 'image/jpeg',
|
|
||||||
'jpeg': 'image/jpeg',
|
|
||||||
'png': 'image/png',
|
|
||||||
'gif': 'image/gif',
|
|
||||||
'svg': 'image/svg+xml',
|
|
||||||
'webp': 'image/webp',
|
|
||||||
'txt': 'text/plain',
|
|
||||||
'html': 'text/html',
|
|
||||||
'csv': 'text/csv',
|
|
||||||
'json': 'application/json',
|
|
||||||
'xml': 'application/xml',
|
|
||||||
'zip': 'application/zip',
|
|
||||||
'doc': 'application/msword',
|
|
||||||
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
||||||
'xls': 'application/vnd.ms-excel',
|
|
||||||
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
||||||
'ppt': 'application/vnd.ms-powerpoint',
|
|
||||||
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
||||||
};
|
|
||||||
|
|
||||||
return contentTypes[ext.toLowerCase()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve and process an incoming email
|
|
||||||
* For MTA, this would handle an email already received by the SMTP server
|
|
||||||
* @param emailData The raw email data or identifier
|
|
||||||
* @param options Additional processing options
|
|
||||||
*/
|
|
||||||
public async receiveEmail(
|
|
||||||
emailData: string,
|
|
||||||
options: {
|
|
||||||
preserveHeaders?: boolean;
|
|
||||||
includeRawData?: boolean;
|
|
||||||
validateSender?: boolean;
|
|
||||||
} = {}
|
|
||||||
): Promise<plugins.smartmail.Smartmail<any>> {
|
|
||||||
try {
|
|
||||||
// In a real implementation, this would retrieve an email from the MTA storage
|
|
||||||
// For now, we can use a simplified approach:
|
|
||||||
|
|
||||||
// Parse the email (assuming emailData is a raw email or a file path)
|
|
||||||
const parsedEmail = await plugins.mailparser.simpleParser(emailData);
|
|
||||||
|
|
||||||
// Extract sender information
|
|
||||||
const sender = parsedEmail.from?.text || '';
|
|
||||||
let senderName = '';
|
|
||||||
let senderEmail = sender;
|
|
||||||
|
|
||||||
// Try to extract name and email from "Name <email>" format
|
|
||||||
const senderMatch = sender.match(/(.*?)\s*<([^>]+)>/);
|
|
||||||
if (senderMatch) {
|
|
||||||
senderName = senderMatch[1].trim();
|
|
||||||
senderEmail = senderMatch[2].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract recipients
|
|
||||||
const recipients = [];
|
|
||||||
if (parsedEmail.to) {
|
|
||||||
// Extract recipients safely
|
|
||||||
try {
|
|
||||||
// Handle AddressObject or AddressObject[]
|
|
||||||
if (parsedEmail.to && typeof parsedEmail.to === 'object' && 'value' in parsedEmail.to) {
|
|
||||||
const addressList = Array.isArray(parsedEmail.to.value)
|
|
||||||
? parsedEmail.to.value
|
|
||||||
: [parsedEmail.to.value];
|
|
||||||
|
|
||||||
for (const addr of addressList) {
|
|
||||||
if (addr && typeof addr === 'object' && 'address' in addr) {
|
|
||||||
recipients.push({
|
|
||||||
name: addr.name || '',
|
|
||||||
email: addr.address || ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If parsing fails, try to extract as string
|
|
||||||
let toStr = '';
|
|
||||||
if (parsedEmail.to && typeof parsedEmail.to === 'object' && 'text' in parsedEmail.to) {
|
|
||||||
toStr = String(parsedEmail.to.text || '');
|
|
||||||
}
|
|
||||||
if (toStr) {
|
|
||||||
recipients.push({
|
|
||||||
name: '',
|
|
||||||
email: toStr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a more comprehensive creation object reference
|
|
||||||
const creationObjectRef: Record<string, any> = {
|
|
||||||
sender: {
|
|
||||||
name: senderName,
|
|
||||||
email: senderEmail
|
|
||||||
},
|
|
||||||
recipients: recipients,
|
|
||||||
subject: parsedEmail.subject || '',
|
|
||||||
date: parsedEmail.date || new Date(),
|
|
||||||
messageId: parsedEmail.messageId || '',
|
|
||||||
inReplyTo: parsedEmail.inReplyTo || null,
|
|
||||||
references: parsedEmail.references || []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Include headers if requested
|
|
||||||
if (options.preserveHeaders) {
|
|
||||||
creationObjectRef.headers = parsedEmail.headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include raw data if requested
|
|
||||||
if (options.includeRawData) {
|
|
||||||
creationObjectRef.rawData = emailData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a Smartmail from the parsed email
|
|
||||||
const smartmail = new plugins.smartmail.Smartmail({
|
|
||||||
from: senderEmail,
|
|
||||||
subject: parsedEmail.subject || '',
|
|
||||||
body: parsedEmail.html || parsedEmail.text || '',
|
|
||||||
creationObjectRef
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add recipients
|
|
||||||
if (recipients.length > 0) {
|
|
||||||
for (const recipient of recipients) {
|
|
||||||
smartmail.addRecipient(recipient.email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add CC recipients if present
|
|
||||||
if (parsedEmail.cc) {
|
|
||||||
try {
|
|
||||||
// Extract CC recipients safely
|
|
||||||
if (parsedEmail.cc && typeof parsedEmail.cc === 'object' && 'value' in parsedEmail.cc) {
|
|
||||||
const ccList = Array.isArray(parsedEmail.cc.value)
|
|
||||||
? parsedEmail.cc.value
|
|
||||||
: [parsedEmail.cc.value];
|
|
||||||
|
|
||||||
for (const addr of ccList) {
|
|
||||||
if (addr && typeof addr === 'object' && 'address' in addr) {
|
|
||||||
smartmail.addRecipient(addr.address, 'cc');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If parsing fails, try to extract as string
|
|
||||||
let ccStr = '';
|
|
||||||
if (parsedEmail.cc && typeof parsedEmail.cc === 'object' && 'text' in parsedEmail.cc) {
|
|
||||||
ccStr = String(parsedEmail.cc.text || '');
|
|
||||||
}
|
|
||||||
if (ccStr) {
|
|
||||||
smartmail.addRecipient(ccStr, 'cc');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add BCC recipients if present (usually not in received emails, but just in case)
|
|
||||||
if (parsedEmail.bcc) {
|
|
||||||
try {
|
|
||||||
// Extract BCC recipients safely
|
|
||||||
if (parsedEmail.bcc && typeof parsedEmail.bcc === 'object' && 'value' in parsedEmail.bcc) {
|
|
||||||
const bccList = Array.isArray(parsedEmail.bcc.value)
|
|
||||||
? parsedEmail.bcc.value
|
|
||||||
: [parsedEmail.bcc.value];
|
|
||||||
|
|
||||||
for (const addr of bccList) {
|
|
||||||
if (addr && typeof addr === 'object' && 'address' in addr) {
|
|
||||||
smartmail.addRecipient(addr.address, 'bcc');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If parsing fails, try to extract as string
|
|
||||||
let bccStr = '';
|
|
||||||
if (parsedEmail.bcc && typeof parsedEmail.bcc === 'object' && 'text' in parsedEmail.bcc) {
|
|
||||||
bccStr = String(parsedEmail.bcc.text || '');
|
|
||||||
}
|
|
||||||
if (bccStr) {
|
|
||||||
smartmail.addRecipient(bccStr, 'bcc');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add attachments if present
|
|
||||||
if (parsedEmail.attachments && parsedEmail.attachments.length > 0) {
|
|
||||||
for (const attachment of parsedEmail.attachments) {
|
|
||||||
// Create smartfile with proper constructor options
|
|
||||||
const file = new plugins.smartfile.SmartFile({
|
|
||||||
path: attachment.filename || 'attachment',
|
|
||||||
contentBuffer: attachment.content,
|
|
||||||
base: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set content type and content ID for proper MIME handling
|
|
||||||
if (attachment.contentType) {
|
|
||||||
(file as any).contentType = attachment.contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.contentId) {
|
|
||||||
(file as any).contentId = attachment.contentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
smartmail.addAttachment(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate sender if requested
|
|
||||||
if (options.validateSender && this.emailRef.emailValidator) {
|
|
||||||
try {
|
|
||||||
const validationResult = await this.emailRef.emailValidator.validate(senderEmail, {
|
|
||||||
checkSyntaxOnly: true // Use syntax-only for performance
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add validation info to the creation object
|
|
||||||
creationObjectRef.senderValidation = validationResult;
|
|
||||||
} catch (validationError) {
|
|
||||||
logger.log('warn', `Sender validation error: ${validationError.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return smartmail;
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Failed to receive email via MTA: ${error.message}`, {
|
|
||||||
eventType: 'emailError',
|
|
||||||
provider: 'mta',
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the status of a sent email
|
|
||||||
* @param emailId The email ID to check
|
|
||||||
* @returns Current status and details
|
|
||||||
*/
|
|
||||||
public async checkEmailStatus(emailId: string): Promise<IEmailStatusResponse> {
|
|
||||||
try {
|
|
||||||
const status = this.mtaService.getEmailStatus(emailId);
|
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
return {
|
|
||||||
status: 'unknown' as const,
|
|
||||||
details: { message: 'Email not found' }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Use type assertion to ensure this passes type check
|
|
||||||
status: status.status as DeliveryStatus,
|
|
||||||
details: {
|
|
||||||
attempts: status.attempts,
|
|
||||||
lastAttempt: status.lastAttempt,
|
|
||||||
nextAttempt: status.nextAttempt,
|
|
||||||
error: status.error?.message,
|
|
||||||
message: `Status: ${status.status}${status.error ? `, Error: ${status.error.message}` : ''}`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.log('error', `Failed to check email status: ${error.message}`, {
|
|
||||||
eventType: 'emailError',
|
|
||||||
provider: 'mta',
|
|
||||||
emailId,
|
|
||||||
error: error.message
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 'error' as const,
|
|
||||||
details: { message: error.message }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user