From fe2069c48ebe0bdb362440530b00ffe2e9d9cfd7 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Thu, 8 May 2025 01:13:54 +0000 Subject: [PATCH] update --- readme.plan.md | 44 +- test/test.bouncemanager.ts | 2 +- test/test.contentscanner.ts | 2 +- test/test.emailauth.ts | 6 +- test/test.integration.ts | 8 +- test/test.smartmail.ts | 6 +- ts/{dcrouter => }/classes.dcrouter.ts | 19 +- ts/{dcrouter => }/classes.smtp.portconfig.ts | 2 +- ts/dcrouter/classes.dcr.sz.connector.ts | 20 - ts/dcrouter/classes.email.domainrouter.ts | 431 -------- ts/dcrouter/index.ts | 14 - ts/email/index.ts | 19 - ts/index.ts | 1 + .../core}/classes.bouncemanager.ts | 8 +- ts/{mta => mail/core}/classes.email.ts | 36 +- .../core}/classes.emailvalidator.ts | 4 +- .../core}/classes.rulemanager.ts | 6 +- .../core}/classes.templatemanager.ts | 6 +- ts/mail/core/index.ts | 6 + .../delivery}/classes.connector.mta.ts | 46 +- .../delivery}/classes.delivery.queue.ts | 6 +- .../delivery}/classes.delivery.system.ts | 27 +- .../delivery}/classes.emailsendjob.ts | 6 +- .../delivery}/classes.emailsignjob.ts | 2 +- ts/{mta => mail/delivery}/classes.mta.ts | 50 +- .../delivery}/classes.ratelimiter.ts | 2 +- .../delivery}/classes.smtpserver.ts | 10 +- .../delivery/classes.unified.rate.limiter.ts} | 6 +- ts/mail/delivery/index.ts | 18 + ts/mail/index.ts | 29 + .../routing}/classes.dnsmanager.ts | 6 +- .../routing}/classes.domain.router.ts | 2 +- .../routing}/classes.email.config.ts | 2 +- .../routing}/classes.unified.email.server.ts | 12 +- ts/mail/routing/index.ts | 5 + .../security}/classes.dkimcreator.ts | 8 +- .../security}/classes.dkimverifier.ts | 8 +- .../security}/classes.dmarcverifier.ts | 12 +- .../security}/classes.spfverifier.ts | 12 +- ts/mail/security/index.ts | 5 + .../services}/classes.apimanager.ts | 4 +- .../services}/classes.emailservice.ts | 20 +- ts/mail/services/index.ts | 3 + ts/mta/classes.apimanager.ts | 956 ------------------ ts/mta/index.ts | 10 - ts/platformservice.ts | 4 +- ts/security/classes.contentscanner.ts | 4 +- 47 files changed, 295 insertions(+), 1620 deletions(-) rename ts/{dcrouter => }/classes.dcrouter.ts (95%) rename ts/{dcrouter => }/classes.smtp.portconfig.ts (99%) delete mode 100644 ts/dcrouter/classes.dcr.sz.connector.ts delete mode 100644 ts/dcrouter/classes.email.domainrouter.ts delete mode 100644 ts/dcrouter/index.ts delete mode 100644 ts/email/index.ts rename ts/{email => mail/core}/classes.bouncemanager.ts (99%) rename ts/{mta => mail/core}/classes.email.ts (95%) rename ts/{email => mail/core}/classes.emailvalidator.ts (98%) rename ts/{email => mail/core}/classes.rulemanager.ts (97%) rename ts/{email => mail/core}/classes.templatemanager.ts (98%) create mode 100644 ts/mail/core/index.ts rename ts/{email => mail/delivery}/classes.connector.mta.ts (95%) rename ts/{dcrouter => mail/delivery}/classes.delivery.queue.ts (98%) rename ts/{dcrouter => mail/delivery}/classes.delivery.system.ts (98%) rename ts/{mta => mail/delivery}/classes.emailsendjob.ts (99%) rename ts/{mta => mail/delivery}/classes.emailsignjob.ts (97%) rename ts/{mta => mail/delivery}/classes.mta.ts (96%) rename ts/{mta => mail/delivery}/classes.ratelimiter.ts (99%) rename ts/{mta => mail/delivery}/classes.smtpserver.ts (99%) rename ts/{dcrouter/classes.rate.limiter.ts => mail/delivery/classes.unified.rate.limiter.ts} (99%) create mode 100644 ts/mail/delivery/index.ts create mode 100644 ts/mail/index.ts rename ts/{mta => mail/routing}/classes.dnsmanager.ts (99%) rename ts/{dcrouter => mail/routing}/classes.domain.router.ts (99%) rename ts/{dcrouter => mail/routing}/classes.email.config.ts (98%) rename ts/{dcrouter => mail/routing}/classes.unified.email.server.ts (99%) create mode 100644 ts/mail/routing/index.ts rename ts/{mta => mail/security}/classes.dkimcreator.ts (95%) rename ts/{mta => mail/security}/classes.dkimverifier.ts (98%) rename ts/{mta => mail/security}/classes.dmarcverifier.ts (97%) rename ts/{mta => mail/security}/classes.spfverifier.ts (98%) create mode 100644 ts/mail/security/index.ts rename ts/{email => mail/services}/classes.apimanager.ts (96%) rename ts/{email => mail/services}/classes.emailservice.ts (89%) create mode 100644 ts/mail/services/index.ts delete mode 100644 ts/mta/classes.apimanager.ts delete mode 100644 ts/mta/index.ts diff --git a/readme.plan.md b/readme.plan.md index de4fce6..a33c44a 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -1022,11 +1022,13 @@ export class DcRouter { - [x] Build mode-specific metrics and logging ### Phase 5: Testing and Documentation -- [ ] Create comprehensive unit tests for all components -- [ ] Implement integration tests for all processing modes -- [ ] Test pattern matching with complex scenarios -- [ ] Create performance tests for high-volume scenarios -- [ ] Build detailed documentation and examples +- [x] Create comprehensive unit tests for all components +- [x] Implement integration tests for all processing modes +- [x] Test pattern matching with complex scenarios +- [x] Create performance tests for high-volume scenarios +- [x] Build detailed documentation and examples +- [x] Identify and document legacy components to be deprecated (EmailDomainRouter) +- [x] Remove deprecated components (EmailDomainRouter) ## 6. Technical Requirements @@ -1194,6 +1196,8 @@ const dcRouter = new DcRouter({ - [x] Allow components to coexist for flexible deployment options - [x] Provide documentation with comments for migrating existing deployments - [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 - [x] Maintain support for basic proxy functionality @@ -1235,22 +1239,22 @@ const dcRouter = new DcRouter({ ## 11. Documentation Requirements ### 11.1 Code Documentation -- [ ] Comprehensive JSDoc comments for all classes and methods -- [ ] Interface definitions with detailed parameter descriptions -- [ ] Example code snippets for common operations -- [ ] Architecture documentation with component diagrams -- [ ] Decision logs for key design choices +- [x] Comprehensive JSDoc comments for all classes and methods +- [x] Interface definitions with detailed parameter descriptions +- [x] Example code snippets for common operations +- [x] Architecture documentation with component diagrams +- [x] Decision logs for key design choices ### 11.2 User Documentation -- [ ] Getting started guide with configuration approach selection guidance -- [ ] Complete configuration reference for both approaches -- [ ] Deployment scenarios and examples -- [ ] Troubleshooting guide -- [ ] Performance tuning recommendations -- [ ] Security best practices +- [x] Getting started guide with configuration approach selection guidance +- [x] Complete configuration reference for both approaches +- [x] Deployment scenarios and examples +- [x] Troubleshooting guide +- [x] Performance tuning recommendations +- [x] Security best practices ### 11.3 Direct SmartProxy Configuration Guide -- [ ] Detailed guide on using SmartProxy's domain configuration capabilities -- [ ] Examples of complex routing scenarios with SmartProxy -- [ ] Performance optimization tips for SmartProxy configurations -- [ ] Security settings for SmartProxy deployments \ No newline at end of file +- [x] Detailed guide on using SmartProxy's domain configuration capabilities +- [x] Examples of complex routing scenarios with SmartProxy +- [x] Performance optimization tips for SmartProxy configurations +- [x] Security settings for SmartProxy deployments \ No newline at end of file diff --git a/test/test.bouncemanager.ts b/test/test.bouncemanager.ts index 67a4705..099cd33 100644 --- a/test/test.bouncemanager.ts +++ b/test/test.bouncemanager.ts @@ -1,7 +1,7 @@ import { tap, expect } from '@push.rocks/tapbundle'; import * as plugins from '../ts/plugins.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 diff --git a/test/test.contentscanner.ts b/test/test.contentscanner.ts index 9affe7d..21d59ef 100644 --- a/test/test.contentscanner.ts +++ b/test/test.contentscanner.ts @@ -1,6 +1,6 @@ import { tap, expect } from '@push.rocks/tapbundle'; 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 tap.test('ContentScanner - should be instantiable', async () => { diff --git a/test/test.emailauth.ts b/test/test.emailauth.ts index c353877..e6c2f66 100644 --- a/test/test.emailauth.ts +++ b/test/test.emailauth.ts @@ -1,8 +1,8 @@ import { tap, expect } from '@push.rocks/tapbundle'; import { SzPlatformService } from '../ts/platformservice.js'; -import { SpfVerifier, SpfQualifier, SpfMechanismType } from '../ts/mta/classes.spfverifier.js'; -import { DmarcVerifier, DmarcPolicy, DmarcAlignment } from '../ts/mta/classes.dmarcverifier.js'; -import { Email } from '../ts/mta/classes.email.js'; +import { SpfVerifier, SpfQualifier, SpfMechanismType } from '../ts/mail/security/classes.spfverifier.js'; +import { DmarcVerifier, DmarcPolicy, DmarcAlignment } from '../ts/mail/security/classes.dmarcverifier.js'; +import { Email } from '../ts/mail/core/classes.email.js'; /** * Test email authentication systems: SPF and DMARC diff --git a/test/test.integration.ts b/test/test.integration.ts index b05e0ce..168673f 100644 --- a/test/test.integration.ts +++ b/test/test.integration.ts @@ -1,10 +1,10 @@ import { tap, expect } from '@push.rocks/tapbundle'; import * as plugins from '../ts/plugins.js'; import { SzPlatformService } from '../ts/platformservice.js'; -import { MtaService } from '../ts/mta/classes.mta.js'; -import { EmailService } from '../ts/email/classes.emailservice.js'; -import { BounceManager } from '../ts/email/classes.bouncemanager.js'; -import DcRouter from '../ts/dcrouter/classes.dcrouter.js'; +import { MtaService } from '../ts/mail/delivery/classes.mta.js'; +import { EmailService } from '../ts/mail/services/classes.emailservice.js'; +import { BounceManager } from '../ts/mail/core/classes.bouncemanager.js'; +import DcRouter from '../ts/classes.dcrouter.js'; // Test the new integration architecture tap.test('should be able to create an independent MTA service', async (tools) => { diff --git a/test/test.smartmail.ts b/test/test.smartmail.ts index e0fa533..c80a92f 100644 --- a/test/test.smartmail.ts +++ b/test/test.smartmail.ts @@ -3,9 +3,9 @@ import * as plugins from '../ts/plugins.js'; import * as paths from '../ts/paths.js'; // Import the components we want to test -import { EmailValidator } from '../ts/email/classes.emailvalidator.js'; -import { TemplateManager } from '../ts/email/classes.templatemanager.js'; -import { Email } from '../ts/mta/classes.email.js'; +import { EmailValidator } from '../ts/mail/core/classes.emailvalidator.js'; +import { TemplateManager } from '../ts/mail/core/classes.templatemanager.js'; +import { Email } from '../ts/mail/core/classes.email.js'; // Ensure test directories exist paths.ensureDirectories(); diff --git a/ts/dcrouter/classes.dcrouter.ts b/ts/classes.dcrouter.ts similarity index 95% rename from ts/dcrouter/classes.dcrouter.ts rename to ts/classes.dcrouter.ts index b0c11fe..7f0cec1 100644 --- a/ts/dcrouter/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -1,18 +1,17 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; +import * as plugins from './plugins.js'; +import * as paths from './paths.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 // Import the consolidated email config -import type { IEmailConfig, IDomainRule } from './classes.email.config.js'; -import { DomainRouter } from './classes.domain.router.js'; -import { UnifiedEmailServer } from './classes.unified.email.server.js'; -import { UnifiedDeliveryQueue, type IQueueOptions } from './classes.delivery.queue.js'; -import { MultiModeDeliverySystem, type IMultiModeDeliveryOptions } from './classes.delivery.system.js'; -import { UnifiedRateLimiter, type IHierarchicalRateLimits } from './classes.rate.limiter.js'; -import { logger } from '../logger.js'; +import type { IEmailConfig, IDomainRule } from './mail/routing/classes.email.config.js'; +import { DomainRouter } from './mail/routing/classes.domain.router.js'; +import { UnifiedEmailServer } from './mail/routing/classes.unified.email.server.js'; +import { UnifiedDeliveryQueue, type IQueueOptions } from './mail/delivery/classes.delivery.queue.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 { logger } from './logger.js'; export interface IDcRouterOptions { /** diff --git a/ts/dcrouter/classes.smtp.portconfig.ts b/ts/classes.smtp.portconfig.ts similarity index 99% rename from ts/dcrouter/classes.smtp.portconfig.ts rename to ts/classes.smtp.portconfig.ts index fcfa433..3ce7a75 100644 --- a/ts/dcrouter/classes.smtp.portconfig.ts +++ b/ts/classes.smtp.portconfig.ts @@ -1,4 +1,4 @@ -import * as plugins from '../plugins.js'; +import * as plugins from './plugins.js'; /** * Configuration options for TLS in SMTP connections diff --git a/ts/dcrouter/classes.dcr.sz.connector.ts b/ts/dcrouter/classes.dcr.sz.connector.ts deleted file mode 100644 index 0a0610b..0000000 --- a/ts/dcrouter/classes.dcr.sz.connector.ts +++ /dev/null @@ -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 { - return this.qenv.getEnvVarOnDemand(varName) || ''; - } -} \ No newline at end of file diff --git a/ts/dcrouter/classes.email.domainrouter.ts b/ts/dcrouter/classes.email.domainrouter.ts deleted file mode 100644 index 16011da..0000000 --- a/ts/dcrouter/classes.email.domainrouter.ts +++ /dev/null @@ -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 = 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; - } -} \ No newline at end of file diff --git a/ts/dcrouter/index.ts b/ts/dcrouter/index.ts deleted file mode 100644 index 3df014a..0000000 --- a/ts/dcrouter/index.ts +++ /dev/null @@ -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'; diff --git a/ts/email/index.ts b/ts/email/index.ts deleted file mode 100644 index 6425060..0000000 --- a/ts/email/index.ts +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/ts/index.ts b/ts/index.ts index c1fcedc..a6cdac0 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,4 +1,5 @@ export * from './00_commitinfo_data.js'; import { SzPlatformService } from './platformservice.js'; +export * from './mail/index.js'; export const runCli = async () => {} \ No newline at end of file diff --git a/ts/email/classes.bouncemanager.ts b/ts/mail/core/classes.bouncemanager.ts similarity index 99% rename from ts/email/classes.bouncemanager.ts rename to ts/mail/core/classes.bouncemanager.ts index 4ad77e3..f6e7cf7 100644 --- a/ts/email/classes.bouncemanager.ts +++ b/ts/mail/core/classes.bouncemanager.ts @@ -1,7 +1,7 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import { logger } from '../logger.js'; -import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import { logger } from '../../logger.js'; +import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; import { LRUCache } from 'lru-cache'; /** diff --git a/ts/mta/classes.email.ts b/ts/mail/core/classes.email.ts similarity index 95% rename from ts/mta/classes.email.ts rename to ts/mail/core/classes.email.ts index a1f969e..cfe7f3c 100644 --- a/ts/mta/classes.email.ts +++ b/ts/mail/core/classes.email.ts @@ -1,5 +1,5 @@ -import * as plugins from '../plugins.js'; -import { EmailValidator } from '../email/classes.emailvalidator.js'; +import * as plugins from '../../plugins.js'; +import { EmailValidator } from './classes.emailvalidator.js'; export interface IAttachment { filename: string; @@ -593,6 +593,38 @@ export class Email { return result; } + /** + * Convert to simple Smartmail-compatible object (for backward compatibility) + * @returns A Promise with a simple Smartmail-compatible object + */ + public async toSmartmailBasic(): Promise { + // 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 * @param smartmail The Smartmail instance to convert diff --git a/ts/email/classes.emailvalidator.ts b/ts/mail/core/classes.emailvalidator.ts similarity index 98% rename from ts/email/classes.emailvalidator.ts rename to ts/mail/core/classes.emailvalidator.ts index c3bc31a..26309a5 100644 --- a/ts/email/classes.emailvalidator.ts +++ b/ts/mail/core/classes.emailvalidator.ts @@ -1,5 +1,5 @@ -import * as plugins from '../plugins.js'; -import { logger } from '../logger.js'; +import * as plugins from '../../plugins.js'; +import { logger } from '../../logger.js'; import { LRUCache } from 'lru-cache'; export interface IEmailValidationResult { diff --git a/ts/email/classes.rulemanager.ts b/ts/mail/core/classes.rulemanager.ts similarity index 97% rename from ts/email/classes.rulemanager.ts rename to ts/mail/core/classes.rulemanager.ts index 3c786ac..a9416f8 100644 --- a/ts/email/classes.rulemanager.ts +++ b/ts/mail/core/classes.rulemanager.ts @@ -1,6 +1,6 @@ -import * as plugins from '../plugins.js'; -import { EmailService } from './classes.emailservice.js'; -import { logger } from '../logger.js'; +import * as plugins from '../../plugins.js'; +import { EmailService } from '../services/classes.emailservice.js'; +import { logger } from '../../logger.js'; export class RuleManager { public emailRef: EmailService; diff --git a/ts/email/classes.templatemanager.ts b/ts/mail/core/classes.templatemanager.ts similarity index 98% rename from ts/email/classes.templatemanager.ts rename to ts/mail/core/classes.templatemanager.ts index 4bbd607..98acfe6 100644 --- a/ts/email/classes.templatemanager.ts +++ b/ts/mail/core/classes.templatemanager.ts @@ -1,6 +1,6 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import { logger } from '../logger.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import { logger } from '../../logger.js'; /** * Email template type definition diff --git a/ts/mail/core/index.ts b/ts/mail/core/index.ts new file mode 100644 index 0000000..e0dfafa --- /dev/null +++ b/ts/mail/core/index.ts @@ -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'; \ No newline at end of file diff --git a/ts/email/classes.connector.mta.ts b/ts/mail/delivery/classes.connector.mta.ts similarity index 95% rename from ts/email/classes.connector.mta.ts rename to ts/mail/delivery/classes.connector.mta.ts index 49d01fd..1f993c1 100644 --- a/ts/email/classes.connector.mta.ts +++ b/ts/mail/delivery/classes.connector.mta.ts @@ -1,15 +1,41 @@ -import * as plugins from '../plugins.js'; -import { EmailService } from './classes.emailservice.js'; -import { logger } from '../logger.js'; +import * as plugins from '../../plugins.js'; +import { EmailService } from '../services/classes.emailservice.js'; +import { logger } from '../../logger.js'; // Import MTA classes -import { - MtaService, - Email as MtaEmail, - type IEmailOptions, - DeliveryStatus, - type IAttachment -} from '../mta/index.js'; +import { MtaService } from './classes.mta.js'; +import { Email as MtaEmail } from '../core/classes.email.js'; + +// Import Email types +export interface IEmailOptions { + from: string; + 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 { public emailRef: EmailService; diff --git a/ts/dcrouter/classes.delivery.queue.ts b/ts/mail/delivery/classes.delivery.queue.ts similarity index 98% rename from ts/dcrouter/classes.delivery.queue.ts rename to ts/mail/delivery/classes.delivery.queue.ts index e9e3547..71af2f1 100644 --- a/ts/dcrouter/classes.delivery.queue.ts +++ b/ts/mail/delivery/classes.delivery.queue.ts @@ -1,9 +1,9 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import { EventEmitter } from 'node:events'; import * as fs from 'node:fs'; import * as path from 'node:path'; -import { logger } from '../logger.js'; -import { type EmailProcessingMode, type IDomainRule } from './classes.email.config.js'; +import { logger } from '../../logger.js'; +import { type EmailProcessingMode, type IDomainRule } from '../routing/classes.email.config.js'; /** * Queue item status diff --git a/ts/dcrouter/classes.delivery.system.ts b/ts/mail/delivery/classes.delivery.system.ts similarity index 98% rename from ts/dcrouter/classes.delivery.system.ts rename to ts/mail/delivery/classes.delivery.system.ts index 926d5d0..33db4ff 100644 --- a/ts/dcrouter/classes.delivery.system.ts +++ b/ts/mail/delivery/classes.delivery.system.ts @@ -1,16 +1,16 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import { EventEmitter } from 'node:events'; import * as net from 'node:net'; import * as tls from 'node:tls'; -import { logger } from '../logger.js'; +import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType -} from '../security/index.js'; +} from '../../security/index.js'; import { UnifiedDeliveryQueue, type IQueueItem } from './classes.delivery.queue.js'; -import type { Email } from '../mta/classes.email.js'; -import type { IDomainRule } from './classes.email.config.js'; +import type { Email } from '../core/classes.email.js'; +import type { IDomainRule } from '../routing/classes.email.config.js'; /** * Delivery handler interface @@ -331,7 +331,7 @@ export class MultiModeDeliverySystem extends EventEmitter { }, success: true }); - } catch (error) { + } catch (error: any) { // Calculate delivery attempt time even for failures 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 await this.smtpCommand(socket, `EHLO ${rule.mtaOptions?.domain || 'localhost'}`); @@ -445,7 +442,7 @@ export class MultiModeDeliverySystem extends EventEmitter { // Complete the SMTP exchange return this.completeSMTPExchange(socket, email, rule); - } catch (error) { + } catch (error: any) { logger.log('error', `Failed to forward email: ${error.message}`); // Close the connection @@ -511,10 +508,6 @@ export class MultiModeDeliverySystem extends EventEmitter { // Close the connection socket.destroy(); - throw error; - } - socket.destroy(); - throw error; } } @@ -554,7 +547,7 @@ export class MultiModeDeliverySystem extends EventEmitter { subject: email.subject, dkimSigned: !!rule.mtaOptions?.dkimSign }; - } catch (error) { + } catch (error: any) { logger.log('error', `Failed to process email in MTA mode: ${error.message}`); throw error; } @@ -633,7 +626,7 @@ export class MultiModeDeliverySystem extends EventEmitter { scanned: !!rule.contentScanning, transformed: !!(rule.transformations && rule.transformations.length > 0) }; - } catch (error) { + } catch (error: any) { logger.log('error', `Failed to process email: ${error.message}`); throw error; } @@ -658,7 +651,7 @@ export class MultiModeDeliverySystem extends EventEmitter { // Add headers 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`; // Add additional headers diff --git a/ts/mta/classes.emailsendjob.ts b/ts/mail/delivery/classes.emailsendjob.ts similarity index 99% rename from ts/mta/classes.emailsendjob.ts rename to ts/mail/delivery/classes.emailsendjob.ts index 827c527..a3ebf39 100644 --- a/ts/mta/classes.emailsendjob.ts +++ b/ts/mail/delivery/classes.emailsendjob.ts @@ -1,6 +1,6 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import { Email } from './classes.email.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import { Email } from '../core/classes.email.js'; import { EmailSignJob } from './classes.emailsignjob.js'; import type { MtaService } from './classes.mta.js'; diff --git a/ts/mta/classes.emailsignjob.ts b/ts/mail/delivery/classes.emailsignjob.ts similarity index 97% rename from ts/mta/classes.emailsignjob.ts rename to ts/mail/delivery/classes.emailsignjob.ts index aed1d28..14cef47 100644 --- a/ts/mta/classes.emailsignjob.ts +++ b/ts/mail/delivery/classes.emailsignjob.ts @@ -1,4 +1,4 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import type { MtaService } from './classes.mta.js'; interface Headers { diff --git a/ts/mta/classes.mta.ts b/ts/mail/delivery/classes.mta.ts similarity index 96% rename from ts/mta/classes.mta.ts rename to ts/mail/delivery/classes.mta.ts index ecf94d1..23a3351 100644 --- a/ts/mta/classes.mta.ts +++ b/ts/mail/delivery/classes.mta.ts @@ -1,20 +1,20 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; +import * as plugins from '../../plugins.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 { DKIMCreator } from './classes.dkimcreator.js'; -import { DKIMVerifier } from './classes.dkimverifier.js'; -import { SpfVerifier } from './classes.spfverifier.js'; -import { DmarcVerifier } from './classes.dmarcverifier.js'; +import { DKIMCreator } from '../security/classes.dkimcreator.js'; +import { DKIMVerifier } from '../security/classes.dkimverifier.js'; +import { SpfVerifier } from '../security/classes.spfverifier.js'; +import { DmarcVerifier } from '../security/classes.dmarcverifier.js'; import { SMTPServer, type ISmtpServerOptions } from './classes.smtpserver.js'; -import { DNSManager } from './classes.dnsmanager.js'; -import { ApiManager } from './classes.apimanager.js'; +import { DNSManager } from '../routing/classes.dnsmanager.js'; +import { ApiManager } from '../services/classes.apimanager.js'; import { RateLimiter, type IRateLimitConfig } from './classes.ratelimiter.js'; -import { ContentScanner } from '../security/classes.contentscanner.js'; -import { IPWarmupManager } from '../deliverability/classes.ipwarmupmanager.js'; -import { SenderReputationMonitor } from '../deliverability/classes.senderreputationmonitor.js'; -import type { SzPlatformService } from '../platformservice.js'; +import { ContentScanner } from '../../security/classes.contentscanner.js'; +import { IPWarmupManager } from '../../deliverability/classes.ipwarmupmanager.js'; +import { SenderReputationMonitor } from '../../deliverability/classes.senderreputationmonitor.js'; +import type { SzPlatformService } from '../../platformservice.js'; /** * Configuration options for the MTA service @@ -265,7 +265,7 @@ export class MtaService { this.dkimCreator = new DKIMCreator(this); this.dkimVerifier = new DKIMVerifier(this); this.dnsManager = new DNSManager(this); - this.apiManager = new ApiManager(); + // Initialize API manager later in start() method when emailService is available // Initialize authentication verifiers this.spfVerifier = new SpfVerifier(this); @@ -283,14 +283,15 @@ export class MtaService { burstTokens: 5 // Allow small bursts }); - // Initialize IP warmup manager - const warmupConfig = this.config.outbound?.warmup; - this.ipWarmupManager = IPWarmupManager.getInstance({ - enabled: warmupConfig?.enabled || false, - ipAddresses: warmupConfig?.ipAddresses || [], - targetDomains: warmupConfig?.targetDomains || [], - fallbackPercentage: warmupConfig?.fallbackPercentage || 50 - }); + // Initialize IP warmup manager with explicit config + const warmupConfig = this.config.outbound?.warmup || {}; + const ipWarmupConfig = { + enabled: warmupConfig.enabled || false, + ipAddresses: warmupConfig.ipAddresses || [], + targetDomains: warmupConfig.targetDomains || [], + fallbackPercentage: warmupConfig.fallbackPercentage || 50 + }; + this.ipWarmupManager = IPWarmupManager.getInstance(ipWarmupConfig); // Set active allocation policy if specified if (warmupConfig?.allocationPolicy) { @@ -432,6 +433,9 @@ export class MtaService { try { console.log('Starting MTA service...'); + // Initialize API manager now that emailService is available + this.apiManager = new ApiManager(this.emailService); + // Load or provision certificate await this.loadOrProvisionCertificate(); @@ -755,7 +759,7 @@ export class MtaService { console.log(`Processing bounce notification from ${email.from}`); // 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 (this.emailService?.bounceManager) { diff --git a/ts/mta/classes.ratelimiter.ts b/ts/mail/delivery/classes.ratelimiter.ts similarity index 99% rename from ts/mta/classes.ratelimiter.ts rename to ts/mail/delivery/classes.ratelimiter.ts index 9bf22e8..a7b0e1e 100644 --- a/ts/mta/classes.ratelimiter.ts +++ b/ts/mail/delivery/classes.ratelimiter.ts @@ -1,4 +1,4 @@ -import { logger } from '../logger.js'; +import { logger } from '../../logger.js'; /** * Configuration options for rate limiter diff --git a/ts/mta/classes.smtpserver.ts b/ts/mail/delivery/classes.smtpserver.ts similarity index 99% rename from ts/mta/classes.smtpserver.ts rename to ts/mail/delivery/classes.smtpserver.ts index 6403d7f..27e4516 100644 --- a/ts/mta/classes.smtpserver.ts +++ b/ts/mail/delivery/classes.smtpserver.ts @@ -1,15 +1,15 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import { Email } from './classes.email.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import { Email } from '../core/classes.email.js'; import type { MtaService } from './classes.mta.js'; -import { logger } from '../logger.js'; +import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType, IPReputationChecker, ReputationThreshold -} from '../security/index.js'; +} from '../../security/index.js'; export interface ISmtpServerOptions { port: number; diff --git a/ts/dcrouter/classes.rate.limiter.ts b/ts/mail/delivery/classes.unified.rate.limiter.ts similarity index 99% rename from ts/dcrouter/classes.rate.limiter.ts rename to ts/mail/delivery/classes.unified.rate.limiter.ts index 6b2cdf6..d80fe34 100644 --- a/ts/dcrouter/classes.rate.limiter.ts +++ b/ts/mail/delivery/classes.unified.rate.limiter.ts @@ -1,7 +1,7 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import { EventEmitter } from 'node:events'; -import { logger } from '../logger.js'; -import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; +import { logger } from '../../logger.js'; +import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; /** * Interface for rate limit configuration diff --git a/ts/mail/delivery/index.ts b/ts/mail/delivery/index.ts new file mode 100644 index 0000000..15f96e8 --- /dev/null +++ b/ts/mail/delivery/index.ts @@ -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'; \ No newline at end of file diff --git a/ts/mail/index.ts b/ts/mail/index.ts new file mode 100644 index 0000000..906f656 --- /dev/null +++ b/ts/mail/index.ts @@ -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 +}; \ No newline at end of file diff --git a/ts/mta/classes.dnsmanager.ts b/ts/mail/routing/classes.dnsmanager.ts similarity index 99% rename from ts/mta/classes.dnsmanager.ts rename to ts/mail/routing/classes.dnsmanager.ts index 196d756..fe987a7 100644 --- a/ts/mta/classes.dnsmanager.ts +++ b/ts/mail/routing/classes.dnsmanager.ts @@ -1,6 +1,6 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import type { MtaService } from './classes.mta.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import type { MtaService } from '../delivery/classes.mta.js'; /** * Interface for DNS record information diff --git a/ts/dcrouter/classes.domain.router.ts b/ts/mail/routing/classes.domain.router.ts similarity index 99% rename from ts/dcrouter/classes.domain.router.ts rename to ts/mail/routing/classes.domain.router.ts index 8ea5311..048206c 100644 --- a/ts/dcrouter/classes.domain.router.ts +++ b/ts/mail/routing/classes.domain.router.ts @@ -1,4 +1,4 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import { EventEmitter } from 'node:events'; import { type IDomainRule, type EmailProcessingMode } from './classes.email.config.js'; diff --git a/ts/dcrouter/classes.email.config.ts b/ts/mail/routing/classes.email.config.ts similarity index 98% rename from ts/dcrouter/classes.email.config.ts rename to ts/mail/routing/classes.email.config.ts index 5c6337d..ace3637 100644 --- a/ts/dcrouter/classes.email.config.ts +++ b/ts/mail/routing/classes.email.config.ts @@ -1,4 +1,4 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; /** * Email processing modes diff --git a/ts/dcrouter/classes.unified.email.server.ts b/ts/mail/routing/classes.unified.email.server.ts similarity index 99% rename from ts/dcrouter/classes.unified.email.server.ts rename to ts/mail/routing/classes.unified.email.server.ts index 533b7b7..05fc84c 100644 --- a/ts/dcrouter/classes.unified.email.server.ts +++ b/ts/mail/routing/classes.unified.email.server.ts @@ -1,23 +1,23 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; import { EventEmitter } from 'events'; -import { logger } from '../logger.js'; +import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType -} from '../security/index.js'; +} from '../../security/index.js'; import { DomainRouter } from './classes.domain.router.js'; import type { IEmailConfig, EmailProcessingMode, IDomainRule } 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 tls from 'node:tls'; 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 diff --git a/ts/mail/routing/index.ts b/ts/mail/routing/index.ts new file mode 100644 index 0000000..0da980a --- /dev/null +++ b/ts/mail/routing/index.ts @@ -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'; \ No newline at end of file diff --git a/ts/mta/classes.dkimcreator.ts b/ts/mail/security/classes.dkimcreator.ts similarity index 95% rename from ts/mta/classes.dkimcreator.ts rename to ts/mail/security/classes.dkimcreator.ts index bde399f..819df83 100644 --- a/ts/mta/classes.dkimcreator.ts +++ b/ts/mail/security/classes.dkimcreator.ts @@ -1,8 +1,8 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; -import { Email } from './classes.email.js'; -import type { MtaService } from './classes.mta.js'; +import { Email } from '../core/classes.email.js'; +import type { MtaService } from '../delivery/classes.mta.js'; const readFile = plugins.util.promisify(plugins.fs.readFile); const writeFile = plugins.util.promisify(plugins.fs.writeFile); diff --git a/ts/mta/classes.dkimverifier.ts b/ts/mail/security/classes.dkimverifier.ts similarity index 98% rename from ts/mta/classes.dkimverifier.ts rename to ts/mail/security/classes.dkimverifier.ts index dfc0244..f40f045 100644 --- a/ts/mta/classes.dkimverifier.ts +++ b/ts/mail/security/classes.dkimverifier.ts @@ -1,7 +1,7 @@ -import * as plugins from '../plugins.js'; -import { MtaService } from './classes.mta.js'; -import { logger } from '../logger.js'; -import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; +import * as plugins from '../../plugins.js'; +import { MtaService } from '../delivery/classes.mta.js'; +import { logger } from '../../logger.js'; +import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; /** * Result of a DKIM verification diff --git a/ts/mta/classes.dmarcverifier.ts b/ts/mail/security/classes.dmarcverifier.ts similarity index 97% rename from ts/mta/classes.dmarcverifier.ts rename to ts/mail/security/classes.dmarcverifier.ts index ccf9359..2edee5b 100644 --- a/ts/mta/classes.dmarcverifier.ts +++ b/ts/mail/security/classes.dmarcverifier.ts @@ -1,9 +1,9 @@ -import * as plugins from '../plugins.js'; -import { logger } from '../logger.js'; -import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; -import type { MtaService } from './classes.mta.js'; -import type { Email } from './classes.email.js'; -import type { IDnsVerificationResult } from './classes.dnsmanager.js'; +import * as plugins from '../../plugins.js'; +import { logger } from '../../logger.js'; +import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; +import type { MtaService } from '../delivery/classes.mta.js'; +import type { Email } from '../core/classes.email.js'; +import type { IDnsVerificationResult } from '../routing/classes.dnsmanager.js'; /** * DMARC policy types diff --git a/ts/mta/classes.spfverifier.ts b/ts/mail/security/classes.spfverifier.ts similarity index 98% rename from ts/mta/classes.spfverifier.ts rename to ts/mail/security/classes.spfverifier.ts index 9ea509c..6838f68 100644 --- a/ts/mta/classes.spfverifier.ts +++ b/ts/mail/security/classes.spfverifier.ts @@ -1,9 +1,9 @@ -import * as plugins from '../plugins.js'; -import { logger } from '../logger.js'; -import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../security/index.js'; -import type { MtaService } from './classes.mta.js'; -import type { Email } from './classes.email.js'; -import type { IDnsVerificationResult } from './classes.dnsmanager.js'; +import * as plugins from '../../plugins.js'; +import { logger } from '../../logger.js'; +import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; +import type { MtaService } from '../delivery/classes.mta.js'; +import type { Email } from '../core/classes.email.js'; +import type { IDnsVerificationResult } from '../routing/classes.dnsmanager.js'; /** * SPF result qualifiers diff --git a/ts/mail/security/index.ts b/ts/mail/security/index.ts new file mode 100644 index 0000000..a6dcee8 --- /dev/null +++ b/ts/mail/security/index.ts @@ -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'; \ No newline at end of file diff --git a/ts/email/classes.apimanager.ts b/ts/mail/services/classes.apimanager.ts similarity index 96% rename from ts/email/classes.apimanager.ts rename to ts/mail/services/classes.apimanager.ts index d4f2c6d..9fe1231 100644 --- a/ts/email/classes.apimanager.ts +++ b/ts/mail/services/classes.apimanager.ts @@ -1,6 +1,6 @@ -import * as plugins from '../plugins.js'; +import * as plugins from '../../plugins.js'; import { EmailService } from './classes.emailservice.js'; -import { logger } from '../logger.js'; +import { logger } from '../../logger.js'; export class ApiManager { public emailRef: EmailService; diff --git a/ts/email/classes.emailservice.ts b/ts/mail/services/classes.emailservice.ts similarity index 89% rename from ts/email/classes.emailservice.ts rename to ts/mail/services/classes.emailservice.ts index 0cc4704..1c91ad0 100644 --- a/ts/email/classes.emailservice.ts +++ b/ts/mail/services/classes.emailservice.ts @@ -1,16 +1,16 @@ -import * as plugins from '../plugins.js'; -import * as paths from '../paths.js'; -import { MtaConnector } from './classes.connector.mta.js'; -import { RuleManager } from './classes.rulemanager.js'; +import * as plugins from '../../plugins.js'; +import * as paths from '../../paths.js'; +import { MtaConnector } from '../delivery/classes.connector.mta.js'; +import { RuleManager } from '../core/classes.rulemanager.js'; import { ApiManager } from './classes.apimanager.js'; -import { TemplateManager } from './classes.templatemanager.js'; -import { EmailValidator } from './classes.emailvalidator.js'; -import { BounceManager } from './classes.bouncemanager.js'; -import { logger } from '../logger.js'; -import type { SzPlatformService } from '../platformservice.js'; +import { TemplateManager } from '../core/classes.templatemanager.js'; +import { EmailValidator } from '../core/classes.emailvalidator.js'; +import { BounceManager } from '../core/classes.bouncemanager.js'; +import { logger } from '../../logger.js'; +import type { SzPlatformService } from '../../platformservice.js'; // Import MTA service -import { MtaService, type IMtaConfig } from '../mta/index.js'; +import { MtaService, type IMtaConfig } from '../delivery/classes.mta.js'; export interface IEmailConstructorOptions { useMta?: boolean; diff --git a/ts/mail/services/index.ts b/ts/mail/services/index.ts new file mode 100644 index 0000000..4a059f6 --- /dev/null +++ b/ts/mail/services/index.ts @@ -0,0 +1,3 @@ +// Email services +export * from './classes.emailservice.js'; +export * from './classes.apimanager.js'; \ No newline at end of file diff --git a/ts/mta/classes.apimanager.ts b/ts/mta/classes.apimanager.ts deleted file mode 100644 index 40c26f4..0000000 --- a/ts/mta/classes.apimanager.ts +++ /dev/null @@ -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; - /** 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; - /** 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 = { - '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; - }> = 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): void { - this.authOptions = { - ...this.authOptions, - ...options - }; - } - - /** - * Handle HTTP request - */ - private async handleRequest(req: any, res: any): Promise { - 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((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 { - const params: Record = {}; - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - // 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 { - 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'); - } - } -} \ No newline at end of file diff --git a/ts/mta/index.ts b/ts/mta/index.ts deleted file mode 100644 index febf48f..0000000 --- a/ts/mta/index.ts +++ /dev/null @@ -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'; diff --git a/ts/platformservice.ts b/ts/platformservice.ts index ca30eff..acb5ac9 100644 --- a/ts/platformservice.ts +++ b/ts/platformservice.ts @@ -1,9 +1,9 @@ import * as plugins from './plugins.js'; import * as paths from './paths.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 { MtaService } from './mta/classes.mta.js'; +import { MtaService } from './mail/delivery/classes.mta.js'; export class SzPlatformService { public projectinfo: plugins.projectinfo.ProjectInfo; diff --git a/ts/security/classes.contentscanner.ts b/ts/security/classes.contentscanner.ts index 86f0ee7..9a4c5aa 100644 --- a/ts/security/classes.contentscanner.ts +++ b/ts/security/classes.contentscanner.ts @@ -1,8 +1,8 @@ import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { logger } from '../logger.js'; -import { Email } from '../mta/classes.email.js'; -import type { IAttachment } from '../mta/classes.email.js'; +import { Email } from '../mail/core/classes.email.js'; +import type { IAttachment } from '../mail/core/classes.email.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js'; import { LRUCache } from 'lru-cache';