|
|
|
@ -15,10 +15,8 @@ import {
|
|
|
|
|
SenderReputationMonitor,
|
|
|
|
|
type IReputationMonitorConfig
|
|
|
|
|
} from '../../deliverability/index.js';
|
|
|
|
|
import { DomainRouter } from './classes.domain.router.js';
|
|
|
|
|
import type {
|
|
|
|
|
IDomainRule
|
|
|
|
|
} from './classes.email.config.js';
|
|
|
|
|
import { EmailRouter } from './classes.email.router.js';
|
|
|
|
|
import type { IEmailRoute, IEmailAction, IEmailContext } from './interfaces.js';
|
|
|
|
|
import { Email } from '../core/classes.email.js';
|
|
|
|
|
import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js';
|
|
|
|
|
import { createSmtpServer } from '../delivery/smtpserver/index.js';
|
|
|
|
@ -32,13 +30,13 @@ import type { EmailProcessingMode, ISmtpSession as IBaseSmtpSession } from '../d
|
|
|
|
|
import type { DcRouter } from '../../classes.dcrouter.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extended SMTP session interface with domain rule information
|
|
|
|
|
* Extended SMTP session interface with route information
|
|
|
|
|
*/
|
|
|
|
|
export interface IExtendedSmtpSession extends ISmtpSession {
|
|
|
|
|
/**
|
|
|
|
|
* Matched domain rule for this session
|
|
|
|
|
* Matched route for this session
|
|
|
|
|
*/
|
|
|
|
|
matchedRule?: IDomainRule;
|
|
|
|
|
matchedRoute?: IEmailRoute;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -77,14 +75,8 @@ export interface IUnifiedEmailServerOptions {
|
|
|
|
|
connectionTimeout?: number;
|
|
|
|
|
socketTimeout?: number;
|
|
|
|
|
|
|
|
|
|
// Domain rules
|
|
|
|
|
domainRules: IDomainRule[];
|
|
|
|
|
|
|
|
|
|
// Default handling for unmatched domains
|
|
|
|
|
defaultMode: EmailProcessingMode;
|
|
|
|
|
defaultServer?: string;
|
|
|
|
|
defaultPort?: number;
|
|
|
|
|
defaultTls?: boolean;
|
|
|
|
|
// Email routing rules
|
|
|
|
|
routes: IEmailRoute[];
|
|
|
|
|
|
|
|
|
|
// Outbound settings
|
|
|
|
|
outbound?: {
|
|
|
|
@ -124,9 +116,9 @@ export interface ISmtpSession extends IBaseSmtpSession {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Matched domain rule for this session
|
|
|
|
|
* Matched route for this session
|
|
|
|
|
*/
|
|
|
|
|
matchedRule?: IDomainRule;
|
|
|
|
|
matchedRoute?: IEmailRoute;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -162,7 +154,7 @@ export interface IServerStats {
|
|
|
|
|
export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
private dcRouter: DcRouter;
|
|
|
|
|
private options: IUnifiedEmailServerOptions;
|
|
|
|
|
private domainRouter: DomainRouter;
|
|
|
|
|
private emailRouter: EmailRouter;
|
|
|
|
|
private servers: any[] = [];
|
|
|
|
|
private stats: IServerStats;
|
|
|
|
|
|
|
|
|
@ -222,16 +214,8 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
domains: []
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Initialize domain router for pattern matching
|
|
|
|
|
this.domainRouter = new DomainRouter({
|
|
|
|
|
domainRules: options.domainRules,
|
|
|
|
|
defaultMode: options.defaultMode,
|
|
|
|
|
defaultServer: options.defaultServer,
|
|
|
|
|
defaultPort: options.defaultPort,
|
|
|
|
|
defaultTls: options.defaultTls,
|
|
|
|
|
enableCache: true,
|
|
|
|
|
cacheSize: 1000
|
|
|
|
|
});
|
|
|
|
|
// Initialize email router with routes
|
|
|
|
|
this.emailRouter = new EmailRouter(options.routes || []);
|
|
|
|
|
|
|
|
|
|
// Initialize rate limiter
|
|
|
|
|
this.rateLimiter = new UnifiedRateLimiter(options.rateLimits || {
|
|
|
|
@ -396,12 +380,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
applyPolicy: () => true
|
|
|
|
|
},
|
|
|
|
|
processIncomingEmail: async (email: Email) => {
|
|
|
|
|
// This is where we'll process the email based on domain routing
|
|
|
|
|
const to = email.to[0]; // Email.to is an array, take the first recipient
|
|
|
|
|
const rule = this.domainRouter.matchRule(to);
|
|
|
|
|
const mode = rule?.mode || this.options.defaultMode;
|
|
|
|
|
|
|
|
|
|
// Process based on the mode
|
|
|
|
|
// Process email using the new route-based system
|
|
|
|
|
await this.processEmailByMode(email, {
|
|
|
|
|
id: 'session-' + Math.random().toString(36).substring(2),
|
|
|
|
|
state: SmtpState.FINISHED,
|
|
|
|
@ -417,10 +396,8 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
envelope: {
|
|
|
|
|
mailFrom: { address: email.from, args: {} },
|
|
|
|
|
rcptTo: email.to.map(recipient => ({ address: recipient, args: {} }))
|
|
|
|
|
},
|
|
|
|
|
processingMode: mode,
|
|
|
|
|
matchedRule: rule
|
|
|
|
|
}, mode);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@ -515,9 +492,9 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process email based on the determined mode
|
|
|
|
|
* Process email based on routing rules
|
|
|
|
|
*/
|
|
|
|
|
public async processEmailByMode(emailData: Email | Buffer, session: IExtendedSmtpSession, mode: EmailProcessingMode): Promise<Email> {
|
|
|
|
|
public async processEmailByMode(emailData: Email | Buffer, session: IExtendedSmtpSession): Promise<Email> {
|
|
|
|
|
// Convert Buffer to Email if needed
|
|
|
|
|
let email: Email;
|
|
|
|
|
if (Buffer.isBuffer(emailData)) {
|
|
|
|
@ -563,38 +540,202 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
logger.log('info', 'Not a valid bounce notification, continuing with regular processing');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process based on mode
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case 'forward':
|
|
|
|
|
throw new Error('Forward mode is not implemented');
|
|
|
|
|
|
|
|
|
|
case 'mta':
|
|
|
|
|
await this.handleMtaMode(email, session);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'process':
|
|
|
|
|
await this.handleProcessMode(email, session);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unknown processing mode: ${mode}`);
|
|
|
|
|
// Find matching route
|
|
|
|
|
const context: IEmailContext = { email, session };
|
|
|
|
|
const route = await this.emailRouter.evaluateRoutes(context);
|
|
|
|
|
|
|
|
|
|
if (!route) {
|
|
|
|
|
// No matching route - reject
|
|
|
|
|
throw new Error('No matching route for email');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store matched route in session
|
|
|
|
|
session.matchedRoute = route;
|
|
|
|
|
|
|
|
|
|
// Execute action based on route
|
|
|
|
|
await this.executeAction(route.action, email, context);
|
|
|
|
|
|
|
|
|
|
// Return the processed email
|
|
|
|
|
return email;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute action based on route configuration
|
|
|
|
|
*/
|
|
|
|
|
private async executeAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> {
|
|
|
|
|
switch (action.type) {
|
|
|
|
|
case 'forward':
|
|
|
|
|
await this.handleForwardAction(action, email, context);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'process':
|
|
|
|
|
await this.handleProcessAction(action, email, context);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'deliver':
|
|
|
|
|
await this.handleDeliverAction(action, email, context);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'reject':
|
|
|
|
|
await this.handleRejectAction(action, email, context);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unknown action type: ${(action as any).type}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle forward action
|
|
|
|
|
*/
|
|
|
|
|
private async handleForwardAction(_action: IEmailAction, email: Email, context: IEmailContext): Promise<void> {
|
|
|
|
|
if (!_action.forward) {
|
|
|
|
|
throw new Error('Forward action requires forward configuration');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { host, port = 25, auth, addHeaders } = _action.forward;
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Forwarding email to ${host}:${port}`);
|
|
|
|
|
|
|
|
|
|
// Add forwarding headers
|
|
|
|
|
if (addHeaders) {
|
|
|
|
|
for (const [key, value] of Object.entries(addHeaders)) {
|
|
|
|
|
email.headers[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add standard forwarding headers
|
|
|
|
|
email.headers['X-Forwarded-For'] = context.session.remoteAddress || 'unknown';
|
|
|
|
|
email.headers['X-Forwarded-To'] = email.to.join(', ');
|
|
|
|
|
email.headers['X-Forwarded-Date'] = new Date().toISOString();
|
|
|
|
|
|
|
|
|
|
// Get SMTP client
|
|
|
|
|
const client = this.getSmtpClient(host, port);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Send email
|
|
|
|
|
await client.sendMail(email);
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Successfully forwarded email to ${host}:${port}`);
|
|
|
|
|
|
|
|
|
|
SecurityLogger.getInstance().logEvent({
|
|
|
|
|
level: SecurityLogLevel.INFO,
|
|
|
|
|
type: SecurityEventType.EMAIL_FORWARDING,
|
|
|
|
|
message: 'Email forwarded successfully',
|
|
|
|
|
ipAddress: context.session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: context.session.id,
|
|
|
|
|
routeName: context.session.matchedRoute?.name,
|
|
|
|
|
targetHost: host,
|
|
|
|
|
targetPort: port,
|
|
|
|
|
recipients: email.to
|
|
|
|
|
},
|
|
|
|
|
success: true
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.log('error', `Failed to forward email: ${error.message}`);
|
|
|
|
|
|
|
|
|
|
SecurityLogger.getInstance().logEvent({
|
|
|
|
|
level: SecurityLogLevel.ERROR,
|
|
|
|
|
type: SecurityEventType.EMAIL_FORWARDING,
|
|
|
|
|
message: 'Email forwarding failed',
|
|
|
|
|
ipAddress: context.session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: context.session.id,
|
|
|
|
|
routeName: context.session.matchedRoute?.name,
|
|
|
|
|
targetHost: host,
|
|
|
|
|
targetPort: port,
|
|
|
|
|
error: error.message
|
|
|
|
|
},
|
|
|
|
|
success: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle as bounce
|
|
|
|
|
for (const recipient of email.getAllRecipients()) {
|
|
|
|
|
await this.bounceManager.processSmtpFailure(recipient, error.message, {
|
|
|
|
|
sender: email.from,
|
|
|
|
|
originalEmailId: email.headers['Message-ID'] as string
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle process action
|
|
|
|
|
*/
|
|
|
|
|
private async handleProcessAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> {
|
|
|
|
|
logger.log('info', `Processing email with action options`);
|
|
|
|
|
|
|
|
|
|
// Apply scanning if requested
|
|
|
|
|
if (action.process?.scan) {
|
|
|
|
|
// Use existing content scanner
|
|
|
|
|
// Note: ContentScanner integration would go here
|
|
|
|
|
logger.log('info', 'Content scanning requested');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: DKIM signing will be applied at delivery time to ensure signature validity
|
|
|
|
|
|
|
|
|
|
// Queue for delivery
|
|
|
|
|
const queue = action.process?.queue || 'normal';
|
|
|
|
|
await this.deliveryQueue.enqueue(email, 'process', context.session.matchedRoute!);
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Email queued for delivery in ${queue} queue`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle deliver action
|
|
|
|
|
*/
|
|
|
|
|
private async handleDeliverAction(_action: IEmailAction, email: Email, context: IEmailContext): Promise<void> {
|
|
|
|
|
logger.log('info', `Delivering email locally`);
|
|
|
|
|
|
|
|
|
|
// Queue for local delivery
|
|
|
|
|
await this.deliveryQueue.enqueue(email, 'mta', context.session.matchedRoute!);
|
|
|
|
|
|
|
|
|
|
logger.log('info', 'Email queued for local delivery');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle reject action
|
|
|
|
|
*/
|
|
|
|
|
private async handleRejectAction(action: IEmailAction, email: Email, context: IEmailContext): Promise<void> {
|
|
|
|
|
const code = action.reject?.code || 550;
|
|
|
|
|
const message = action.reject?.message || 'Message rejected';
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Rejecting email with code ${code}: ${message}`);
|
|
|
|
|
|
|
|
|
|
SecurityLogger.getInstance().logEvent({
|
|
|
|
|
level: SecurityLogLevel.WARN,
|
|
|
|
|
type: SecurityEventType.EMAIL_PROCESSING,
|
|
|
|
|
message: 'Email rejected by routing rule',
|
|
|
|
|
ipAddress: context.session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: context.session.id,
|
|
|
|
|
routeName: context.session.matchedRoute?.name,
|
|
|
|
|
rejectCode: code,
|
|
|
|
|
rejectMessage: message,
|
|
|
|
|
from: email.from,
|
|
|
|
|
to: email.to
|
|
|
|
|
},
|
|
|
|
|
success: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Throw error with SMTP code and message
|
|
|
|
|
const error = new Error(message);
|
|
|
|
|
(error as any).responseCode = code;
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle email in MTA mode (programmatic processing)
|
|
|
|
|
*/
|
|
|
|
|
private async handleMtaMode(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
|
|
|
|
private async _handleMtaMode(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
|
|
|
|
logger.log('info', `Handling email in MTA mode for session ${session.id}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Apply MTA rule options if provided
|
|
|
|
|
if (session.matchedRule?.mtaOptions) {
|
|
|
|
|
const options = session.matchedRule.mtaOptions;
|
|
|
|
|
if (session.matchedRoute?.action.options?.mtaOptions) {
|
|
|
|
|
const options = session.matchedRoute.action.options.mtaOptions;
|
|
|
|
|
|
|
|
|
|
// Apply DKIM signing if enabled
|
|
|
|
|
if (options.dkimSign && options.dkimOptions) {
|
|
|
|
@ -654,7 +795,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
ipAddress: session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
ruleName: session.matchedRule?.pattern || 'default',
|
|
|
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
|
|
|
subject,
|
|
|
|
|
recipients
|
|
|
|
|
},
|
|
|
|
@ -670,7 +811,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
ipAddress: session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
ruleName: session.matchedRule?.pattern || 'default',
|
|
|
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
|
|
|
error: error.message
|
|
|
|
|
},
|
|
|
|
|
success: false
|
|
|
|
@ -683,18 +824,18 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
/**
|
|
|
|
|
* Handle email in process mode (store-and-forward with scanning)
|
|
|
|
|
*/
|
|
|
|
|
private async handleProcessMode(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
|
|
|
|
private async _handleProcessMode(email: Email, session: IExtendedSmtpSession): Promise<void> {
|
|
|
|
|
logger.log('info', `Handling email in process mode for session ${session.id}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const rule = session.matchedRule;
|
|
|
|
|
const route = session.matchedRoute;
|
|
|
|
|
|
|
|
|
|
// Apply content scanning if enabled
|
|
|
|
|
if (rule?.contentScanning && rule.scanners && rule.scanners.length > 0) {
|
|
|
|
|
if (route?.action.options?.contentScanning && route.action.options.scanners && route.action.options.scanners.length > 0) {
|
|
|
|
|
logger.log('info', 'Performing content scanning');
|
|
|
|
|
|
|
|
|
|
// Apply each scanner
|
|
|
|
|
for (const scanner of rule.scanners) {
|
|
|
|
|
for (const scanner of route.action.options.scanners) {
|
|
|
|
|
switch (scanner.type) {
|
|
|
|
|
case 'spam':
|
|
|
|
|
logger.log('info', 'Scanning for spam content');
|
|
|
|
@ -728,10 +869,10 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply transformations if defined
|
|
|
|
|
if (rule?.transformations && rule.transformations.length > 0) {
|
|
|
|
|
if (route?.action.options?.transformations && route.action.options.transformations.length > 0) {
|
|
|
|
|
logger.log('info', 'Applying email transformations');
|
|
|
|
|
|
|
|
|
|
for (const transform of rule.transformations) {
|
|
|
|
|
for (const transform of route.action.options.transformations) {
|
|
|
|
|
switch (transform.type) {
|
|
|
|
|
case 'addHeader':
|
|
|
|
|
if (transform.header && transform.value) {
|
|
|
|
@ -751,8 +892,8 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
ipAddress: session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
ruleName: rule?.pattern || 'default',
|
|
|
|
|
contentScanning: rule?.contentScanning || false,
|
|
|
|
|
ruleName: route?.name || 'default',
|
|
|
|
|
contentScanning: route?.action.options?.contentScanning || false,
|
|
|
|
|
subject: email.subject
|
|
|
|
|
},
|
|
|
|
|
success: true
|
|
|
|
@ -767,7 +908,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
ipAddress: session.remoteAddress,
|
|
|
|
|
details: {
|
|
|
|
|
sessionId: session.id,
|
|
|
|
|
ruleName: session.matchedRule?.pattern || 'default',
|
|
|
|
|
ruleName: session.matchedRoute?.name || 'default',
|
|
|
|
|
error: error.message
|
|
|
|
|
},
|
|
|
|
|
success: false
|
|
|
|
@ -921,19 +1062,19 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
// Update options without restart
|
|
|
|
|
this.options = { ...this.options, ...options };
|
|
|
|
|
|
|
|
|
|
// Update domain router if rules changed
|
|
|
|
|
if (options.domainRules) {
|
|
|
|
|
this.domainRouter.updateRules(options.domainRules);
|
|
|
|
|
// Update email router if routes changed
|
|
|
|
|
if (options.routes) {
|
|
|
|
|
this.emailRouter.updateRoutes(options.routes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update domain rules
|
|
|
|
|
* Update email routes
|
|
|
|
|
*/
|
|
|
|
|
public updateDomainRules(rules: IDomainRule[]): void {
|
|
|
|
|
this.options.domainRules = rules;
|
|
|
|
|
this.domainRouter.updateRules(rules);
|
|
|
|
|
public updateEmailRoutes(routes: IEmailRoute[]): void {
|
|
|
|
|
this.options.routes = routes;
|
|
|
|
|
this.emailRouter.updateRoutes(routes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@ -943,6 +1084,14 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
return { ...this.stats };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update email routes dynamically
|
|
|
|
|
*/
|
|
|
|
|
public updateRoutes(routes: IEmailRoute[]): void {
|
|
|
|
|
this.emailRouter.setRoutes(routes);
|
|
|
|
|
logger.log('info', `Updated email routes with ${routes.length} routes`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Send an email through the delivery system
|
|
|
|
|
* @param email The email to send
|
|
|
|
@ -954,7 +1103,7 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
public async sendEmail(
|
|
|
|
|
email: Email,
|
|
|
|
|
mode: EmailProcessingMode = 'mta',
|
|
|
|
|
rule?: IDomainRule,
|
|
|
|
|
route?: IEmailRoute,
|
|
|
|
|
options?: {
|
|
|
|
|
skipSuppressionCheck?: boolean;
|
|
|
|
|
ipAddress?: string;
|
|
|
|
@ -1040,16 +1189,16 @@ export class UnifiedEmailServer extends EventEmitter {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the sender domain has DKIM keys and sign the email if needed
|
|
|
|
|
if (mode === 'mta' && rule?.mtaOptions?.dkimSign) {
|
|
|
|
|
if (mode === 'mta' && route?.action.options?.mtaOptions?.dkimSign) {
|
|
|
|
|
const domain = email.from.split('@')[1];
|
|
|
|
|
await this.handleDkimSigning(email, domain, rule.mtaOptions.dkimOptions?.keySelector || 'mta');
|
|
|
|
|
await this.handleDkimSigning(email, domain, route.action.options.mtaOptions.dkimOptions?.keySelector || 'mta');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate a unique ID for this email
|
|
|
|
|
const id = plugins.uuid.v4();
|
|
|
|
|
|
|
|
|
|
// Queue the email for delivery
|
|
|
|
|
await this.deliveryQueue.enqueue(email, mode, rule);
|
|
|
|
|
await this.deliveryQueue.enqueue(email, mode, route);
|
|
|
|
|
|
|
|
|
|
// Record 'sent' event for domain reputation monitoring
|
|
|
|
|
const senderDomain = email.from.split('@')[1];
|
|
|
|
|