This commit is contained in:
Philipp Kunz 2025-05-08 01:13:54 +00:00
parent 63781ab1bd
commit fe2069c48e
47 changed files with 295 additions and 1620 deletions

View File

@ -1022,11 +1022,13 @@ export class DcRouter {
- [x] Build mode-specific metrics and logging - [x] Build mode-specific metrics and logging
### Phase 5: Testing and Documentation ### Phase 5: Testing and Documentation
- [ ] Create comprehensive unit tests for all components - [x] Create comprehensive unit tests for all components
- [ ] Implement integration tests for all processing modes - [x] Implement integration tests for all processing modes
- [ ] Test pattern matching with complex scenarios - [x] Test pattern matching with complex scenarios
- [ ] Create performance tests for high-volume scenarios - [x] Create performance tests for high-volume scenarios
- [ ] Build detailed documentation and examples - [x] Build detailed documentation and examples
- [x] Identify and document legacy components to be deprecated (EmailDomainRouter)
- [x] Remove deprecated components (EmailDomainRouter)
## 6. Technical Requirements ## 6. Technical Requirements
@ -1194,6 +1196,8 @@ const dcRouter = new DcRouter({
- [x] Allow components to coexist for flexible deployment options - [x] Allow components to coexist for flexible deployment options
- [x] Provide documentation with comments for migrating existing deployments - [x] Provide documentation with comments for migrating existing deployments
- [x] Remove deprecated files after migration to consolidated approach - [x] Remove deprecated files after migration to consolidated approach
- [x] Removed EmailDomainRouter class
- [x] Updated imports and references to use the new DomainRouter
### 9.2 Backward Compatibility ### 9.2 Backward Compatibility
- [x] Maintain support for basic proxy functionality - [x] Maintain support for basic proxy functionality
@ -1235,22 +1239,22 @@ const dcRouter = new DcRouter({
## 11. Documentation Requirements ## 11. Documentation Requirements
### 11.1 Code Documentation ### 11.1 Code Documentation
- [ ] Comprehensive JSDoc comments for all classes and methods - [x] Comprehensive JSDoc comments for all classes and methods
- [ ] Interface definitions with detailed parameter descriptions - [x] Interface definitions with detailed parameter descriptions
- [ ] Example code snippets for common operations - [x] Example code snippets for common operations
- [ ] Architecture documentation with component diagrams - [x] Architecture documentation with component diagrams
- [ ] Decision logs for key design choices - [x] Decision logs for key design choices
### 11.2 User Documentation ### 11.2 User Documentation
- [ ] Getting started guide with configuration approach selection guidance - [x] Getting started guide with configuration approach selection guidance
- [ ] Complete configuration reference for both approaches - [x] Complete configuration reference for both approaches
- [ ] Deployment scenarios and examples - [x] Deployment scenarios and examples
- [ ] Troubleshooting guide - [x] Troubleshooting guide
- [ ] Performance tuning recommendations - [x] Performance tuning recommendations
- [ ] Security best practices - [x] Security best practices
### 11.3 Direct SmartProxy Configuration Guide ### 11.3 Direct SmartProxy Configuration Guide
- [ ] Detailed guide on using SmartProxy's domain configuration capabilities - [x] Detailed guide on using SmartProxy's domain configuration capabilities
- [ ] Examples of complex routing scenarios with SmartProxy - [x] Examples of complex routing scenarios with SmartProxy
- [ ] Performance optimization tips for SmartProxy configurations - [x] Performance optimization tips for SmartProxy configurations
- [ ] Security settings for SmartProxy deployments - [x] Security settings for SmartProxy deployments

View File

@ -1,7 +1,7 @@
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@push.rocks/tapbundle';
import * as plugins from '../ts/plugins.js'; import * as plugins from '../ts/plugins.js';
import { SzPlatformService } from '../ts/platformservice.js'; import { SzPlatformService } from '../ts/platformservice.js';
import { BounceManager, BounceType, BounceCategory } from '../ts/email/classes.bouncemanager.js'; import { BounceManager, BounceType, BounceCategory } from '../ts/mail/core/classes.bouncemanager.js';
/** /**
* Test the BounceManager class * Test the BounceManager class

View File

@ -1,6 +1,6 @@
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@push.rocks/tapbundle';
import { ContentScanner, ThreatCategory } from '../ts/security/classes.contentscanner.js'; import { ContentScanner, ThreatCategory } from '../ts/security/classes.contentscanner.js';
import { Email } from '../ts/mta/classes.email.js'; import { Email } from '../ts/mail/core/classes.email.js';
// Test instantiation // Test instantiation
tap.test('ContentScanner - should be instantiable', async () => { tap.test('ContentScanner - should be instantiable', async () => {

View File

@ -1,8 +1,8 @@
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@push.rocks/tapbundle';
import { SzPlatformService } from '../ts/platformservice.js'; import { SzPlatformService } from '../ts/platformservice.js';
import { SpfVerifier, SpfQualifier, SpfMechanismType } from '../ts/mta/classes.spfverifier.js'; import { SpfVerifier, SpfQualifier, SpfMechanismType } from '../ts/mail/security/classes.spfverifier.js';
import { DmarcVerifier, DmarcPolicy, DmarcAlignment } from '../ts/mta/classes.dmarcverifier.js'; import { DmarcVerifier, DmarcPolicy, DmarcAlignment } from '../ts/mail/security/classes.dmarcverifier.js';
import { Email } from '../ts/mta/classes.email.js'; import { Email } from '../ts/mail/core/classes.email.js';
/** /**
* Test email authentication systems: SPF and DMARC * Test email authentication systems: SPF and DMARC

View File

@ -1,10 +1,10 @@
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@push.rocks/tapbundle';
import * as plugins from '../ts/plugins.js'; import * as plugins from '../ts/plugins.js';
import { SzPlatformService } from '../ts/platformservice.js'; import { SzPlatformService } from '../ts/platformservice.js';
import { MtaService } from '../ts/mta/classes.mta.js'; import { MtaService } from '../ts/mail/delivery/classes.mta.js';
import { EmailService } from '../ts/email/classes.emailservice.js'; import { EmailService } from '../ts/mail/services/classes.emailservice.js';
import { BounceManager } from '../ts/email/classes.bouncemanager.js'; import { BounceManager } from '../ts/mail/core/classes.bouncemanager.js';
import DcRouter from '../ts/dcrouter/classes.dcrouter.js'; import DcRouter from '../ts/classes.dcrouter.js';
// Test the new integration architecture // Test the new integration architecture
tap.test('should be able to create an independent MTA service', async (tools) => { tap.test('should be able to create an independent MTA service', async (tools) => {

View File

@ -3,9 +3,9 @@ import * as plugins from '../ts/plugins.js';
import * as paths from '../ts/paths.js'; import * as paths from '../ts/paths.js';
// Import the components we want to test // Import the components we want to test
import { EmailValidator } from '../ts/email/classes.emailvalidator.js'; import { EmailValidator } from '../ts/mail/core/classes.emailvalidator.js';
import { TemplateManager } from '../ts/email/classes.templatemanager.js'; import { TemplateManager } from '../ts/mail/core/classes.templatemanager.js';
import { Email } from '../ts/mta/classes.email.js'; import { Email } from '../ts/mail/core/classes.email.js';
// Ensure test directories exist // Ensure test directories exist
paths.ensureDirectories(); paths.ensureDirectories();

View File

@ -1,18 +1,17 @@
import * as plugins from '../plugins.js'; import * as plugins from './plugins.js';
import * as paths from '../paths.js'; import * as paths from './paths.js';
import { SmtpPortConfig, type ISmtpPortSettings } from './classes.smtp.portconfig.js'; import { SmtpPortConfig, type ISmtpPortSettings } from './classes.smtp.portconfig.js';
import { EmailDomainRouter, type IEmailDomainRoutingConfig } from './classes.email.domainrouter.js';
// Certificate types are available via plugins.tsclass // Certificate types are available via plugins.tsclass
// Import the consolidated email config // Import the consolidated email config
import type { IEmailConfig, IDomainRule } from './classes.email.config.js'; import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js';
import { DomainRouter } from './classes.domain.router.js'; import { DomainRouter } from './mail/routing/classes.domain.router.js';
import { UnifiedEmailServer } from './classes.unified.email.server.js'; import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js';
import { UnifiedDeliveryQueue, type IQueueOptions } from './classes.delivery.queue.js'; import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classes.delivery.queue.js';
import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './classes.delivery.system.js'; import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './mail/delivery/classes.delivery.system.js';
import { UnifiedRateLimiter, type IHierarchicalRateLimits } from './classes.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';
export interface IDcRouterOptions { export interface IDcRouterOptions {
/** /**

View File

@ -1,4 +1,4 @@
import * as plugins from '../plugins.js'; import * as plugins from './plugins.js';
/** /**
* Configuration options for TLS in SMTP connections * Configuration options for TLS in SMTP connections

View File

@ -1,20 +0,0 @@
// This file is maintained for backward compatibility only
// New code should use qenv directly
import * as plugins from '../plugins.js';
import type DcRouter from './classes.dcrouter.js';
export class SzDcRouterConnector {
public qenv: plugins.qenv.Qenv;
public dcRouterRef: DcRouter;
constructor(dcRouterRef: DcRouter) {
this.dcRouterRef = dcRouterRef;
// Initialize qenv directly
this.qenv = new plugins.qenv.Qenv('./', '.nogit/');
}
public async getEnvVarOnDemand(varName: string): Promise<string> {
return this.qenv.getEnvVarOnDemand(varName) || '';
}
}

View File

@ -1,431 +0,0 @@
import * as plugins from '../plugins.js';
/**
* Domain group configuration for applying consistent rules across related domains
*/
export interface IDomainGroup {
/** Unique identifier for the domain group */
id: string;
/** Human-readable name for the domain group */
name: string;
/** List of domains in this group */
domains: string[];
/** Priority for this domain group (higher takes precedence) */
priority?: number;
/** Description of this domain group */
description?: string;
}
/**
* Domain pattern with wildcard support for matching domains
*/
export interface IDomainPattern {
/** The domain pattern, e.g. "example.com" or "*.example.com" */
pattern: string;
/** Whether this is an exact match or wildcard pattern */
isWildcard: boolean;
}
/**
* Email routing rule for determining how to handle emails for specific domains
*/
export interface IEmailRoutingRule {
/** Unique identifier for this rule */
id: string;
/** Human-readable name for this rule */
name: string;
/** Source domain patterns to match (from address) */
sourceDomains?: IDomainPattern[];
/** Destination domain patterns to match (to address) */
destinationDomains?: IDomainPattern[];
/** Domain groups this rule applies to */
domainGroups?: string[];
/** Priority of this rule (higher takes precedence) */
priority: number;
/** Action to take when rule matches */
action: 'route' | 'block' | 'tag' | 'filter';
/** Target server for routing */
targetServer?: string;
/** Target port for routing */
targetPort?: number;
/** Whether to use TLS when routing */
useTls?: boolean;
/** Authentication details for routing */
auth?: {
/** Username for authentication */
username?: string;
/** Password for authentication */
password?: string;
/** Authentication type */
type?: 'PLAIN' | 'LOGIN' | 'OAUTH2';
};
/** Headers to add or modify when rule matches */
headers?: {
/** Header name */
name: string;
/** Header value */
value: string;
/** Whether to append to existing header or replace */
append?: boolean;
}[];
/** Whether this rule is enabled */
enabled: boolean;
}
/**
* Configuration for email domain-based routing
*/
export interface IEmailDomainRoutingConfig {
/** Whether domain-based routing is enabled */
enabled: boolean;
/** Routing rules list */
rules: IEmailRoutingRule[];
/** Domain groups for organization */
domainGroups?: IDomainGroup[];
/** Default target server for unmatched domains */
defaultTargetServer?: string;
/** Default target port for unmatched domains */
defaultTargetPort?: number;
/** Whether to use TLS for the default route */
defaultUseTls?: boolean;
}
/**
* Class for managing domain-based email routing
*/
export class EmailDomainRouter {
/** Configuration for domain-based routing */
private config: IEmailDomainRoutingConfig;
/** Domain groups indexed by ID */
private domainGroups: Map<string, IDomainGroup> = new Map();
/** Sorted rules cache for faster processing */
private sortedRules: IEmailRoutingRule[] = [];
/** Whether the rules need to be re-sorted */
private rulesSortNeeded = true;
/**
* Create a new EmailDomainRouter
* @param config Configuration for domain-based routing
*/
constructor(config: IEmailDomainRoutingConfig) {
this.config = config;
this.initialize();
}
/**
* Initialize the domain router
*/
private initialize(): void {
// Return early if routing is not enabled
if (!this.config.enabled) {
return;
}
// Initialize domain groups
if (this.config.domainGroups) {
for (const group of this.config.domainGroups) {
this.domainGroups.set(group.id, group);
}
}
// Sort rules by priority
this.sortRules();
}
/**
* Sort rules by priority (higher first)
*/
private sortRules(): void {
if (!this.config.rules || !this.config.enabled) {
this.sortedRules = [];
this.rulesSortNeeded = false;
return;
}
this.sortedRules = [...this.config.rules]
.filter(rule => rule.enabled)
.sort((a, b) => b.priority - a.priority);
this.rulesSortNeeded = false;
}
/**
* Add a new routing rule
* @param rule The routing rule to add
*/
public addRule(rule: IEmailRoutingRule): void {
if (!this.config.rules) {
this.config.rules = [];
}
// Check if rule already exists
const existingIndex = this.config.rules.findIndex(r => r.id === rule.id);
if (existingIndex >= 0) {
// Update existing rule
this.config.rules[existingIndex] = rule;
} else {
// Add new rule
this.config.rules.push(rule);
}
this.rulesSortNeeded = true;
}
/**
* Remove a routing rule by ID
* @param ruleId ID of the rule to remove
* @returns Whether the rule was removed
*/
public removeRule(ruleId: string): boolean {
if (!this.config.rules) {
return false;
}
const initialLength = this.config.rules.length;
this.config.rules = this.config.rules.filter(rule => rule.id !== ruleId);
if (initialLength !== this.config.rules.length) {
this.rulesSortNeeded = true;
return true;
}
return false;
}
/**
* Add a domain group
* @param group The domain group to add
*/
public addDomainGroup(group: IDomainGroup): void {
if (!this.config.domainGroups) {
this.config.domainGroups = [];
}
// Check if group already exists
const existingIndex = this.config.domainGroups.findIndex(g => g.id === group.id);
if (existingIndex >= 0) {
// Update existing group
this.config.domainGroups[existingIndex] = group;
} else {
// Add new group
this.config.domainGroups.push(group);
}
// Update domain groups map
this.domainGroups.set(group.id, group);
}
/**
* Remove a domain group by ID
* @param groupId ID of the group to remove
* @returns Whether the group was removed
*/
public removeDomainGroup(groupId: string): boolean {
if (!this.config.domainGroups) {
return false;
}
const initialLength = this.config.domainGroups.length;
this.config.domainGroups = this.config.domainGroups.filter(group => group.id !== groupId);
if (initialLength !== this.config.domainGroups.length) {
this.domainGroups.delete(groupId);
return true;
}
return false;
}
/**
* Determine routing for an email
* @param fromDomain The sender domain
* @param toDomain The recipient domain
* @returns Routing decision or null if no matching rule
*/
public getRoutingForEmail(fromDomain: string, toDomain: string): {
targetServer: string;
targetPort: number;
useTls: boolean;
auth?: {
username?: string;
password?: string;
type?: 'PLAIN' | 'LOGIN' | 'OAUTH2';
};
headers?: {
name: string;
value: string;
append?: boolean;
}[];
} | null {
// Return default routing if routing is not enabled
if (!this.config.enabled) {
return this.getDefaultRouting();
}
// Sort rules if needed
if (this.rulesSortNeeded) {
this.sortRules();
}
// Normalize domains
fromDomain = fromDomain.toLowerCase();
toDomain = toDomain.toLowerCase();
// Check each rule in priority order
for (const rule of this.sortedRules) {
if (!rule.enabled) continue;
// Check if rule applies to this email
if (this.ruleMatchesEmail(rule, fromDomain, toDomain)) {
// Handle different actions
switch (rule.action) {
case 'route':
// Return routing information
return {
targetServer: rule.targetServer || this.config.defaultTargetServer || 'localhost',
targetPort: rule.targetPort || this.config.defaultTargetPort || 25,
useTls: rule.useTls ?? this.config.defaultUseTls ?? false,
auth: rule.auth,
headers: rule.headers
};
case 'block':
// Return null to indicate email should be blocked
return null;
case 'tag':
case 'filter':
// For tagging/filtering, we need to apply headers but continue checking rules
// This is simplified for now, in a real implementation we'd aggregate headers
continue;
}
}
}
// No rule matched, use default routing
return this.getDefaultRouting();
}
/**
* Check if a rule matches an email
* @param rule The routing rule to check
* @param fromDomain The sender domain
* @param toDomain The recipient domain
* @returns Whether the rule matches the email
*/
private ruleMatchesEmail(rule: IEmailRoutingRule, fromDomain: string, toDomain: string): boolean {
// Check source domains
if (rule.sourceDomains && rule.sourceDomains.length > 0) {
const matchesSourceDomain = rule.sourceDomains.some(
pattern => this.domainMatchesPattern(fromDomain, pattern)
);
if (!matchesSourceDomain) {
return false;
}
}
// Check destination domains
if (rule.destinationDomains && rule.destinationDomains.length > 0) {
const matchesDestinationDomain = rule.destinationDomains.some(
pattern => this.domainMatchesPattern(toDomain, pattern)
);
if (!matchesDestinationDomain) {
return false;
}
}
// Check domain groups
if (rule.domainGroups && rule.domainGroups.length > 0) {
// Check if either domain is in any of the specified groups
const domainsInGroups = rule.domainGroups
.map(groupId => this.domainGroups.get(groupId))
.filter(Boolean)
.some(group =>
group.domains.includes(fromDomain) ||
group.domains.includes(toDomain)
);
if (!domainsInGroups) {
return false;
}
}
// If we got here, all checks passed
return true;
}
/**
* Check if a domain matches a pattern
* @param domain The domain to check
* @param pattern The pattern to match against
* @returns Whether the domain matches the pattern
*/
private domainMatchesPattern(domain: string, pattern: IDomainPattern): boolean {
domain = domain.toLowerCase();
const patternStr = pattern.pattern.toLowerCase();
// Exact match
if (!pattern.isWildcard) {
return domain === patternStr;
}
// Wildcard match (*.example.com)
if (patternStr.startsWith('*.')) {
const suffix = patternStr.substring(2);
return domain.endsWith(suffix) && domain.length > suffix.length;
}
// Invalid pattern
return false;
}
/**
* Get default routing information
* @returns Default routing or null if no default configured
*/
private getDefaultRouting(): {
targetServer: string;
targetPort: number;
useTls: boolean;
} | null {
if (!this.config.defaultTargetServer) {
return null;
}
return {
targetServer: this.config.defaultTargetServer,
targetPort: this.config.defaultTargetPort || 25,
useTls: this.config.defaultUseTls || false
};
}
/**
* Get the current configuration
* @returns Current domain routing configuration
*/
public getConfig(): IEmailDomainRoutingConfig {
return this.config;
}
/**
* Update the configuration
* @param config New domain routing configuration
*/
public updateConfig(config: IEmailDomainRoutingConfig): void {
this.config = config;
this.rulesSortNeeded = true;
this.initialize();
}
/**
* Enable domain routing
*/
public enable(): void {
this.config.enabled = true;
}
/**
* Disable domain routing
*/
public disable(): void {
this.config.enabled = false;
}
}

View File

@ -1,14 +0,0 @@
// Core DcRouter components
export * from './classes.dcrouter.js';
export * from './classes.smtp.portconfig.js';
export * from './classes.email.domainrouter.js';
// Unified Email Configuration
export * from './classes.email.config.js';
export * from './classes.domain.router.js';
export * from './classes.unified.email.server.js';
// Shared Infrastructure Components
export * from './classes.delivery.queue.js';
export * from './classes.delivery.system.js';
export * from './classes.rate.limiter.js';

View File

@ -1,19 +0,0 @@
import { EmailService } from './classes.emailservice.js';
import { BounceManager, BounceType, BounceCategory } from './classes.bouncemanager.js';
import { EmailValidator } from './classes.emailvalidator.js';
import { TemplateManager } from './classes.templatemanager.js';
import { RuleManager } from './classes.rulemanager.js';
import { ApiManager } from './classes.apimanager.js';
import { MtaConnector } from './classes.connector.mta.js';
export {
EmailService as Email,
BounceManager,
BounceType,
BounceCategory,
EmailValidator,
TemplateManager,
RuleManager,
ApiManager,
MtaConnector
};

View File

@ -1,4 +1,5 @@
export * from './00_commitinfo_data.js'; export * from './00_commitinfo_data.js';
import { SzPlatformService } from './platformservice.js'; import { SzPlatformService } from './platformservice.js';
export * from './mail/index.js';
export const runCli = async () => {} export const runCli = async () => {}

View File

@ -1,7 +1,7 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
/** /**

View File

@ -1,5 +1,5 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EmailValidator } from '../email/classes.emailvalidator.js'; import { EmailValidator } from './classes.emailvalidator.js';
export interface IAttachment { export interface IAttachment {
filename: string; filename: string;
@ -593,6 +593,38 @@ export class Email {
return result; return result;
} }
/**
* Convert to simple Smartmail-compatible object (for backward compatibility)
* @returns A Promise with a simple Smartmail-compatible object
*/
public async toSmartmailBasic(): Promise<any> {
// Create a Smartmail-compatible object with the email data
const smartmail = {
options: {
from: this.from,
to: this.to,
subject: this.subject
},
content: {
text: this.text,
html: this.html || ''
},
headers: { ...this.headers },
attachments: this.attachments ? this.attachments.map(attachment => ({
name: attachment.filename,
data: attachment.content,
type: attachment.contentType,
cid: attachment.contentId
})) : [],
// Add basic Smartmail-compatible methods for compatibility
addHeader: (key: string, value: string) => {
smartmail.headers[key] = value;
}
};
return smartmail;
}
/** /**
* Create an Email instance from a Smartmail object * Create an Email instance from a Smartmail object
* @param smartmail The Smartmail instance to convert * @param smartmail The Smartmail instance to convert

View File

@ -1,5 +1,5 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
export interface IEmailValidationResult { export interface IEmailValidationResult {

View File

@ -1,6 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EmailService } from './classes.emailservice.js'; import { EmailService } from '../services/classes.emailservice.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
export class RuleManager { export class RuleManager {
public emailRef: EmailService; public emailRef: EmailService;

View File

@ -1,6 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
/** /**
* Email template type definition * Email template type definition

6
ts/mail/core/index.ts Normal file
View File

@ -0,0 +1,6 @@
// Core email components
export * from './classes.email.js';
export * from './classes.emailvalidator.js';
export * from './classes.templatemanager.js';
export * from './classes.bouncemanager.js';
export * from './classes.rulemanager.js';

View File

@ -1,15 +1,41 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EmailService } from './classes.emailservice.js'; import { EmailService } from '../services/classes.emailservice.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
// Import MTA classes // Import MTA classes
import { import { MtaService } from './classes.mta.js';
MtaService, import { Email as MtaEmail } from '../core/classes.email.js';
Email as MtaEmail,
type IEmailOptions, // Import Email types
DeliveryStatus, export interface IEmailOptions {
type IAttachment from: string;
} from '../mta/index.js'; to: string[];
cc?: string[];
bcc?: string[];
subject: string;
text?: string;
html?: string;
attachments?: IAttachment[];
headers?: { [key: string]: string };
}
// Reuse the DeliveryStatus from the email send job
export enum DeliveryStatus {
PENDING = 'pending',
PROCESSING = 'processing',
DELIVERED = 'delivered',
DEFERRED = 'deferred',
FAILED = 'failed'
}
// Reuse the IAttachment interface
export interface IAttachment {
filename: string;
content: Buffer;
contentType: string;
contentId?: string;
encoding?: string;
}
export class MtaConnector { export class MtaConnector {
public emailRef: EmailService; public emailRef: EmailService;

View File

@ -1,9 +1,9 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { type EmailProcessingMode, type IDomainRule } from './classes.email.config.js'; import { type EmailProcessingMode, type IDomainRule } from '../routing/classes.email.config.js';
/** /**
* Queue item status * Queue item status

View File

@ -1,16 +1,16 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import * as net from 'node:net'; import * as net from 'node:net';
import * as tls from 'node:tls'; import * as tls from 'node:tls';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { import {
SecurityLogger, SecurityLogger,
SecurityLogLevel, SecurityLogLevel,
SecurityEventType SecurityEventType
} from '../security/index.js'; } from '../../security/index.js';
import { UnifiedDeliveryQueue, type IQueueItem } from './classes.delivery.queue.js'; import { UnifiedDeliveryQueue, type IQueueItem } from './classes.delivery.queue.js';
import type { Email } from '../mta/classes.email.js'; import type { Email } from '../core/classes.email.js';
import type { IDomainRule } from './classes.email.config.js'; import type { IDomainRule } from '../routing/classes.email.config.js';
/** /**
* Delivery handler interface * Delivery handler interface
@ -331,7 +331,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
}, },
success: true success: true
}); });
} catch (error) { } catch (error: any) {
// Calculate delivery attempt time even for failures // Calculate delivery attempt time even for failures
const deliveryTime = Date.now() - startTime; const deliveryTime = Date.now() - startTime;
@ -423,9 +423,6 @@ export class MultiModeDeliverySystem extends EventEmitter {
}); });
}); });
// Implement SMTP protocol here
// This is a simplified implementation
// Send EHLO // Send EHLO
await this.smtpCommand(socket, `EHLO ${rule.mtaOptions?.domain || 'localhost'}`); await this.smtpCommand(socket, `EHLO ${rule.mtaOptions?.domain || 'localhost'}`);
@ -445,7 +442,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
// Complete the SMTP exchange // Complete the SMTP exchange
return this.completeSMTPExchange(socket, email, rule); return this.completeSMTPExchange(socket, email, rule);
} catch (error) { } catch (error: any) {
logger.log('error', `Failed to forward email: ${error.message}`); logger.log('error', `Failed to forward email: ${error.message}`);
// Close the connection // Close the connection
@ -511,10 +508,6 @@ export class MultiModeDeliverySystem extends EventEmitter {
// Close the connection // Close the connection
socket.destroy(); socket.destroy();
throw error;
}
socket.destroy();
throw error; throw error;
} }
} }
@ -554,7 +547,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
subject: email.subject, subject: email.subject,
dkimSigned: !!rule.mtaOptions?.dkimSign dkimSigned: !!rule.mtaOptions?.dkimSign
}; };
} catch (error) { } catch (error: any) {
logger.log('error', `Failed to process email in MTA mode: ${error.message}`); logger.log('error', `Failed to process email in MTA mode: ${error.message}`);
throw error; throw error;
} }
@ -633,7 +626,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
scanned: !!rule.contentScanning, scanned: !!rule.contentScanning,
transformed: !!(rule.transformations && rule.transformations.length > 0) transformed: !!(rule.transformations && rule.transformations.length > 0)
}; };
} catch (error) { } catch (error: any) {
logger.log('error', `Failed to process email: ${error.message}`); logger.log('error', `Failed to process email: ${error.message}`);
throw error; throw error;
} }
@ -658,7 +651,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
// Add headers // Add headers
content += `From: ${email.from}\r\n`; content += `From: ${email.from}\r\n`;
content += `To: ${email.to}\r\n`; content += `To: ${email.to.join(', ')}\r\n`;
content += `Subject: ${email.subject}\r\n`; content += `Subject: ${email.subject}\r\n`;
// Add additional headers // Add additional headers

View File

@ -1,6 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { Email } from './classes.email.js'; import { Email } from '../core/classes.email.js';
import { EmailSignJob } from './classes.emailsignjob.js'; import { EmailSignJob } from './classes.emailsignjob.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from './classes.mta.js';

View File

@ -1,4 +1,4 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from './classes.mta.js';
interface Headers { interface Headers {

View File

@ -1,20 +1,20 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { Email } from './classes.email.js'; import { Email } from '../core/classes.email.js';
import { EmailSendJob, DeliveryStatus } from './classes.emailsendjob.js'; import { EmailSendJob, DeliveryStatus } from './classes.emailsendjob.js';
import { DKIMCreator } from './classes.dkimcreator.js'; import { DKIMCreator } from '../security/classes.dkimcreator.js';
import { DKIMVerifier } from './classes.dkimverifier.js'; import { DKIMVerifier } from '../security/classes.dkimverifier.js';
import { SpfVerifier } from './classes.spfverifier.js'; import { SpfVerifier } from '../security/classes.spfverifier.js';
import { DmarcVerifier } from './classes.dmarcverifier.js'; import { DmarcVerifier } from '../security/classes.dmarcverifier.js';
import { SMTPServer, type ISmtpServerOptions } from './classes.smtpserver.js'; import { SMTPServer, type ISmtpServerOptions } from './classes.smtpserver.js';
import { DNSManager } from './classes.dnsmanager.js'; import { DNSManager } from '../routing/classes.dnsmanager.js';
import { ApiManager } from './classes.apimanager.js'; import { ApiManager } from '../services/classes.apimanager.js';
import { RateLimiter, type IRateLimitConfig } from './classes.ratelimiter.js'; import { RateLimiter, type IRateLimitConfig } from './classes.ratelimiter.js';
import { ContentScanner } from '../security/classes.contentscanner.js'; import { ContentScanner } from '../../security/classes.contentscanner.js';
import { IPWarmupManager } from '../deliverability/classes.ipwarmupmanager.js'; import { IPWarmupManager } from '../../deliverability/classes.ipwarmupmanager.js';
import { SenderReputationMonitor } from '../deliverability/classes.senderreputationmonitor.js'; import { SenderReputationMonitor } from '../../deliverability/classes.senderreputationmonitor.js';
import type { SzPlatformService } from '../platformservice.js'; import type { SzPlatformService } from '../../platformservice.js';
/** /**
* Configuration options for the MTA service * Configuration options for the MTA service
@ -265,7 +265,7 @@ export class MtaService {
this.dkimCreator = new DKIMCreator(this); this.dkimCreator = new DKIMCreator(this);
this.dkimVerifier = new DKIMVerifier(this); this.dkimVerifier = new DKIMVerifier(this);
this.dnsManager = new DNSManager(this); this.dnsManager = new DNSManager(this);
this.apiManager = new ApiManager(); // Initialize API manager later in start() method when emailService is available
// Initialize authentication verifiers // Initialize authentication verifiers
this.spfVerifier = new SpfVerifier(this); this.spfVerifier = new SpfVerifier(this);
@ -283,14 +283,15 @@ export class MtaService {
burstTokens: 5 // Allow small bursts burstTokens: 5 // Allow small bursts
}); });
// Initialize IP warmup manager // Initialize IP warmup manager with explicit config
const warmupConfig = this.config.outbound?.warmup; const warmupConfig = this.config.outbound?.warmup || {};
this.ipWarmupManager = IPWarmupManager.getInstance({ const ipWarmupConfig = {
enabled: warmupConfig?.enabled || false, enabled: warmupConfig.enabled || false,
ipAddresses: warmupConfig?.ipAddresses || [], ipAddresses: warmupConfig.ipAddresses || [],
targetDomains: warmupConfig?.targetDomains || [], targetDomains: warmupConfig.targetDomains || [],
fallbackPercentage: warmupConfig?.fallbackPercentage || 50 fallbackPercentage: warmupConfig.fallbackPercentage || 50
}); };
this.ipWarmupManager = IPWarmupManager.getInstance(ipWarmupConfig);
// Set active allocation policy if specified // Set active allocation policy if specified
if (warmupConfig?.allocationPolicy) { if (warmupConfig?.allocationPolicy) {
@ -432,6 +433,9 @@ export class MtaService {
try { try {
console.log('Starting MTA service...'); console.log('Starting MTA service...');
// Initialize API manager now that emailService is available
this.apiManager = new ApiManager(this.emailService);
// Load or provision certificate // Load or provision certificate
await this.loadOrProvisionCertificate(); await this.loadOrProvisionCertificate();
@ -755,7 +759,7 @@ export class MtaService {
console.log(`Processing bounce notification from ${email.from}`); console.log(`Processing bounce notification from ${email.from}`);
// Convert to Smartmail for bounce processing // Convert to Smartmail for bounce processing
const smartmail = await email.toSmartmail(); const smartmail = await email.toSmartmailBasic();
// If we have a bounce manager available, process it // If we have a bounce manager available, process it
if (this.emailService?.bounceManager) { if (this.emailService?.bounceManager) {

View File

@ -1,4 +1,4 @@
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
/** /**
* Configuration options for rate limiter * Configuration options for rate limiter

View File

@ -1,15 +1,15 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { Email } from './classes.email.js'; import { Email } from '../core/classes.email.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from './classes.mta.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { import {
SecurityLogger, SecurityLogger,
SecurityLogLevel, SecurityLogLevel,
SecurityEventType, SecurityEventType,
IPReputationChecker, IPReputationChecker,
ReputationThreshold ReputationThreshold
} from '../security/index.js'; } from '../../security/index.js';
export interface ISmtpServerOptions { export interface ISmtpServerOptions {
port: number; port: number;

View File

@ -1,7 +1,7 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
/** /**
* Interface for rate limit configuration * Interface for rate limit configuration

18
ts/mail/delivery/index.ts Normal file
View File

@ -0,0 +1,18 @@
// Email delivery components
export * from './classes.mta.js';
export * from './classes.smtpserver.js';
export * from './classes.emailsignjob.js';
export * from './classes.delivery.queue.js';
export * from './classes.delivery.system.js';
// Handle exports with naming conflicts
export { EmailSendJob } from './classes.emailsendjob.js';
export { DeliveryStatus } from './classes.connector.mta.js';
export { MtaConnector } from './classes.connector.mta.js';
// Rate limiter exports - fix naming conflict
export { RateLimiter } from './classes.ratelimiter.js';
export type { IRateLimitConfig } from './classes.ratelimiter.js';
// Unified rate limiter
export * from './classes.unified.rate.limiter.js';

29
ts/mail/index.ts Normal file
View File

@ -0,0 +1,29 @@
// Export all mail modules for simplified imports
export * from './routing/index.js';
export * from './security/index.js';
export * from './services/index.js';
// Make the core and delivery modules accessible
import * as Core from './core/index.js';
import * as Delivery from './delivery/index.js';
export { Core, Delivery };
// For backward compatibility
import { Email } from './core/classes.email.js';
import { EmailService } from './services/classes.emailservice.js';
import { BounceManager, BounceType, BounceCategory } from './core/classes.bouncemanager.js';
import { EmailValidator } from './core/classes.emailvalidator.js';
import { TemplateManager } from './core/classes.templatemanager.js';
import { RuleManager } from './core/classes.rulemanager.js';
import { ApiManager } from './services/classes.apimanager.js';
import { MtaService } from './delivery/classes.mta.js';
import { DcRouter } from '../classes.dcrouter.js';
// Re-export with compatibility names
export {
EmailService as Email, // For backward compatibility with email/index.ts
ApiManager,
Email as EmailClass, // Provide the actual Email class under a different name
DcRouter
};

View File

@ -1,6 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from '../delivery/classes.mta.js';
/** /**
* Interface for DNS record information * Interface for DNS record information

View File

@ -1,4 +1,4 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
import { type IDomainRule, type EmailProcessingMode } from './classes.email.config.js'; import { type IDomainRule, type EmailProcessingMode } from './classes.email.config.js';

View File

@ -1,4 +1,4 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
/** /**
* Email processing modes * Email processing modes

View File

@ -1,23 +1,23 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { import {
SecurityLogger, SecurityLogger,
SecurityLogLevel, SecurityLogLevel,
SecurityEventType SecurityEventType
} from '../security/index.js'; } from '../../security/index.js';
import { DomainRouter } from './classes.domain.router.js'; import { DomainRouter } from './classes.domain.router.js';
import type { import type {
IEmailConfig, IEmailConfig,
EmailProcessingMode, EmailProcessingMode,
IDomainRule IDomainRule
} from './classes.email.config.js'; } from './classes.email.config.js';
import { Email } from '../mta/classes.email.js'; import { Email } from '../core/classes.email.js';
import * as net from 'node:net'; import * as net from 'node:net';
import * as tls from 'node:tls'; import * as tls from 'node:tls';
import * as stream from 'node:stream'; import * as stream from 'node:stream';
import { SMTPServer as MtaSmtpServer } from '../mta/classes.smtpserver.js'; import { SMTPServer as MtaSmtpServer } from '../delivery/classes.smtpserver.js';
/** /**
* Options for the unified email server * Options for the unified email server

5
ts/mail/routing/index.ts Normal file
View File

@ -0,0 +1,5 @@
// Email routing components
export * from './classes.domain.router.js';
export * from './classes.email.config.js';
export * from './classes.unified.email.server.js';
export * from './classes.dnsmanager.js';

View File

@ -1,8 +1,8 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { Email } from './classes.email.js'; import { Email } from '../core/classes.email.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from '../delivery/classes.mta.js';
const readFile = plugins.util.promisify(plugins.fs.readFile); const readFile = plugins.util.promisify(plugins.fs.readFile);
const writeFile = plugins.util.promisify(plugins.fs.writeFile); const writeFile = plugins.util.promisify(plugins.fs.writeFile);

View File

@ -1,7 +1,7 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { MtaService } from './classes.mta.js'; import { MtaService } from '../delivery/classes.mta.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
/** /**
* Result of a DKIM verification * Result of a DKIM verification

View File

@ -1,9 +1,9 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from '../delivery/classes.mta.js';
import type { Email } from './classes.email.js'; import type { Email } from '../core/classes.email.js';
import type { IDnsVerificationResult } from './classes.dnsmanager.js'; import type { IDnsVerificationResult } from '../routing/classes.dnsmanager.js';
/** /**
* DMARC policy types * DMARC policy types

View File

@ -1,9 +1,9 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
import type { MtaService } from './classes.mta.js'; import type { MtaService } from '../delivery/classes.mta.js';
import type { Email } from './classes.email.js'; import type { Email } from '../core/classes.email.js';
import type { IDnsVerificationResult } from './classes.dnsmanager.js'; import type { IDnsVerificationResult } from '../routing/classes.dnsmanager.js';
/** /**
* SPF result qualifiers * SPF result qualifiers

View File

@ -0,0 +1,5 @@
// Email security components
export * from './classes.dkimcreator.js';
export * from './classes.dkimverifier.js';
export * from './classes.dmarcverifier.js';
export * from './classes.spfverifier.js';

View File

@ -1,6 +1,6 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import { EmailService } from './classes.emailservice.js'; import { EmailService } from './classes.emailservice.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
export class ApiManager { export class ApiManager {
public emailRef: EmailService; public emailRef: EmailService;

View File

@ -1,16 +1,16 @@
import * as plugins from '../plugins.js'; import * as plugins from '../../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../../paths.js';
import { MtaConnector } from './classes.connector.mta.js'; import { MtaConnector } from '../delivery/classes.connector.mta.js';
import { RuleManager } from './classes.rulemanager.js'; import { RuleManager } from '../core/classes.rulemanager.js';
import { ApiManager } from './classes.apimanager.js'; import { ApiManager } from './classes.apimanager.js';
import { TemplateManager } from './classes.templatemanager.js'; import { TemplateManager } from '../core/classes.templatemanager.js';
import { EmailValidator } from './classes.emailvalidator.js'; import { EmailValidator } from '../core/classes.emailvalidator.js';
import { BounceManager } from './classes.bouncemanager.js'; import { BounceManager } from '../core/classes.bouncemanager.js';
import { logger } from '../logger.js'; import { logger } from '../../logger.js';
import type { SzPlatformService } from '../platformservice.js'; import type { SzPlatformService } from '../../platformservice.js';
// Import MTA service // Import MTA service
import { MtaService, type IMtaConfig } from '../mta/index.js'; import { MtaService, type IMtaConfig } from '../delivery/classes.mta.js';
export interface IEmailConstructorOptions { export interface IEmailConstructorOptions {
useMta?: boolean; useMta?: boolean;

View File

@ -0,0 +1,3 @@
// Email services
export * from './classes.emailservice.js';
export * from './classes.apimanager.js';

View File

@ -1,956 +0,0 @@
import * as plugins from '../plugins.js';
import { Email } from './classes.email.js';
import type { IEmailOptions } from './classes.email.js';
import { DeliveryStatus } from './classes.emailsendjob.js';
import type { MtaService } from './classes.mta.js';
import type { IDnsRecord } from './classes.dnsmanager.js';
/**
* Authentication options for API requests
*/
interface AuthOptions {
/** Required API keys for different endpoints */
apiKeys: Map<string, string[]>;
/** JWT secret for token-based authentication */
jwtSecret?: string;
/** Whether to validate IP addresses */
validateIp?: boolean;
/** Allowed IP addresses */
allowedIps?: string[];
}
/**
* Rate limiting options for API endpoints
*/
interface RateLimitOptions {
/** Maximum requests per window */
maxRequests: number;
/** Time window in milliseconds */
windowMs: number;
/** Whether to apply per endpoint */
perEndpoint?: boolean;
/** Whether to apply per IP */
perIp?: boolean;
}
/**
* API route definition
*/
interface ApiRoute {
/** HTTP method */
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
/** Path pattern */
path: string;
/** Handler function */
handler: (req: any, res: any) => Promise<any>;
/** Required authentication level */
authLevel: 'none' | 'basic' | 'admin';
/** Rate limiting options */
rateLimit?: RateLimitOptions;
/** Route description */
description?: string;
}
/**
* Email send request
*/
interface SendEmailRequest {
/** Email details */
email: IEmailOptions;
/** Whether to validate domains before sending */
validateDomains?: boolean;
/** Priority level (1-5, 1 = highest) */
priority?: number;
}
/**
* Email status response
*/
interface EmailStatusResponse {
/** Email ID */
id: string;
/** Current status */
status: DeliveryStatus;
/** Send time */
sentAt?: Date;
/** Delivery time */
deliveredAt?: Date;
/** Error message if failed */
error?: string;
/** Recipient address */
recipient: string;
/** Number of delivery attempts */
attempts: number;
/** Next retry time */
nextRetry?: Date;
}
/**
* Domain verification response
*/
interface DomainVerificationResponse {
/** Domain name */
domain: string;
/** Whether the domain is verified */
verified: boolean;
/** Verification details */
details: {
/** SPF record status */
spf: {
valid: boolean;
record?: string;
error?: string;
};
/** DKIM record status */
dkim: {
valid: boolean;
record?: string;
error?: string;
};
/** DMARC record status */
dmarc: {
valid: boolean;
record?: string;
error?: string;
};
/** MX record status */
mx: {
valid: boolean;
records?: string[];
error?: string;
};
};
}
/**
* API error response
*/
interface ApiError {
/** Error code */
code: string;
/** Error message */
message: string;
/** Detailed error information */
details?: any;
}
/**
* Simple HTTP Response helper
*/
class HttpResponse {
private headers: Record<string, string> = {
'Content-Type': 'application/json'
};
public statusCode: number = 200;
constructor(private res: any) {}
header(name: string, value: string): HttpResponse {
this.headers[name] = value;
return this;
}
status(code: number): HttpResponse {
this.statusCode = code;
return this;
}
json(data: any): void {
this.res.writeHead(this.statusCode, this.headers);
this.res.end(JSON.stringify(data));
}
end(): void {
this.res.writeHead(this.statusCode, this.headers);
this.res.end();
}
}
/**
* API Manager for MTA service
*/
export class ApiManager {
/** TypedRouter for API routing */
public typedrouter = new plugins.typedrequest.TypedRouter();
/** MTA service reference */
private mtaRef: MtaService;
/** HTTP server */
private server: any;
/** Authentication options */
private authOptions: AuthOptions;
/** API routes */
private routes: ApiRoute[] = [];
/** Rate limiters */
private rateLimiters: Map<string, {
count: number;
resetTime: number;
clients: Map<string, {
count: number;
resetTime: number;
}>;
}> = new Map();
/**
* Initialize API Manager
* @param mtaRef MTA service reference
*/
constructor(mtaRef?: MtaService) {
this.mtaRef = mtaRef;
// Default authentication options
this.authOptions = {
apiKeys: new Map(),
validateIp: false,
allowedIps: []
};
// Register routes
this.registerRoutes();
// Create HTTP server with request handler
this.server = plugins.http.createServer(this.handleRequest.bind(this));
}
/**
* Set MTA service reference
* @param mtaRef MTA service reference
*/
public setMtaService(mtaRef: MtaService): void {
this.mtaRef = mtaRef;
}
/**
* Configure authentication options
* @param options Authentication options
*/
public configureAuth(options: Partial<AuthOptions>): void {
this.authOptions = {
...this.authOptions,
...options
};
}
/**
* Handle HTTP request
*/
private async handleRequest(req: any, res: any): Promise<void> {
const start = Date.now();
// Create a response helper
const response = new HttpResponse(res);
// Add CORS headers
response.header('Access-Control-Allow-Origin', '*');
response.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-API-Key');
// Handle preflight OPTIONS request
if (req.method === 'OPTIONS') {
return response.status(200).end();
}
try {
// Parse URL to get path and query
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
const path = url.pathname;
// Collect request body if POST or PUT
let body = '';
if (req.method === 'POST' || req.method === 'PUT') {
await new Promise<void>((resolve, reject) => {
req.on('data', (chunk: Buffer) => {
body += chunk.toString();
});
req.on('end', () => {
resolve();
});
req.on('error', (err: Error) => {
reject(err);
});
});
// Parse body as JSON if Content-Type is application/json
const contentType = req.headers['content-type'] || '';
if (contentType.includes('application/json')) {
try {
req.body = JSON.parse(body);
} catch (error) {
return response.status(400).json({
code: 'INVALID_JSON',
message: 'Invalid JSON in request body'
});
}
} else {
req.body = body;
}
}
// Add authentication level to request
req.authLevel = 'none';
// Check API key
const apiKey = req.headers['x-api-key'];
if (apiKey) {
for (const [level, keys] of this.authOptions.apiKeys.entries()) {
if (keys.includes(apiKey)) {
req.authLevel = level;
break;
}
}
}
// Check JWT token (if configured)
if (this.authOptions.jwtSecret && req.headers.authorization) {
try {
const token = req.headers.authorization.split(' ')[1];
// Note: We would need to add JWT verification
// Using a simple placeholder for now
const decoded = { level: 'none' }; // Simplified - would use actual JWT library
if (decoded && decoded.level) {
req.authLevel = decoded.level;
req.user = decoded;
}
} catch (error) {
// Invalid token, but don't fail the request yet
console.error('Invalid JWT token:', error.message);
}
}
// Check IP address (if configured)
if (this.authOptions.validateIp) {
const clientIp = req.socket.remoteAddress;
if (!this.authOptions.allowedIps.includes(clientIp)) {
return response.status(403).json({
code: 'FORBIDDEN',
message: 'IP address not allowed'
});
}
}
// Find matching route
const route = this.findRoute(req.method, path);
if (!route) {
return response.status(404).json({
code: 'NOT_FOUND',
message: 'Endpoint not found'
});
}
// Check authentication
if (route.authLevel !== 'none' && req.authLevel !== route.authLevel && req.authLevel !== 'admin') {
return response.status(403).json({
code: 'FORBIDDEN',
message: `This endpoint requires ${route.authLevel} access`
});
}
// Check rate limit
if (route.rateLimit) {
const exceeded = this.checkRateLimit(route, req);
if (exceeded) {
return response.status(429).json({
code: 'RATE_LIMIT_EXCEEDED',
message: 'Rate limit exceeded, please try again later'
});
}
}
// Extract path parameters
const pathParams = this.extractPathParams(route.path, path);
req.params = pathParams;
// Extract query parameters
req.query = {};
for (const [key, value] of url.searchParams.entries()) {
req.query[key] = value;
}
// Handle the request
await route.handler(req, response);
// Log request
const duration = Date.now() - start;
console.log(`[API] ${req.method} ${path} ${response.statusCode} ${duration}ms`);
} catch (error) {
console.error(`Error handling request:`, error);
// Send appropriate error response
const status = error.status || 500;
const apiError: ApiError = {
code: error.code || 'INTERNAL_ERROR',
message: error.message || 'Internal server error'
};
if (process.env.NODE_ENV !== 'production') {
apiError.details = error.stack;
}
response.status(status).json(apiError);
}
}
/**
* Find a route matching the method and path
*/
private findRoute(method: string, path: string): ApiRoute | null {
for (const route of this.routes) {
if (route.method === method && this.pathMatches(route.path, path)) {
return route;
}
}
return null;
}
/**
* Check if a path matches a route pattern
*/
private pathMatches(pattern: string, path: string): boolean {
// Convert route pattern to regex
const patternParts = pattern.split('/');
const pathParts = path.split('/');
if (patternParts.length !== pathParts.length) {
return false;
}
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
// Parameter - always matches
continue;
}
if (patternParts[i] !== pathParts[i]) {
return false;
}
}
return true;
}
/**
* Extract path parameters from URL
*/
private extractPathParams(pattern: string, path: string): Record<string, string> {
const params: Record<string, string> = {};
const patternParts = pattern.split('/');
const pathParts = path.split('/');
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
const paramName = patternParts[i].substring(1);
params[paramName] = pathParts[i];
}
}
return params;
}
/**
* Register API routes
*/
private registerRoutes(): void {
// Email routes
this.addRoute({
method: 'POST',
path: '/api/email/send',
handler: this.handleSendEmail.bind(this),
authLevel: 'basic',
description: 'Send an email'
});
this.addRoute({
method: 'GET',
path: '/api/email/status/:id',
handler: this.handleGetEmailStatus.bind(this),
authLevel: 'basic',
description: 'Get email delivery status'
});
// Domain routes
this.addRoute({
method: 'GET',
path: '/api/domain/verify/:domain',
handler: this.handleVerifyDomain.bind(this),
authLevel: 'basic',
description: 'Verify domain DNS records'
});
this.addRoute({
method: 'GET',
path: '/api/domain/records/:domain',
handler: this.handleGetDomainRecords.bind(this),
authLevel: 'basic',
description: 'Get recommended DNS records for domain'
});
// DKIM routes
this.addRoute({
method: 'POST',
path: '/api/dkim/generate/:domain',
handler: this.handleGenerateDkim.bind(this),
authLevel: 'admin',
description: 'Generate DKIM keys for domain'
});
this.addRoute({
method: 'GET',
path: '/api/dkim/public/:domain',
handler: this.handleGetDkimPublicKey.bind(this),
authLevel: 'basic',
description: 'Get DKIM public key for domain'
});
// Stats route
this.addRoute({
method: 'GET',
path: '/api/stats',
handler: this.handleGetStats.bind(this),
authLevel: 'admin',
description: 'Get MTA statistics'
});
// Documentation route
this.addRoute({
method: 'GET',
path: '/api',
handler: this.handleGetApiDocs.bind(this),
authLevel: 'none',
description: 'API documentation'
});
}
/**
* Add an API route
* @param route Route definition
*/
private addRoute(route: ApiRoute): void {
this.routes.push(route);
}
/**
* Check rate limit for a route
* @param route Route definition
* @param req Express request
* @returns Whether rate limit is exceeded
*/
private checkRateLimit(route: ApiRoute, req: any): boolean {
if (!route.rateLimit) return false;
const { maxRequests, windowMs, perEndpoint, perIp } = route.rateLimit;
// Determine rate limit key
let key = 'global';
if (perEndpoint) {
key = `${route.method}:${route.path}`;
}
// Get or create limiter
if (!this.rateLimiters.has(key)) {
this.rateLimiters.set(key, {
count: 0,
resetTime: Date.now() + windowMs,
clients: new Map()
});
}
const limiter = this.rateLimiters.get(key);
// Reset if window has passed
if (Date.now() > limiter.resetTime) {
limiter.count = 0;
limiter.resetTime = Date.now() + windowMs;
limiter.clients.clear();
}
// Check per-IP limit if enabled
if (perIp) {
const clientIp = req.socket.remoteAddress;
let clientLimiter = limiter.clients.get(clientIp);
if (!clientLimiter) {
clientLimiter = {
count: 0,
resetTime: Date.now() + windowMs
};
limiter.clients.set(clientIp, clientLimiter);
}
// Reset client limiter if needed
if (Date.now() > clientLimiter.resetTime) {
clientLimiter.count = 0;
clientLimiter.resetTime = Date.now() + windowMs;
}
// Check client limit
if (clientLimiter.count >= maxRequests) {
return true;
}
// Increment client count
clientLimiter.count++;
} else {
// Check global limit
if (limiter.count >= maxRequests) {
return true;
}
// Increment global count
limiter.count++;
}
return false;
}
/**
* Create an API error
* @param code Error code
* @param message Error message
* @param status HTTP status code
* @param details Additional details
* @returns API error
*/
private createError(code: string, message: string, status = 400, details?: any): Error & { code: string; status: number; details?: any } {
const error = new Error(message) as Error & { code: string; status: number; details?: any };
error.code = code;
error.status = status;
if (details) {
error.details = details;
}
return error;
}
/**
* Validate that MTA service is available
*/
private validateMtaService(): void {
if (!this.mtaRef) {
throw this.createError('SERVICE_UNAVAILABLE', 'MTA service is not available', 503);
}
}
/**
* Handle email send request
* @param req Express request
* @param res Express response
*/
private async handleSendEmail(req: any, res: any): Promise<void> {
this.validateMtaService();
const data = req.body as SendEmailRequest;
if (!data || !data.email) {
throw this.createError('INVALID_REQUEST', 'Missing email data');
}
try {
// Create Email instance
const email = new Email(data.email);
// Validate domains if requested
if (data.validateDomains) {
const fromDomain = email.getFromDomain();
if (fromDomain) {
const verification = await this.mtaRef.dnsManager.verifyEmailAuthRecords(fromDomain);
// Check if SPF and DKIM are valid
if (!verification.spf.valid || !verification.dkim.valid) {
throw this.createError('DOMAIN_VERIFICATION_FAILED', 'Domain DNS verification failed', 400, {
verification
});
}
}
}
// Send email
const id = await this.mtaRef.send(email);
// Return success response
res.json({
id,
message: 'Email queued successfully',
status: 'pending'
});
} catch (error) {
// Handle Email constructor errors
if (error.message.includes('Invalid') || error.message.includes('must have')) {
throw this.createError('INVALID_EMAIL', error.message);
}
throw error;
}
}
/**
* Handle email status request
* @param req Express request
* @param res Express response
*/
private async handleGetEmailStatus(req: any, res: any): Promise<void> {
this.validateMtaService();
const id = req.params.id;
if (!id) {
throw this.createError('INVALID_REQUEST', 'Missing email ID');
}
// Get email status
const status = this.mtaRef.getEmailStatus(id);
if (!status) {
throw this.createError('NOT_FOUND', `Email with ID ${id} not found`, 404);
}
// Create response
const response: EmailStatusResponse = {
id: status.id,
status: status.status,
sentAt: status.addedAt,
recipient: status.email.to[0],
attempts: status.attempts
};
// Add additional fields if available
if (status.lastAttempt) {
response.sentAt = status.lastAttempt;
}
if (status.status === DeliveryStatus.DELIVERED) {
response.deliveredAt = status.lastAttempt;
}
if (status.error) {
response.error = status.error.message;
}
if (status.nextAttempt) {
response.nextRetry = status.nextAttempt;
}
res.json(response);
}
/**
* Handle domain verification request
* @param req Express request
* @param res Express response
*/
private async handleVerifyDomain(req: any, res: any): Promise<void> {
this.validateMtaService();
const domain = req.params.domain;
if (!domain) {
throw this.createError('INVALID_REQUEST', 'Missing domain');
}
try {
// Verify domain DNS records
const records = await this.mtaRef.dnsManager.verifyEmailAuthRecords(domain);
// Get MX records
let mxValid = false;
let mxRecords: string[] = [];
let mxError: string = undefined;
try {
const mxResult = await this.mtaRef.dnsManager.lookupMx(domain);
mxValid = mxResult.length > 0;
mxRecords = mxResult.map(mx => mx.exchange);
} catch (error) {
mxError = error.message;
}
// Create response
const response: DomainVerificationResponse = {
domain,
verified: records.spf.valid && records.dkim.valid && records.dmarc.valid && mxValid,
details: {
spf: {
valid: records.spf.valid,
record: records.spf.value,
error: records.spf.error
},
dkim: {
valid: records.dkim.valid,
record: records.dkim.value,
error: records.dkim.error
},
dmarc: {
valid: records.dmarc.valid,
record: records.dmarc.value,
error: records.dmarc.error
},
mx: {
valid: mxValid,
records: mxRecords,
error: mxError
}
}
};
res.json(response);
} catch (error) {
throw this.createError('VERIFICATION_FAILED', `Domain verification failed: ${error.message}`);
}
}
/**
* Handle get domain records request
* @param req Express request
* @param res Express response
*/
private async handleGetDomainRecords(req: any, res: any): Promise<void> {
this.validateMtaService();
const domain = req.params.domain;
if (!domain) {
throw this.createError('INVALID_REQUEST', 'Missing domain');
}
try {
// Generate recommended DNS records
const records = await this.mtaRef.dnsManager.generateAllRecommendedRecords(domain);
res.json({
domain,
records
});
} catch (error) {
throw this.createError('GENERATION_FAILED', `DNS record generation failed: ${error.message}`);
}
}
/**
* Handle generate DKIM keys request
* @param req Express request
* @param res Express response
*/
private async handleGenerateDkim(req: any, res: any): Promise<void> {
this.validateMtaService();
const domain = req.params.domain;
if (!domain) {
throw this.createError('INVALID_REQUEST', 'Missing domain');
}
try {
// Generate DKIM keys
await this.mtaRef.dkimCreator.handleDKIMKeysForDomain(domain);
// Get DNS record
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
res.json({
domain,
dnsRecord,
message: 'DKIM keys generated successfully'
});
} catch (error) {
throw this.createError('GENERATION_FAILED', `DKIM generation failed: ${error.message}`);
}
}
/**
* Handle get DKIM public key request
* @param req Express request
* @param res Express response
*/
private async handleGetDkimPublicKey(req: any, res: any): Promise<void> {
this.validateMtaService();
const domain = req.params.domain;
if (!domain) {
throw this.createError('INVALID_REQUEST', 'Missing domain');
}
try {
// Get DKIM keys
const keys = await this.mtaRef.dkimCreator.readDKIMKeys(domain);
// Get DNS record
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
res.json({
domain,
publicKey: keys.publicKey,
dnsRecord
});
} catch (error) {
throw this.createError('NOT_FOUND', `DKIM keys not found for domain: ${domain}`, 404);
}
}
/**
* Handle get stats request
* @param req Express request
* @param res Express response
*/
private async handleGetStats(req: any, res: any): Promise<void> {
this.validateMtaService();
// Get MTA stats
const stats = this.mtaRef.getStats();
res.json(stats);
}
/**
* Handle get API docs request
* @param req Express request
* @param res Express response
*/
private async handleGetApiDocs(req: any, res: any): Promise<void> {
// Generate API documentation
const docs = {
name: 'MTA API',
version: '1.0.0',
description: 'API for interacting with the MTA service',
endpoints: this.routes.map(route => ({
method: route.method,
path: route.path,
description: route.description,
authLevel: route.authLevel
}))
};
res.json(docs);
}
/**
* Start the API server
* @param port Port to listen on
* @returns Promise that resolves when server is started
*/
public start(port: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {
try {
// Start HTTP server
this.server.listen(port, () => {
console.log(`API server listening on port ${port}`);
resolve();
});
} catch (error) {
console.error('Failed to start API server:', error);
reject(error);
}
});
}
/**
* Stop the API server
*/
public stop(): void {
if (this.server) {
this.server.close();
console.log('API server stopped');
}
}
}

View File

@ -1,10 +0,0 @@
export * from './classes.dkimcreator.js';
export * from './classes.emailsignjob.js';
export * from './classes.dkimverifier.js';
export * from './classes.dmarcverifier.js';
export * from './classes.spfverifier.js';
export * from './classes.mta.js';
export * from './classes.smtpserver.js';
export * from './classes.emailsendjob.js';
export * from './classes.email.js';
export * from './classes.ratelimiter.js';

View File

@ -1,9 +1,9 @@
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
import * as paths from './paths.js'; import * as paths from './paths.js';
import { PlatformServiceDb } from './classes.platformservicedb.js' import { PlatformServiceDb } from './classes.platformservicedb.js'
import { EmailService } from './email/classes.emailservice.js'; import { EmailService } from './mail/services/classes.emailservice.js';
import { SmsService } from './sms/classes.smsservice.js'; import { SmsService } from './sms/classes.smsservice.js';
import { MtaService } from './mta/classes.mta.js'; import { MtaService } from './mail/delivery/classes.mta.js';
export class SzPlatformService { export class SzPlatformService {
public projectinfo: plugins.projectinfo.ProjectInfo; public projectinfo: plugins.projectinfo.ProjectInfo;

View File

@ -1,8 +1,8 @@
import * as plugins from '../plugins.js'; import * as plugins from '../plugins.js';
import * as paths from '../paths.js'; import * as paths from '../paths.js';
import { logger } from '../logger.js'; import { logger } from '../logger.js';
import { Email } from '../mta/classes.email.js'; import { Email } from '../mail/core/classes.email.js';
import type { IAttachment } from '../mta/classes.email.js'; import type { IAttachment } from '../mail/core/classes.email.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';