diff --git a/src/queue-manager.js b/src/queue-manager.js index 6fdf0ee..4b166e3 100644 --- a/src/queue-manager.js +++ b/src/queue-manager.js @@ -40,13 +40,39 @@ export class QueueManager { { store: new SQLiteStore({ path: this.dbPath, - // Store email - serialize: (data) => JSON.stringify(data), - deserialize: (text) => JSON.parse(text), + // Buffer handling + serialize: (data) => { + // Convert attachment buffers to base64 + const serializable = { ...data }; + if (serializable.attachments) { + serializable.attachments = serializable.attachments.map((att) => ({ + ...att, + content: att.content && Buffer.isBuffer(att.content) + ? att.content.toString('base64') + : att.content, + _isBuffer: Buffer.isBuffer(att.content), + })); + } + return JSON.stringify(serializable); + }, + deserialize: (text) => { + const data = JSON.parse(text); + // Convert base64 back to Buffers + if (data.attachments) { + data.attachments = data.attachments.map((att) => ({ + ...att, + content: att._isBuffer && typeof att.content === 'string' + ? Buffer.from(att.content, 'base64') + : att.content, + })); + // Clean up + data.attachments.forEach((att) => delete att._isBuffer); + } + return data; + }, }), - // Retry + // Retry configuration maxRetries: queueConfig.maxRetries, - retryDelay: queueConfig.retryDelay, concurrent: 1, // Retry exponential backoff afterProcessDelay: 100, diff --git a/src/smtp-client.js b/src/smtp-client.js index e5a7d94..77e0ba8 100644 --- a/src/smtp-client.js +++ b/src/smtp-client.js @@ -50,6 +50,24 @@ export class SMTPClient { const transport = this.getTransport(provider); try { + // Validate and sanitize attachments + const sanitizedAttachments = (emailData.attachments || []).map((att) => { + // Ensure content is a Buffer or string + if (att.content && typeof att.content === 'object' && !Buffer.isBuffer(att.content)) { + this.logger.warn('Invalid attachment content type, skipping', { + filename: att.filename, + type: typeof att.content, + }); + return null; + } + return { + filename: att.filename || 'attachment', + content: att.content, + contentType: att.contentType || 'application/octet-stream', + encoding: att.encoding || 'base64', + }; + }).filter(Boolean); // Remove null entries + // Prepare email const mailOptions = { from: provider.from, @@ -58,7 +76,7 @@ export class SMTPClient { text: emailData.text, html: emailData.html, headers: emailData.headers || {}, - attachments: emailData.attachments || [], + attachments: sanitizedAttachments, // Reply-to original sender replyTo: emailData.envelope.from, diff --git a/src/smtp-server.js b/src/smtp-server.js index a63e71e..a2b3472 100644 --- a/src/smtp-server.js +++ b/src/smtp-server.js @@ -119,7 +119,8 @@ export class IncomingSMTPServer { content: att.content, contentType: att.contentType, contentDisposition: att.contentDisposition, - size: att.size, + contentId: att.contentId, + encoding: 'base64', })) || [], messageId: parsed.messageId, date: parsed.date, @@ -135,6 +136,20 @@ export class IncomingSMTPServer { throw new Error('Missing recipient address'); } + // Check for email loops (sender sending to themselves) + const loopRecipients = emailData.envelope.to.filter( + (recipient) => recipient.toLowerCase() === emailData.envelope.from.toLowerCase() + ); + + if (loopRecipients.length > 0) { + this.logger.warn('Email loop detected - dropping email', { + from: emailData.envelope.from, + to: emailData.envelope.to, + loopRecipients, + }); + throw new Error('Email loop detected: sender cannot send to themselves'); + } + this.logger.debug('Email parsed successfully', { from: emailData.envelope.from, to: emailData.envelope.to,