Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
cb33dd26d0 | |||
d3d197d9d3 | |||
0e914a3366 | |||
747478f0f9 | |||
b61de33ee0 | |||
970c0d5c60 | |||
fe2069c48e |
33
changelog.md
33
changelog.md
@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-08 - 2.8.4 - fix(mail)
|
||||
refactor(mail): Remove Mailgun references from PlatformService. Update keywords, error messages, and documentation to use MTA exclusively.
|
||||
|
||||
- Removed Mailgun integration from keywords in package.json and npmextra.json
|
||||
- Updated EmailService to remove Mailgun API key usage and reference MTA instead
|
||||
- Updated changelog.md and readme.md to reflect removal of Mailgun and update examples
|
||||
- Revised error messages to mention 'MTA not configured' instead of generic provider errors
|
||||
- Updated readme.plan.md to document Mailgun removal
|
||||
|
||||
## 2025-05-08 - 2.8.3 - refactor(mail): Remove Mailgun references
|
||||
Remove all Mailgun references from the codebase since it's no longer used as an email provider
|
||||
|
||||
- Removed "mailgun integration" from keywords in package.json and npmextra.json
|
||||
- Updated comments and documentation in EmailService to remove Mailgun mentions
|
||||
- Updated error messages to reference MTA instead of generic email providers
|
||||
- Updated the readme email example to use PlatformService reference instead of Mailgun API key
|
||||
|
||||
## 2025-05-08 - 2.8.2 - fix(tests)
|
||||
Fix outdated import paths in test files for dcrouter and ratelimiter modules
|
||||
|
||||
- Updated dcrouter import from '../ts/dcrouter/index.js' to '../ts/classes.dcrouter.js'
|
||||
- Updated ratelimiter import from '../ts/mta/classes.ratelimiter.js' to '../ts/mail/delivery/classes.ratelimiter.js'
|
||||
|
||||
## 2025-05-08 - 2.8.1 - fix(readme)
|
||||
Update readme with consolidated email system improvements and modular directory structure
|
||||
|
||||
Clarify that the platform now organizes email functionality into distinct directories (mail/core, mail/delivery, mail/routing, mail/security, mail/services) and update the diagram and key features list accordingly. Adjust code examples to reflect explicit module imports and the use of SzPlatformService.
|
||||
|
||||
- Changed description of consolidated email configuration to include 'streamlined directory structure'.
|
||||
- Updated mermaid diagram to show 'Mail System Structure' with separate components for core, delivery, routing, security, and services.
|
||||
- Modified key features list to document modular directory structure.
|
||||
- Revised code sample imports to use explicit paths and SzPlatformService.
|
||||
|
||||
## 2025-05-08 - 2.8.0 - feat(docs)
|
||||
Update documentation to include consolidated email handling and pattern‑based routing details
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
"mail parsing",
|
||||
"DKIM",
|
||||
"platform service",
|
||||
"mailgun integration",
|
||||
"letterXpress",
|
||||
"OpenAI",
|
||||
"Anthropic AI",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@serve.zone/platformservice",
|
||||
"private": true,
|
||||
"version": "2.8.0",
|
||||
"version": "2.8.4",
|
||||
"description": "A multifaceted platform service handling mail, SMS, letter delivery, and AI services.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
@ -61,7 +61,6 @@
|
||||
"mail parsing",
|
||||
"DKIM",
|
||||
"platform service",
|
||||
"mailgun integration",
|
||||
"letterXpress",
|
||||
"OpenAI",
|
||||
"Anthropic AI",
|
||||
|
39
readme.md
39
readme.md
@ -51,7 +51,7 @@ async function sendEmail() {
|
||||
body: '<h1>This is a test email</h1>',
|
||||
};
|
||||
|
||||
const emailService = new EmailService('MAILGUN_API_KEY'); // Replace with your real API key
|
||||
const emailService = new EmailService(platformService);
|
||||
await emailService.sendEmail(emailOptions);
|
||||
|
||||
console.log('Email sent successfully.');
|
||||
@ -106,7 +106,7 @@ sendLetter();
|
||||
### Mail Transfer Agent (MTA) and Consolidated Email Handling
|
||||
|
||||
The platform includes a robust Mail Transfer Agent (MTA) for enterprise-grade email handling with complete control over the email delivery process.
|
||||
Additionally, the platform now features a consolidated email configuration system with pattern-based routing:
|
||||
Additionally, the platform now features a consolidated email configuration system with pattern-based routing and a streamlined directory structure:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
@ -123,11 +123,13 @@ graph TD
|
||||
ApiManager[API Manager] --> DcRouter
|
||||
end
|
||||
|
||||
subgraph "MTA Service"
|
||||
MtaMode --> MtaService[MTA Service]
|
||||
MtaService --> EmailSendJob[Email Send Job]
|
||||
MtaService --> DnsManager[DNS Manager]
|
||||
MtaService --> DkimCreator[DKIM Creator]
|
||||
subgraph "Mail System Structure"
|
||||
MailCore[mail/core] --> EmailClasses[Email, TemplateManager, etc.]
|
||||
MailDelivery[mail/delivery] --> MtaService[MTA Service]
|
||||
MailDelivery --> EmailSendJob[Email Send Job]
|
||||
MailRouting[mail/routing] --> DnsManager[DNS Manager]
|
||||
MailSecurity[mail/security] --> AuthClasses[DKIM, SPF, DMARC]
|
||||
MailServices[mail/services] --> ServiceClasses[EmailService, ApiManager]
|
||||
end
|
||||
|
||||
subgraph "External Services"
|
||||
@ -140,6 +142,12 @@ graph TD
|
||||
#### Key Features
|
||||
|
||||
The email handling system provides:
|
||||
- **Modular Directory Structure**: Clean organization with clear separation of concerns:
|
||||
- **mail/core**: Core email models and basic functionality (Email, BounceManager, etc.)
|
||||
- **mail/delivery**: Email delivery mechanisms (MTA, SMTP server, rate limiting)
|
||||
- **mail/routing**: DNS and domain routing capabilities
|
||||
- **mail/security**: Authentication and security features (DKIM, SPF, DMARC)
|
||||
- **mail/services**: High-level services and API interfaces
|
||||
- **Pattern-based Routing**: Route emails based on glob patterns like `*@domain.com` or `*@*.domain.com`
|
||||
- **Multi-Modal Processing**: Handle different email domains with different processing modes:
|
||||
- **Forward Mode**: SMTP forwarding to other servers
|
||||
@ -249,12 +257,19 @@ setupEmailHandling();
|
||||
|
||||
#### Using the MTA Service Directly
|
||||
|
||||
You can still use the MTA service directly for more granular control:
|
||||
You can still use the MTA service directly for more granular control with our new modular directory structure:
|
||||
|
||||
```ts
|
||||
import { MtaService, Email } from '@serve.zone/platformservice';
|
||||
import { SzPlatformService } from '@serve.zone/platformservice';
|
||||
import { MtaService } from '@serve.zone/platformservice/mail/delivery';
|
||||
import { Email } from '@serve.zone/platformservice/mail/core';
|
||||
import { ApiManager } from '@serve.zone/platformservice/mail/services';
|
||||
|
||||
async function useMtaService() {
|
||||
// Initialize platform service
|
||||
const platformService = new SzPlatformService();
|
||||
await platformService.start();
|
||||
|
||||
// Initialize MTA service
|
||||
const mtaService = new MtaService(platformService);
|
||||
await mtaService.start();
|
||||
@ -277,7 +292,7 @@ async function useMtaService() {
|
||||
console.log(`Email status: ${status.status}`);
|
||||
|
||||
// Set up API for external access
|
||||
const apiManager = new ApiManager(mtaService);
|
||||
const apiManager = new ApiManager(platformService.emailService);
|
||||
await apiManager.start(3000);
|
||||
console.log('MTA API running on port 3000');
|
||||
}
|
||||
@ -311,7 +326,3 @@ async function useAiService() {
|
||||
|
||||
useAiService();
|
||||
```
|
||||
|
||||
### Conclusion
|
||||
|
||||
The `@serve.zone/platformservice` offers a robust set of features for modern application requirements, including but not limited to communication and AI services. By following the examples above, developers can integrate these services into their applications, harnessing the power of email, SMS, letters, MTA capabilities, and artificial intelligence seamlessly.
|
@ -1,3 +1,13 @@
|
||||
# PlatformService Roadmap
|
||||
|
||||
## Latest Changes
|
||||
|
||||
### Mailgun Removal
|
||||
- [x] Remove Mailgun integration from keywords in package.json and npmextra.json
|
||||
- [x] Update EmailService comments to remove mentions of Mailgun
|
||||
- [x] Update error messages to reference MTA instead of generic email providers
|
||||
- [x] Update the readme email example to use PlatformService reference instead of Mailgun API key
|
||||
|
||||
# DcRouter Consolidated Email Configuration Plan
|
||||
|
||||
## Overview
|
||||
@ -1022,11 +1032,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 +1206,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 +1249,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
|
||||
- [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
|
@ -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
|
||||
|
@ -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 () => {
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
type IEmailConfig,
|
||||
type EmailProcessingMode,
|
||||
type IDomainRule
|
||||
} from '../ts/dcrouter/index.js';
|
||||
} from '../ts/classes.dcrouter.js';
|
||||
|
||||
tap.test('DcRouter class - basic functionality', async () => {
|
||||
// Create a simple DcRouter instance
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { RateLimiter } from '../ts/mta/classes.ratelimiter.js';
|
||||
import { RateLimiter } from '../ts/mail/delivery/classes.ratelimiter.js';
|
||||
|
||||
tap.test('RateLimiter - should be instantiable', async () => {
|
||||
const limiter = new RateLimiter({
|
||||
|
@ -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();
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/platformservice',
|
||||
version: '2.8.0',
|
||||
version: '2.8.4',
|
||||
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
|
||||
}
|
||||
|
@ -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 {
|
||||
/**
|
@ -1,4 +1,4 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
/**
|
||||
* Configuration options for TLS in SMTP connections
|
@ -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) || '';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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';
|
@ -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
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
export * from './00_commitinfo_data.js';
|
||||
import { SzPlatformService } from './platformservice.js';
|
||||
export * from './mail/index.js';
|
||||
|
||||
export const runCli = async () => {}
|
@ -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';
|
||||
|
||||
/**
|
@ -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<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
|
||||
* @param smartmail The Smartmail instance to convert
|
@ -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 {
|
@ -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;
|
@ -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
|
6
ts/mail/core/index.ts
Normal file
6
ts/mail/core/index.ts
Normal 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';
|
@ -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;
|
@ -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
|
@ -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
|
@ -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';
|
||||
|
@ -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 {
|
@ -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) {
|
@ -1,4 +1,4 @@
|
||||
import { logger } from '../logger.js';
|
||||
import { logger } from '../../logger.js';
|
||||
|
||||
/**
|
||||
* Configuration options for rate limiter
|
@ -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;
|
@ -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
|
18
ts/mail/delivery/index.ts
Normal file
18
ts/mail/delivery/index.ts
Normal 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
29
ts/mail/index.ts
Normal 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
|
||||
};
|
@ -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
|
@ -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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as plugins from '../../plugins.js';
|
||||
|
||||
/**
|
||||
* Email processing modes
|
@ -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
|
5
ts/mail/routing/index.ts
Normal file
5
ts/mail/routing/index.ts
Normal 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';
|
@ -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);
|
@ -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
|
@ -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
|
@ -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
|
5
ts/mail/security/index.ts
Normal file
5
ts/mail/security/index.ts
Normal 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';
|
@ -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;
|
||||
@ -69,10 +69,10 @@ export class ApiManager {
|
||||
return status;
|
||||
}
|
||||
|
||||
// For Mailgun, we don't have a status check implementation currently
|
||||
// Status tracking not available if MTA is not configured
|
||||
return {
|
||||
status: 'unknown',
|
||||
details: { message: 'Status tracking not available for current provider' }
|
||||
details: { message: 'Status tracking not available without MTA configuration' }
|
||||
};
|
||||
})
|
||||
);
|
@ -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;
|
||||
@ -25,7 +25,7 @@ export interface IEmailConstructorOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Email service with support for both Mailgun and local MTA
|
||||
* Email service with MTA support
|
||||
*/
|
||||
export class EmailService {
|
||||
public platformServiceRef: SzPlatformService;
|
||||
@ -128,7 +128,7 @@ export class EmailService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email using the configured provider (Mailgun or MTA)
|
||||
* Send an email using the MTA
|
||||
* @param email The email to send
|
||||
* @param to Recipient(s)
|
||||
* @param options Additional options
|
||||
@ -142,7 +142,7 @@ export class EmailService {
|
||||
if (this.config.useMta && this.mtaConnector) {
|
||||
return this.mtaConnector.sendEmail(email, to, options);
|
||||
} else {
|
||||
throw new Error('No email provider configured');
|
||||
throw new Error('MTA not configured');
|
||||
}
|
||||
}
|
||||
|
3
ts/mail/services/index.ts
Normal file
3
ts/mail/services/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// Email services
|
||||
export * from './classes.emailservice.js';
|
||||
export * from './classes.apimanager.js';
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
@ -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';
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/platformservice',
|
||||
version: '2.8.0',
|
||||
version: '2.8.4',
|
||||
description: 'A multifaceted platform service handling mail, SMS, letter delivery, and AI services.'
|
||||
}
|
||||
|
Reference in New Issue
Block a user