feat: Enhance Nodemailer transport with attachment support and raw message streaming
This commit is contained in:
parent
a89e780165
commit
26d11289ea
2 changed files with 47 additions and 5 deletions
|
@ -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!');
|
||||||
|
|
|
@ -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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue