update
This commit is contained in:
230
test/test.dcrouter.email.ts
Normal file
230
test/test.dcrouter.email.ts
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as plugins from '../ts/plugins.js';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import {
|
||||||
|
DcRouter,
|
||||||
|
type IDcRouterOptions,
|
||||||
|
type IEmailConfig,
|
||||||
|
type EmailProcessingMode,
|
||||||
|
type IDomainRule
|
||||||
|
} from '../ts/classes.dcrouter.js';
|
||||||
|
|
||||||
|
// Mock platform service for testing
|
||||||
|
const mockPlatformService = {
|
||||||
|
mtaService: {
|
||||||
|
saveToLocalMailbox: async (email: any) => {
|
||||||
|
// Mock implementation
|
||||||
|
console.log('Mock: Saving email to local mailbox');
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
isBounceNotification: (email: any) => false,
|
||||||
|
processBounceNotification: async (email: any) => {
|
||||||
|
// Mock implementation
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('DcRouter class - Custom email port configuration', async () => {
|
||||||
|
// Define custom port mapping
|
||||||
|
const customPortMapping = {
|
||||||
|
25: 11025, // Custom SMTP port mapping
|
||||||
|
587: 11587, // Custom submission port mapping
|
||||||
|
465: 11465, // Custom SMTPS port mapping
|
||||||
|
2525: 12525 // Additional custom port
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a custom email configuration
|
||||||
|
const emailConfig: IEmailConfig = {
|
||||||
|
ports: [25, 587, 465, 2525], // Added a non-standard port
|
||||||
|
hostname: 'mail.example.com',
|
||||||
|
maxMessageSize: 50 * 1024 * 1024, // 50MB
|
||||||
|
|
||||||
|
defaultMode: 'forward' as EmailProcessingMode,
|
||||||
|
defaultServer: 'fallback-mail.example.com',
|
||||||
|
defaultPort: 25,
|
||||||
|
defaultTls: true,
|
||||||
|
|
||||||
|
domainRules: [
|
||||||
|
{
|
||||||
|
pattern: '*@example.com',
|
||||||
|
mode: 'forward' as EmailProcessingMode,
|
||||||
|
target: {
|
||||||
|
server: 'mail1.example.com',
|
||||||
|
port: 25,
|
||||||
|
useTls: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: '*@example.org',
|
||||||
|
mode: 'mta' as EmailProcessingMode,
|
||||||
|
mtaOptions: {
|
||||||
|
domain: 'example.org',
|
||||||
|
allowLocalDelivery: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create custom email storage path
|
||||||
|
const customEmailsPath = path.join(process.cwd(), 'email');
|
||||||
|
|
||||||
|
// Ensure directory exists and is empty
|
||||||
|
if (fs.existsSync(customEmailsPath)) {
|
||||||
|
try {
|
||||||
|
fs.rmdirSync(customEmailsPath, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not remove test directory:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.mkdirSync(customEmailsPath, { recursive: true });
|
||||||
|
|
||||||
|
// Create DcRouter options with custom email port configuration
|
||||||
|
const options: IDcRouterOptions = {
|
||||||
|
emailConfig,
|
||||||
|
emailPortConfig: {
|
||||||
|
portMapping: customPortMapping,
|
||||||
|
portSettings: {
|
||||||
|
2525: {
|
||||||
|
terminateTls: false,
|
||||||
|
routeName: 'custom-smtp-route'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receivedEmailsPath: customEmailsPath
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
contactEmail: 'test@example.com'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create DcRouter instance with mock platform service
|
||||||
|
const router = new DcRouter(options, mockPlatformService);
|
||||||
|
|
||||||
|
// Verify the options are correctly set
|
||||||
|
expect(router.options.emailPortConfig).toBeTruthy();
|
||||||
|
expect(router.options.emailPortConfig.portMapping).toEqual(customPortMapping);
|
||||||
|
expect(router.options.emailPortConfig.receivedEmailsPath).toEqual(customEmailsPath);
|
||||||
|
|
||||||
|
// Test the generateEmailRoutes method
|
||||||
|
if (typeof router['generateEmailRoutes'] === 'function') {
|
||||||
|
const routes = router['generateEmailRoutes'](emailConfig);
|
||||||
|
|
||||||
|
// Verify that all ports are configured
|
||||||
|
expect(routes.length).toBeGreaterThan(0); // At least some routes are configured
|
||||||
|
|
||||||
|
// Check the custom port configuration
|
||||||
|
const customPortRoute = routes.find(r => r.match.ports?.includes(2525));
|
||||||
|
expect(customPortRoute).toBeTruthy();
|
||||||
|
expect(customPortRoute?.name).toEqual('custom-smtp-route');
|
||||||
|
expect(customPortRoute?.action.target.port).toEqual(12525);
|
||||||
|
|
||||||
|
// Check standard port mappings
|
||||||
|
const smtpRoute = routes.find(r => r.match.ports?.includes(25));
|
||||||
|
expect(smtpRoute?.action.target.port).toEqual(11025);
|
||||||
|
|
||||||
|
const submissionRoute = routes.find(r => r.match.ports?.includes(587));
|
||||||
|
expect(submissionRoute?.action.target.port).toEqual(11587);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
try {
|
||||||
|
fs.rmdirSync(customEmailsPath, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not remove test directory in cleanup:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('DcRouter class - Custom email storage path', async () => {
|
||||||
|
// Create custom email storage path
|
||||||
|
const customEmailsPath = path.join(process.cwd(), 'email');
|
||||||
|
|
||||||
|
// Ensure directory exists and is empty
|
||||||
|
if (fs.existsSync(customEmailsPath)) {
|
||||||
|
try {
|
||||||
|
fs.rmdirSync(customEmailsPath, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not remove test directory:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.mkdirSync(customEmailsPath, { recursive: true });
|
||||||
|
|
||||||
|
// Create a basic email configuration
|
||||||
|
const emailConfig: IEmailConfig = {
|
||||||
|
ports: [25],
|
||||||
|
hostname: 'mail.example.com',
|
||||||
|
defaultMode: 'mta' as EmailProcessingMode,
|
||||||
|
domainRules: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create DcRouter options with custom email storage path
|
||||||
|
const options: IDcRouterOptions = {
|
||||||
|
emailConfig,
|
||||||
|
emailPortConfig: {
|
||||||
|
receivedEmailsPath: customEmailsPath
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
contactEmail: 'test@example.com'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a mock MTA service with a trackable saveToLocalMailbox method
|
||||||
|
let savedToCustomPath = false;
|
||||||
|
const mockEmail = {
|
||||||
|
to: ['test@example.com'],
|
||||||
|
toRFC822String: () => 'From: sender@example.com\nTo: test@example.com\nSubject: Test\n\nTest email'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockMtaService = {
|
||||||
|
saveToLocalMailbox: async (email: any) => {
|
||||||
|
console.log('Original saveToLocalMailbox called');
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
isBounceNotification: (email: any) => false,
|
||||||
|
processBounceNotification: async (email: any) => {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPlatformServiceWithMta = {
|
||||||
|
mtaService: mockMtaService
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create DcRouter instance with mock platform service
|
||||||
|
const router = new DcRouter(options, mockPlatformServiceWithMta);
|
||||||
|
|
||||||
|
// Import the patch function and apply it directly for testing
|
||||||
|
const { configureEmailStorage } = await import('../ts/mail/delivery/classes.mta.patch.js');
|
||||||
|
configureEmailStorage(mockMtaService, { receivedEmailsPath: customEmailsPath });
|
||||||
|
|
||||||
|
// Call the patched method
|
||||||
|
await mockMtaService.saveToLocalMailbox(mockEmail);
|
||||||
|
|
||||||
|
// Check if a file was created in the custom path
|
||||||
|
const files = fs.readdirSync(customEmailsPath);
|
||||||
|
expect(files.length).toBeGreaterThan(0);
|
||||||
|
expect(files[0].endsWith('.eml')).toEqual(true);
|
||||||
|
|
||||||
|
// Verify file contents
|
||||||
|
const fileContent = fs.readFileSync(path.join(customEmailsPath, files[0]), 'utf8');
|
||||||
|
expect(fileContent).toContain('From: sender@example.com');
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
try {
|
||||||
|
fs.rmdirSync(customEmailsPath, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not remove test directory in cleanup:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Final clean-up test
|
||||||
|
tap.test('clean up after tests', async () => {
|
||||||
|
// No-op - just to make sure everything is cleaned up properly
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('stop', async () => {
|
||||||
|
await tap.stopForcefully();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export a function to run all tests
|
||||||
|
export default tap.start();
|
@@ -11,6 +11,7 @@ import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classe
|
|||||||
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './mail/delivery/classes.delivery.system.js';
|
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './mail/delivery/classes.delivery.system.js';
|
||||||
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from './mail/delivery/classes.unified.rate.limiter.js';
|
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from './mail/delivery/classes.unified.rate.limiter.js';
|
||||||
import { logger } from './logger.js';
|
import { logger } from './logger.js';
|
||||||
|
import { applyCustomEmailStorage } from './mail/delivery/classes.mta.patch.js';
|
||||||
|
|
||||||
export interface IDcRouterOptions {
|
export interface IDcRouterOptions {
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +26,20 @@ export interface IDcRouterOptions {
|
|||||||
*/
|
*/
|
||||||
emailConfig?: IEmailConfig;
|
emailConfig?: IEmailConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom email port configuration
|
||||||
|
* Allows configuring specific ports for email handling
|
||||||
|
* This overrides the default port mapping in the emailConfig
|
||||||
|
*/
|
||||||
|
emailPortConfig?: {
|
||||||
|
/** External to internal port mapping */
|
||||||
|
portMapping?: Record<number, number>;
|
||||||
|
/** Custom port configuration for specific ports */
|
||||||
|
portSettings?: Record<number, any>;
|
||||||
|
/** Path to store received emails */
|
||||||
|
receivedEmailsPath?: string;
|
||||||
|
};
|
||||||
|
|
||||||
/** TLS/certificate configuration */
|
/** TLS/certificate configuration */
|
||||||
tls?: {
|
tls?: {
|
||||||
/** Contact email for ACME certificates */
|
/** Contact email for ACME certificates */
|
||||||
@@ -76,14 +91,20 @@ export class DcRouter {
|
|||||||
public deliverySystem?: MultiModeDeliverySystem;
|
public deliverySystem?: MultiModeDeliverySystem;
|
||||||
public rateLimiter?: UnifiedRateLimiter;
|
public rateLimiter?: UnifiedRateLimiter;
|
||||||
|
|
||||||
|
// Reference to the platform service for accessing MTA and other services
|
||||||
|
public platformServiceRef?: any;
|
||||||
|
|
||||||
// Environment access
|
// Environment access
|
||||||
private qenv = new plugins.qenv.Qenv('./', '.nogit/');
|
private qenv = new plugins.qenv.Qenv('./', '.nogit/');
|
||||||
|
|
||||||
constructor(optionsArg: IDcRouterOptions) {
|
constructor(optionsArg: IDcRouterOptions, platformServiceRef?: any) {
|
||||||
// Set defaults in options
|
// Set defaults in options
|
||||||
this.options = {
|
this.options = {
|
||||||
...optionsArg
|
...optionsArg
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Store reference to platform service if provided
|
||||||
|
this.platformServiceRef = platformServiceRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
@@ -96,6 +117,12 @@ export class DcRouter {
|
|||||||
// Set up unified email handling if configured
|
// Set up unified email handling if configured
|
||||||
if (this.options.emailConfig) {
|
if (this.options.emailConfig) {
|
||||||
await this.setupUnifiedEmailHandling();
|
await this.setupUnifiedEmailHandling();
|
||||||
|
|
||||||
|
// Apply custom email storage configuration if available
|
||||||
|
if (this.platformServiceRef && this.options.emailPortConfig?.receivedEmailsPath) {
|
||||||
|
logger.log('info', 'Applying custom email storage configuration');
|
||||||
|
applyCustomEmailStorage(this.platformServiceRef, this.options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up DNS server if configured
|
// Set up DNS server if configured
|
||||||
@@ -222,69 +249,90 @@ export class DcRouter {
|
|||||||
private generateEmailRoutes(emailConfig: IEmailConfig): plugins.smartproxy.IRouteConfig[] {
|
private generateEmailRoutes(emailConfig: IEmailConfig): plugins.smartproxy.IRouteConfig[] {
|
||||||
const emailRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
const emailRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
||||||
|
|
||||||
|
// Get the custom port mapping if available, otherwise use defaults
|
||||||
|
const defaultPortMapping = {
|
||||||
|
25: 10025, // SMTP
|
||||||
|
587: 10587, // Submission
|
||||||
|
465: 10465 // SMTPS
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use custom port mapping if provided, otherwise fall back to defaults
|
||||||
|
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
|
||||||
|
|
||||||
// Create routes for each email port
|
// Create routes for each email port
|
||||||
for (const port of emailConfig.ports) {
|
for (const port of emailConfig.ports) {
|
||||||
|
// Calculate the internal port using the mapping
|
||||||
|
const internalPort = portMapping[port] || port + 10000;
|
||||||
|
|
||||||
|
// Create a descriptive name for the route based on the port
|
||||||
|
let routeName = 'email-route';
|
||||||
|
let tlsMode = 'passthrough';
|
||||||
|
|
||||||
// Handle different email ports differently
|
// Handle different email ports differently
|
||||||
switch (port) {
|
switch (port) {
|
||||||
case 25: // SMTP
|
case 25: // SMTP
|
||||||
emailRoutes.push({
|
routeName = 'smtp-route';
|
||||||
name: 'smtp-route',
|
tlsMode = 'passthrough'; // STARTTLS handled by email server
|
||||||
match: {
|
|
||||||
ports: [25]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: {
|
|
||||||
host: 'localhost', // Forward to internal email server
|
|
||||||
port: 10025 // Internal email server port
|
|
||||||
},
|
|
||||||
// No TLS termination for port 25 (STARTTLS handled by email server)
|
|
||||||
tls: {
|
|
||||||
mode: 'passthrough'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 587: // Submission
|
case 587: // Submission
|
||||||
emailRoutes.push({
|
routeName = 'submission-route';
|
||||||
name: 'submission-route',
|
tlsMode = 'passthrough'; // STARTTLS handled by email server
|
||||||
match: {
|
|
||||||
ports: [587]
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
type: 'forward',
|
|
||||||
target: {
|
|
||||||
host: 'localhost',
|
|
||||||
port: 10587
|
|
||||||
},
|
|
||||||
tls: {
|
|
||||||
mode: 'passthrough' // STARTTLS handled by email server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 465: // SMTPS
|
case 465: // SMTPS
|
||||||
emailRoutes.push({
|
routeName = 'smtps-route';
|
||||||
name: 'smtps-route',
|
tlsMode = 'terminate'; // Terminate TLS and re-encrypt to email server
|
||||||
match: {
|
break;
|
||||||
ports: [465]
|
|
||||||
},
|
default:
|
||||||
action: {
|
routeName = `email-port-${port}-route`;
|
||||||
type: 'forward',
|
// For unknown ports, assume passthrough by default
|
||||||
target: {
|
tlsMode = 'passthrough';
|
||||||
host: 'localhost',
|
|
||||||
port: 10465
|
// Check if we have specific settings for this port
|
||||||
},
|
if (this.options.emailPortConfig?.portSettings &&
|
||||||
tls: {
|
this.options.emailPortConfig.portSettings[port]) {
|
||||||
mode: 'terminate', // Terminate TLS and re-encrypt to email server
|
const portSettings = this.options.emailPortConfig.portSettings[port];
|
||||||
certificate: 'auto'
|
|
||||||
}
|
// If this port requires TLS termination, set the mode accordingly
|
||||||
|
if (portSettings.terminateTls) {
|
||||||
|
tlsMode = 'terminate';
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Override the route name if specified
|
||||||
|
if (portSettings.routeName) {
|
||||||
|
routeName = portSettings.routeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the route configuration
|
||||||
|
const routeConfig: plugins.smartproxy.IRouteConfig = {
|
||||||
|
name: routeName,
|
||||||
|
match: {
|
||||||
|
ports: [port]
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: 'localhost', // Forward to internal email server
|
||||||
|
port: internalPort
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
mode: tlsMode as any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For TLS terminate mode, add certificate info
|
||||||
|
if (tlsMode === 'terminate') {
|
||||||
|
routeConfig.action.tls.certificate = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the route to our list
|
||||||
|
emailRoutes.push(routeConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add domain-specific email routes if configured
|
// Add domain-specific email routes if configured
|
||||||
@@ -406,13 +454,16 @@ export class DcRouter {
|
|||||||
|
|
||||||
const emailConfig = this.options.emailConfig;
|
const emailConfig = this.options.emailConfig;
|
||||||
|
|
||||||
// Map external ports to internal ports
|
// Map external ports to internal ports with support for custom port mapping
|
||||||
const portMapping = {
|
const defaultPortMapping = {
|
||||||
25: 10025, // SMTP
|
25: 10025, // SMTP
|
||||||
587: 10587, // Submission
|
587: 10587, // Submission
|
||||||
465: 10465 // SMTPS
|
465: 10465 // SMTPS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Use custom port mapping if provided, otherwise fall back to defaults
|
||||||
|
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
|
||||||
|
|
||||||
// Create internal email server configuration
|
// Create internal email server configuration
|
||||||
const internalEmailConfig: IEmailConfig = {
|
const internalEmailConfig: IEmailConfig = {
|
||||||
...emailConfig,
|
...emailConfig,
|
||||||
@@ -420,6 +471,17 @@ export class DcRouter {
|
|||||||
hostname: 'localhost' // Listen on localhost for SmartProxy forwarding
|
hostname: 'localhost' // Listen on localhost for SmartProxy forwarding
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If custom MTA options are provided, merge them
|
||||||
|
if (this.options.emailPortConfig?.portSettings) {
|
||||||
|
// Will be used in MTA configuration
|
||||||
|
logger.log('info', 'Custom port settings detected for email configuration');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure custom email storage path if specified
|
||||||
|
if (this.options.emailPortConfig?.receivedEmailsPath) {
|
||||||
|
logger.log('info', `Custom email storage path configured: ${this.options.emailPortConfig.receivedEmailsPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create domain router for pattern matching
|
// Create domain router for pattern matching
|
||||||
this.domainRouter = new DomainRouter({
|
this.domainRouter = new DomainRouter({
|
||||||
|
@@ -80,7 +80,7 @@ export class SmtpPortConfig {
|
|||||||
|
|
||||||
/** Default port configurations */
|
/** Default port configurations */
|
||||||
private static readonly DEFAULT_CONFIGS: Record<number, Partial<ISmtpPortSettings>> = {
|
private static readonly DEFAULT_CONFIGS: Record<number, Partial<ISmtpPortSettings>> = {
|
||||||
// Port 25: Standard SMTP
|
// Port 25: Standard SMTP (modified from port 25)
|
||||||
25: {
|
25: {
|
||||||
description: 'Standard SMTP',
|
description: 'Standard SMTP',
|
||||||
requireAuth: false,
|
requireAuth: false,
|
||||||
@@ -258,7 +258,7 @@ export class SmtpPortConfig {
|
|||||||
type: 'forward',
|
type: 'forward',
|
||||||
target: {
|
target: {
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: portConfig.port + 10000 // Map to internal port (e.g., 25 -> 10025)
|
port: portConfig.port === 29200 ? 39200 : portConfig.port + 10000 // Map to internal port (29200 -> 39200, 587 -> 10587, etc.)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
72
ts/mail/delivery/classes.mta.patch.ts
Normal file
72
ts/mail/delivery/classes.mta.patch.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import * as plugins from '../../plugins.js';
|
||||||
|
import * as paths from '../../paths.js';
|
||||||
|
import type { SzPlatformService } from '../../platformservice.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension to the MTA service to handle custom email storage
|
||||||
|
*/
|
||||||
|
export function configureEmailStorage(mtaService: any, options: any = {}) {
|
||||||
|
const originalSaveToLocalMailbox = mtaService.saveToLocalMailbox;
|
||||||
|
|
||||||
|
// Override the saveToLocalMailbox method to use custom path
|
||||||
|
mtaService.saveToLocalMailbox = async function(email: any): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Use the custom received emails path if configured
|
||||||
|
const customPath = options?.receivedEmailsPath;
|
||||||
|
|
||||||
|
if (customPath) {
|
||||||
|
// Ensure the directory exists
|
||||||
|
plugins.smartfile.fs.ensureDirSync(customPath);
|
||||||
|
|
||||||
|
// Check if this is a bounce notification
|
||||||
|
const isBounceNotification = this.isBounceNotification(email);
|
||||||
|
|
||||||
|
if (isBounceNotification) {
|
||||||
|
await this.processBounceNotification(email);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the email in the custom path
|
||||||
|
const emailContent = email.toRFC822String();
|
||||||
|
const filename = `${Date.now()}_${email.to[0].replace(/[^a-zA-Z0-9]/g, '_')}.eml`;
|
||||||
|
|
||||||
|
plugins.smartfile.memory.toFsSync(
|
||||||
|
emailContent,
|
||||||
|
plugins.path.join(customPath, filename)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Email saved to custom mailbox location: ${customPath}/${filename}`);
|
||||||
|
} else {
|
||||||
|
// Use the original implementation
|
||||||
|
await originalSaveToLocalMailbox.call(this, email);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving email to local mailbox:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return mtaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the email storage configuration to the platform service
|
||||||
|
*/
|
||||||
|
export function applyCustomEmailStorage(platformService: SzPlatformService, options: any): void {
|
||||||
|
// Extract the receivedEmailsPath if available
|
||||||
|
if (options?.emailPortConfig?.receivedEmailsPath) {
|
||||||
|
const receivedEmailsPath = options.emailPortConfig.receivedEmailsPath;
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
plugins.smartfile.fs.ensureDirSync(receivedEmailsPath);
|
||||||
|
|
||||||
|
// Apply configuration to MTA service if available
|
||||||
|
if (platformService.mtaService) {
|
||||||
|
configureEmailStorage(platformService.mtaService, {
|
||||||
|
receivedEmailsPath
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Configured MTA to store received emails to: ${receivedEmailsPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user