feat: Enhance Nodemailer transport with attachment support and raw message streaming

This commit is contained in:
Siwat Sirichai 2025-08-19 02:09:10 +07:00
parent a89e780165
commit 26d11289ea
2 changed files with 47 additions and 5 deletions

View file

@ -16,7 +16,7 @@ async function nodemailerTransportExample() {
apiKey: 'cebc9a7f-4e0c-4fda-9dd0-85f48c02800c', apiKey: 'cebc9a7f-4e0c-4fda-9dd0-85f48c02800c',
port: 80, port: 80,
secure: false, // Set to true for wss:// secure: false, // Set to true for wss://
debug: false debug: true
}); });
// Create Nodemailer transporter // Create Nodemailer transporter
@ -51,7 +51,15 @@ async function nodemailerTransportExample() {
<li>Nodemailer compatibility</li> <li>Nodemailer compatibility</li>
<li>WebSocket-based SMTP relay</li> <li>WebSocket-based SMTP relay</li>
</ul> </ul>
` <p><strong>This email includes a test attachment!</strong></p>
`,
attachments: [
{
filename: 'test-attachment.txt',
content: Buffer.from(`This is a test attachment generated at ${new Date().toISOString()}\n\nRandom data: ${Math.random()}\nUUID: ${Date.now()}-${Math.random().toString(36).substr(2, 9)}\n\nFeatures tested:\n- File attachment support\n- Binary content handling\n- MIME multipart encoding\n- WebSocket SMTP transport\n\nEnd of test file.`, 'utf8'),
contentType: 'text/plain'
}
]
}); });
console.log('Email sent successfully!'); console.log('Email sent successfully!');

View file

@ -6,6 +6,7 @@ import { EventEmitter } from 'events';
import { SMTPOverWSClient } from './client'; import { SMTPOverWSClient } from './client';
import { SMTPClientConfig, ConnectionState } from './types'; import { SMTPClientConfig, ConnectionState } from './types';
import { ConnectionError, MessageError, TimeoutError } from './errors'; import { ConnectionError, MessageError, TimeoutError } from './errors';
import { Readable } from 'stream';
/** /**
* Nodemailer transport interface compatibility * Nodemailer transport interface compatibility
@ -45,7 +46,8 @@ export interface MailMessage {
data: any; data: any;
message: { message: {
_envelope: Envelope; _envelope: Envelope;
_raw: string | Buffer; _raw?: string | Buffer;
createReadStream(): Readable;
}; };
mailer: any; mailer: any;
} }
@ -202,9 +204,11 @@ export class SMTPWSTransport extends EventEmitter {
*/ */
private async sendMail(mail: MailMessage): Promise<SendResult> { private async sendMail(mail: MailMessage): Promise<SendResult> {
const envelope = this.extractEnvelope(mail); const envelope = this.extractEnvelope(mail);
const raw = mail.message._raw;
const messageId = this.generateMessageId(); const messageId = this.generateMessageId();
// Get the raw message content from Nodemailer's stream
const rawMessage = await this.getRawMessage(mail);
// Build complete SMTP transaction // Build complete SMTP transaction
let smtpTransaction = ''; let smtpTransaction = '';
@ -220,7 +224,7 @@ export class SMTPWSTransport extends EventEmitter {
smtpTransaction += 'DATA\r\n'; smtpTransaction += 'DATA\r\n';
// Message content // Message content
const messageData = this.prepareMessageData(raw); const messageData = this.prepareMessageData(rawMessage);
smtpTransaction += messageData; smtpTransaction += messageData;
// QUIT // QUIT
@ -337,6 +341,36 @@ export class SMTPWSTransport extends EventEmitter {
return `smtp-ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; return `smtp-ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
} }
/**
* Get raw message content from Nodemailer mail object
*/
private async getRawMessage(mail: MailMessage): Promise<string> {
return new Promise((resolve, reject) => {
// If raw message is already available, use it
if (mail.message._raw) {
resolve(mail.message._raw.toString());
return;
}
// Otherwise, stream the message from Nodemailer
const chunks: Buffer[] = [];
const stream = mail.message.createReadStream();
stream.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
stream.on('end', () => {
const rawMessage = Buffer.concat(chunks).toString();
resolve(rawMessage);
});
stream.on('error', (error) => {
reject(new MessageError(`Failed to read message stream: ${error.message}`, 'stream-read', 0));
});
});
}
} }
/** /**