update
This commit is contained in:
@ -54,7 +54,10 @@ export class CommandHandler extends EventEmitter {
|
||||
* Send MAIL FROM command
|
||||
*/
|
||||
public async sendMailFrom(connection: ISmtpConnection, fromAddress: string): Promise<ISmtpResponse> {
|
||||
const command = `${SMTP_COMMANDS.MAIL_FROM}:<${fromAddress}>`;
|
||||
// Handle empty return path for bounce messages
|
||||
const command = fromAddress === ''
|
||||
? `${SMTP_COMMANDS.MAIL_FROM}:<>`
|
||||
: `${SMTP_COMMANDS.MAIL_FROM}:<${fromAddress}>`;
|
||||
return this.sendCommand(connection, command);
|
||||
}
|
||||
|
||||
@ -77,15 +80,19 @@ export class CommandHandler extends EventEmitter {
|
||||
* Send email data content
|
||||
*/
|
||||
public async sendDataContent(connection: ISmtpConnection, emailData: string): Promise<ISmtpResponse> {
|
||||
// Ensure email data ends with CRLF.CRLF
|
||||
let data = emailData;
|
||||
// Normalize line endings to CRLF
|
||||
let data = emailData.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, '\r\n');
|
||||
|
||||
// Ensure email data ends with CRLF
|
||||
if (!data.endsWith(LINE_ENDINGS.CRLF)) {
|
||||
data += LINE_ENDINGS.CRLF;
|
||||
}
|
||||
data += '.' + LINE_ENDINGS.CRLF;
|
||||
|
||||
// Perform dot stuffing (escape lines starting with a dot)
|
||||
data = data.replace(/\n\./g, '\n..');
|
||||
data = data.replace(/\r\n\./g, '\r\n..');
|
||||
|
||||
// Add termination sequence
|
||||
data += '.' + LINE_ENDINGS.CRLF;
|
||||
|
||||
return this.sendRawData(connection, data);
|
||||
}
|
||||
@ -306,7 +313,7 @@ export class CommandHandler extends EventEmitter {
|
||||
const response = parseSmtpResponse(this.responseBuffer);
|
||||
this.responseBuffer = '';
|
||||
|
||||
if (isSuccessCode(response.code) || response.code >= 400) {
|
||||
if (isSuccessCode(response.code) || (response.code >= 300 && response.code < 400) || response.code >= 400) {
|
||||
this.pendingCommand.resolve(response);
|
||||
} else {
|
||||
this.pendingCommand.reject(new Error(`Command failed: ${response.message}`));
|
||||
|
@ -57,20 +57,27 @@ export class SmtpClient extends EventEmitter {
|
||||
*/
|
||||
public async sendMail(email: Email): Promise<ISmtpSendResult> {
|
||||
const startTime = Date.now();
|
||||
const fromAddress = email.from;
|
||||
const recipients = Array.isArray(email.to) ? email.to : [email.to];
|
||||
|
||||
// Extract clean email addresses without display names for SMTP operations
|
||||
const fromAddress = email.getFromAddress();
|
||||
const recipients = email.getToAddresses();
|
||||
const ccRecipients = email.getCcAddresses();
|
||||
const bccRecipients = email.getBccAddresses();
|
||||
|
||||
// Combine all recipients for SMTP operations
|
||||
const allRecipients = [...recipients, ...ccRecipients, ...bccRecipients];
|
||||
|
||||
// Validate email addresses
|
||||
if (!validateSender(fromAddress)) {
|
||||
throw new Error(`Invalid sender address: ${fromAddress}`);
|
||||
}
|
||||
|
||||
const recipientErrors = validateRecipients(recipients);
|
||||
const recipientErrors = validateRecipients(allRecipients);
|
||||
if (recipientErrors.length > 0) {
|
||||
throw new Error(`Invalid recipients: ${recipientErrors.join(', ')}`);
|
||||
}
|
||||
|
||||
logEmailSend('start', recipients, this.options);
|
||||
logEmailSend('start', allRecipients, this.options);
|
||||
|
||||
let connection: ISmtpConnection | null = null;
|
||||
const result: ISmtpSendResult = {
|
||||
@ -79,7 +86,7 @@ export class SmtpClient extends EventEmitter {
|
||||
rejectedRecipients: [],
|
||||
envelope: {
|
||||
from: fromAddress,
|
||||
to: recipients
|
||||
to: allRecipients
|
||||
}
|
||||
};
|
||||
|
||||
@ -114,8 +121,8 @@ export class SmtpClient extends EventEmitter {
|
||||
throw new Error(`MAIL FROM failed: ${mailFromResponse.message}`);
|
||||
}
|
||||
|
||||
// Send RCPT TO for each recipient
|
||||
for (const recipient of recipients) {
|
||||
// Send RCPT TO for each recipient (includes TO, CC, and BCC)
|
||||
for (const recipient of allRecipients) {
|
||||
try {
|
||||
const rcptResponse = await this.commandHandler.sendRcptTo(connection, recipient);
|
||||
if (rcptResponse.code >= 400) {
|
||||
|
@ -8,12 +8,28 @@ import type { ISmtpClientOptions, ISmtpAuthOptions } from '../interfaces.js';
|
||||
|
||||
/**
|
||||
* Validate email address format
|
||||
* Supports RFC-compliant addresses including empty return paths for bounces
|
||||
*/
|
||||
export function validateEmailAddress(email: string): boolean {
|
||||
if (!email || typeof email !== 'string') {
|
||||
if (typeof email !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return REGEX_PATTERNS.EMAIL_ADDRESS.test(email.trim());
|
||||
|
||||
const trimmed = email.trim();
|
||||
|
||||
// Handle empty return path for bounce messages (RFC 5321)
|
||||
if (trimmed === '' || trimmed === '<>') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle display name formats
|
||||
const angleMatch = trimmed.match(/<([^>]+)>/);
|
||||
if (angleMatch) {
|
||||
return REGEX_PATTERNS.EMAIL_ADDRESS.test(angleMatch[1]);
|
||||
}
|
||||
|
||||
// Regular email validation
|
||||
return REGEX_PATTERNS.EMAIL_ADDRESS.test(trimmed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user