@serve.zone/dcrouter 15.11.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +34 -34
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.dcrouter.d.ts +10 -3
- package/dist_ts/classes.dcrouter.js +39 -43
- package/dist_ts/db/documents/classes.cached.email.d.ts +32 -0
- package/dist_ts/db/documents/classes.cached.email.js +67 -3
- package/dist_ts/db/documents/classes.email-domain.doc.d.ts +2 -1
- package/dist_ts/db/documents/classes.email-domain.doc.js +8 -2
- package/dist_ts/db/documents/classes.email-server-settings.doc.js +2 -2
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.d.ts +3 -0
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.js +20 -2
- package/dist_ts/dns/classes.dns-server-runtime.js +3 -1
- package/dist_ts/email/classes.accepted-email-spool.d.ts +5 -1
- package/dist_ts/email/classes.accepted-email-spool.js +9 -1
- package/dist_ts/email/classes.email-domain.manager.d.ts +17 -1
- package/dist_ts/email/classes.email-domain.manager.js +100 -17
- package/dist_ts/email/classes.email-route-builder.js +5 -2
- package/dist_ts/email/classes.email-settings.manager.js +3 -3
- package/dist_ts/email/classes.mail-dns-sync.d.ts +27 -0
- package/dist_ts/email/classes.mail-dns-sync.js +196 -0
- package/dist_ts/email/classes.mail-egress-coordinator.d.ts +37 -0
- package/dist_ts/email/classes.mail-egress-coordinator.js +113 -0
- package/dist_ts/email/index.d.ts +2 -0
- package/dist_ts/email/index.js +3 -1
- package/dist_ts/opsserver/handlers/config.handler.js +4 -2
- package/dist_ts/opsserver/handlers/email-domain.handler.js +3 -1
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +20 -3
- package/dist_ts/opsserver/handlers/email-ops.handler.js +141 -19
- package/dist_ts/opsserver/handlers/email-settings.handler.js +11 -6
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +16 -7
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +33 -1
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +94 -2
- package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +18 -0
- package/dist_ts/remoteingress/classes.tunnel-manager.js +114 -12
- package/dist_ts/remoteingress/edge-derivation-routes.d.ts +10 -0
- package/dist_ts/remoteingress/edge-derivation-routes.js +28 -0
- package/dist_ts/remoteingress/index.d.ts +1 -0
- package/dist_ts/remoteingress/index.js +2 -1
- package/dist_ts_interfaces/data/email-domain.d.ts +11 -1
- package/dist_ts_interfaces/data/email-settings.d.ts +11 -0
- package/dist_ts_interfaces/data/remoteingress.d.ts +10 -0
- package/dist_ts_interfaces/data/remoteingress.js +5 -2
- package/dist_ts_interfaces/requests/email-domains.d.ts +5 -1
- package/dist_ts_interfaces/requests/email-ops.d.ts +4 -0
- package/dist_ts_interfaces/requests/remoteingress.d.ts +11 -0
- package/dist_ts_migrations/index.js +91 -9
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/package.json +4 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +41 -48
- package/ts/db/documents/classes.cached.email.ts +72 -1
- package/ts/db/documents/classes.email-domain.doc.ts +4 -0
- package/ts/db/documents/classes.email-server-settings.doc.ts +1 -1
- package/ts/db/documents/classes.remote-ingress-edge.doc.ts +9 -0
- package/ts/dns/classes.dns-server-runtime.ts +2 -0
- package/ts/email/classes.accepted-email-spool.ts +12 -1
- package/ts/email/classes.email-domain.manager.ts +118 -17
- package/ts/email/classes.email-route-builder.ts +4 -1
- package/ts/email/classes.email-settings.manager.ts +2 -2
- package/ts/email/classes.mail-dns-sync.ts +218 -0
- package/ts/email/classes.mail-egress-coordinator.ts +125 -0
- package/ts/email/index.ts +2 -0
- package/ts/opsserver/handlers/config.handler.ts +3 -1
- package/ts/opsserver/handlers/email-domain.handler.ts +2 -0
- package/ts/opsserver/handlers/email-ops.handler.ts +156 -18
- package/ts/opsserver/handlers/email-settings.handler.ts +10 -5
- package/ts/opsserver/handlers/remoteingress.handler.ts +15 -5
- package/ts/remoteingress/classes.remoteingress-manager.ts +111 -0
- package/ts/remoteingress/classes.tunnel-manager.ts +125 -10
- package/ts/remoteingress/edge-derivation-routes.ts +29 -0
- package/ts/remoteingress/index.ts +1 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -14,8 +14,6 @@ import {
|
|
|
14
14
|
type IExtendedSmtpSession,
|
|
15
15
|
type IMessageAcceptanceContext,
|
|
16
16
|
type IMessageAcceptanceDecision,
|
|
17
|
-
type IOutboundConnectionProxy,
|
|
18
|
-
type IOutboundConnectionProxyContext,
|
|
19
17
|
} from '@push.rocks/smartmta';
|
|
20
18
|
import { logger } from './logger.js';
|
|
21
19
|
import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
|
|
@@ -29,7 +27,7 @@ import { commitinfo } from './00_commitinfo_data.js';
|
|
|
29
27
|
import { OpsServer } from './opsserver/index.js';
|
|
30
28
|
import { MetricsManager } from './monitoring/index.js';
|
|
31
29
|
import { RadiusServer, type IRadiusServerConfig } from './radius/index.js';
|
|
32
|
-
import { RemoteIngressHubLifecycle, RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
|
|
30
|
+
import { RemoteIngressHubLifecycle, RemoteIngressManager, TunnelManager, buildEdgeOnlyDerivationRoutes } from './remoteingress/index.js';
|
|
33
31
|
import { VpnAccessResolver, VpnManager, type IVpnManagerConfig } from './vpn/index.js';
|
|
34
32
|
import { RouteConfigManager, ApiTokenManager, GatewayClientManager, ReferenceResolver, DbSeeder, TargetProfileManager, buildHttpRedirectRuntimeRoutes } from './config/index.js';
|
|
35
33
|
import type { TVpnClientAllowEntry } from './config/classes.route-config-manager.js';
|
|
@@ -39,9 +37,9 @@ import { DnsManager } from './dns/manager.dns.js';
|
|
|
39
37
|
import { DnsServerRuntime } from './dns/classes.dns-server-runtime.js';
|
|
40
38
|
import { AcmeConfigManager } from './acme/manager.acme-config.js';
|
|
41
39
|
import { SmartAcmeLifecycle } from './acme/classes.smartacme-lifecycle.js';
|
|
42
|
-
import { AcceptedEmailSpool, EmailDomainManager, EmailRouteBuilder, EmailSettingsManager, SmartMtaStorageManager, WorkAppMailManager, type TSmartMtaQueueItemLike } from './email/index.js';
|
|
40
|
+
import { AcceptedEmailSpool, EmailDomainManager, EmailRouteBuilder, EmailSettingsManager, MailDnsSync, MailEgressCoordinator, SmartMtaStorageManager, WorkAppMailManager, type TSmartMtaQueueItemLike } from './email/index.js';
|
|
43
41
|
import type { IRoute } from '../ts_interfaces/data/route-management.js';
|
|
44
|
-
import type { IEmailPortConfig, IEmailServerSettings, IEmailServerSettingsSeed, TEmailOutboundMode, TEmailServerSettingsUpdate } from '../ts_interfaces/data/email-settings.js';
|
|
42
|
+
import type { IEmailOutboundEgressStatus, IEmailPortConfig, IEmailServerSettings, IEmailServerSettingsSeed, TEmailOutboundMode, TEmailServerSettingsUpdate } from '../ts_interfaces/data/email-settings.js';
|
|
45
43
|
import type { IDcRouterRouteConfig, IRemoteIngressHubSettings, IRemoteIngressPerformanceConfig, TRemoteIngressHubSettingsUpdate } from '../ts_interfaces/data/remoteingress.js';
|
|
46
44
|
import type { ISecurityCompiledPolicy } from '../ts_interfaces/data/security-policy.js';
|
|
47
45
|
|
|
@@ -311,6 +309,8 @@ export class DcRouter {
|
|
|
311
309
|
public emailDomainManager?: EmailDomainManager;
|
|
312
310
|
public workAppMailManager: WorkAppMailManager;
|
|
313
311
|
public acceptedEmailSpool: AcceptedEmailSpool;
|
|
312
|
+
public mailEgressCoordinator: MailEgressCoordinator;
|
|
313
|
+
public mailDnsSync: MailDnsSync;
|
|
314
314
|
public securityPolicyManager?: SecurityPolicyManager;
|
|
315
315
|
|
|
316
316
|
// Auto-discovered public IP (populated by generateAuthoritativeRecords)
|
|
@@ -372,6 +372,8 @@ export class DcRouter {
|
|
|
372
372
|
);
|
|
373
373
|
this.workAppMailManager = new WorkAppMailManager(this);
|
|
374
374
|
this.acceptedEmailSpool = new AcceptedEmailSpool(this);
|
|
375
|
+
this.mailEgressCoordinator = new MailEgressCoordinator(this);
|
|
376
|
+
this.mailDnsSync = new MailDnsSync(this);
|
|
375
377
|
this.smartAcmeLifecycle = new SmartAcmeLifecycle(this);
|
|
376
378
|
this.vpnAccessResolver = new VpnAccessResolver(this);
|
|
377
379
|
this.remoteIngressHubLifecycle = new RemoteIngressHubLifecycle(this);
|
|
@@ -521,7 +523,14 @@ export class DcRouter {
|
|
|
521
523
|
.dependsOn('DcRouterDb')
|
|
522
524
|
.withStart(async () => {
|
|
523
525
|
this.emailSettingsManager = new EmailSettingsManager(this.options);
|
|
524
|
-
|
|
526
|
+
try {
|
|
527
|
+
await this.emailSettingsManager.start();
|
|
528
|
+
} catch (error: unknown) {
|
|
529
|
+
// Loud failure: without this manager, outbound mail defers (strict mode
|
|
530
|
+
// resolution) — this log is the operator's pointer to the root cause.
|
|
531
|
+
logger.log('error', `EmailSettingsManager failed to start — DB-backed email settings unavailable, outbound mail will defer: ${(error as Error).message}`);
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
525
534
|
})
|
|
526
535
|
.withStop(async () => {
|
|
527
536
|
if (this.emailSettingsManager) {
|
|
@@ -566,6 +575,8 @@ export class DcRouter {
|
|
|
566
575
|
.dependsOn('DcRouterDb')
|
|
567
576
|
.withStart(async () => {
|
|
568
577
|
this.remoteIngressManager = new RemoteIngressManager();
|
|
578
|
+
this.remoteIngressManager.setHubPublicIps([this.options.publicIp]);
|
|
579
|
+
this.remoteIngressManager.setOnEdgesChanged((reason) => this.mailDnsSync.requestSync(reason));
|
|
569
580
|
await this.remoteIngressManager.initialize();
|
|
570
581
|
})
|
|
571
582
|
.withStop(async () => {
|
|
@@ -659,7 +670,10 @@ export class DcRouter {
|
|
|
659
670
|
// then push updated derived ports to the Rust hub binary
|
|
660
671
|
async (routes) => {
|
|
661
672
|
try {
|
|
662
|
-
await this.remoteIngressHubLifecycle.updateRoutes(
|
|
673
|
+
await this.remoteIngressHubLifecycle.updateRoutes([
|
|
674
|
+
...(routes as IDcRouterRouteConfig[]),
|
|
675
|
+
...this.getEdgeOnlyDerivationRoutes(),
|
|
676
|
+
]);
|
|
663
677
|
} catch (err: unknown) {
|
|
664
678
|
logger.log('error', `Failed to sync Remote Ingress allowed edges: ${(err as Error).message}`);
|
|
665
679
|
}
|
|
@@ -701,6 +715,10 @@ export class DcRouter {
|
|
|
701
715
|
if (this.options.dbConfig?.enabled !== false) {
|
|
702
716
|
const emailServiceDeps = ['SmartProxy', 'MetricsManager'];
|
|
703
717
|
emailServiceDeps.push('EmailDomainManager');
|
|
718
|
+
// Explicit dependency: the outbound mode must come from the DB-backed settings;
|
|
719
|
+
// without it, deliveries defer via the coordinator's strict mode resolution
|
|
720
|
+
// instead of going direct.
|
|
721
|
+
emailServiceDeps.push('EmailSettingsManager');
|
|
704
722
|
this.serviceManager.addService(
|
|
705
723
|
new plugins.taskbuffer.Service('EmailServer')
|
|
706
724
|
.optional()
|
|
@@ -1599,9 +1617,6 @@ export class DcRouter {
|
|
|
1599
1617
|
// Create config with mapped ports
|
|
1600
1618
|
const baseEmailConfig = this.options.emailConfig;
|
|
1601
1619
|
const configuredMessageDataHook = baseEmailConfig.hooks?.onMessageData;
|
|
1602
|
-
const outboundMode = this.emailSettingsManager?.getOutboundMode()
|
|
1603
|
-
|| this.options.emailOutboundMode
|
|
1604
|
-
|| 'direct';
|
|
1605
1620
|
const mappedEmailPorts = baseEmailConfig.ports.map(port => portMapping[port] || port + 10000);
|
|
1606
1621
|
const configuredSecurePort = baseEmailConfig.smtp?.securePort;
|
|
1607
1622
|
const mappedSecurePort = configuredSecurePort === undefined
|
|
@@ -1619,9 +1634,7 @@ export class DcRouter {
|
|
|
1619
1634
|
},
|
|
1620
1635
|
outbound: {
|
|
1621
1636
|
...baseEmailConfig.outbound,
|
|
1622
|
-
|
|
1623
|
-
? { connectionProxyProvider: async (context) => this.createRemoteIngressEmailConnectionProxy(context) }
|
|
1624
|
-
: {}),
|
|
1637
|
+
connectionProxyProvider: (context) => this.mailEgressCoordinator.provideConnectionProxy(context),
|
|
1625
1638
|
},
|
|
1626
1639
|
smtp: {
|
|
1627
1640
|
...baseEmailConfig.smtp,
|
|
@@ -1770,44 +1783,19 @@ export class DcRouter {
|
|
|
1770
1783
|
}
|
|
1771
1784
|
|
|
1772
1785
|
logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
|
|
1786
|
+
|
|
1787
|
+
this.mailEgressCoordinator.logEgressPreflight();
|
|
1788
|
+
this.mailDnsSync.requestSync('email server start');
|
|
1773
1789
|
}
|
|
1774
1790
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
throw new Error('RemoteIngress outbound SMTP egress is enabled, but the hub is not running');
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
const connectionTimeoutMs = this.options.emailConfig?.outbound?.connectionTimeout ?? 30_000;
|
|
1784
|
-
const proxy = await tunnelManager.startEgressTcpProxy({
|
|
1785
|
-
logicalHost: context.host,
|
|
1786
|
-
port: context.port,
|
|
1787
|
-
serverFirst: !context.secure,
|
|
1788
|
-
connectTimeoutMs: connectionTimeoutMs,
|
|
1789
|
-
acceptTimeoutMs: connectionTimeoutMs,
|
|
1790
|
-
});
|
|
1791
|
-
let closed = false;
|
|
1792
|
-
return {
|
|
1793
|
-
connectHost: proxy.listenHost,
|
|
1794
|
-
connectPort: proxy.listenPort,
|
|
1795
|
-
poolKey: `remoteIngress:${proxy.edgeId}:${context.host}:${context.port}`,
|
|
1796
|
-
close: async () => {
|
|
1797
|
-
if (closed) {
|
|
1798
|
-
return;
|
|
1799
|
-
}
|
|
1800
|
-
closed = true;
|
|
1801
|
-
try {
|
|
1802
|
-
await tunnelManager.stopEgressTcpProxy(proxy.proxyId);
|
|
1803
|
-
} catch (err: unknown) {
|
|
1804
|
-
logger.log('warn', `Failed to stop RemoteIngress egress proxy ${proxy.proxyId}: ${(err as Error).message}`);
|
|
1805
|
-
}
|
|
1806
|
-
},
|
|
1807
|
-
};
|
|
1791
|
+
/**
|
|
1792
|
+
* Readiness of the RemoteIngress outbound mail egress path (mail-tagged edges).
|
|
1793
|
+
*/
|
|
1794
|
+
public getOutboundEgressStatus(): IEmailOutboundEgressStatus {
|
|
1795
|
+
return this.mailEgressCoordinator.getOutboundEgressStatus();
|
|
1808
1796
|
}
|
|
1809
1797
|
|
|
1810
|
-
|
|
1798
|
+
|
|
1811
1799
|
/**
|
|
1812
1800
|
* Update the unified email configuration
|
|
1813
1801
|
* @param config New email configuration
|
|
@@ -2017,7 +2005,12 @@ export class DcRouter {
|
|
|
2017
2005
|
|
|
2018
2006
|
/** Bootstrap routes the RemoteIngress hub uses to derive edge ports before the DB route set is applied. */
|
|
2019
2007
|
public getRemoteIngressBootstrapRoutes(): plugins.smartproxy.IRouteConfig[] {
|
|
2020
|
-
return [...this.seedConfigRoutes, ...this.seedEmailRoutes, ...this.runtimeDnsRoutes];
|
|
2008
|
+
return [...this.seedConfigRoutes, ...this.seedEmailRoutes, ...this.runtimeDnsRoutes, ...this.getEdgeOnlyDerivationRoutes()];
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
/** Edge-derivation-only routes (see buildEdgeOnlyDerivationRoutes) — never passed to SmartProxy. */
|
|
2012
|
+
public getEdgeOnlyDerivationRoutes(): IDcRouterRouteConfig[] {
|
|
2013
|
+
return buildEdgeOnlyDerivationRoutes(this.options);
|
|
2021
2014
|
}
|
|
2022
2015
|
|
|
2023
2016
|
/**
|
|
@@ -8,6 +8,12 @@ const TTL = plugins.smartdata.smartdataTtlValues;
|
|
|
8
8
|
*/
|
|
9
9
|
export type TCachedEmailStatus = 'pending' | 'processing' | 'queued' | 'delivered' | 'failed' | 'deferred';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Direction of an accepted email: 'inbound' = received from a remote peer,
|
|
13
|
+
* 'outbound' = locally submitted for delivery.
|
|
14
|
+
*/
|
|
15
|
+
export type TCachedEmailDirection = 'inbound' | 'outbound';
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* Helper to get the smartdata database instance
|
|
13
19
|
*/
|
|
@@ -112,6 +118,24 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
|
|
|
112
118
|
@plugins.smartdata.svDb()
|
|
113
119
|
public senderDomain!: string;
|
|
114
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Direction of the accepted message (inbound receipt vs local submission)
|
|
123
|
+
*/
|
|
124
|
+
@plugins.smartdata.svDb()
|
|
125
|
+
public direction: TCachedEmailDirection = 'outbound';
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Lowercased recipient domains (for querying/filtering)
|
|
129
|
+
*/
|
|
130
|
+
@plugins.smartdata.svDb()
|
|
131
|
+
public recipientDomains: string[] = [];
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Epoch ms when the message was accepted (for newest-first listings)
|
|
135
|
+
*/
|
|
136
|
+
@plugins.smartdata.svDb()
|
|
137
|
+
public acceptedAt: number = 0;
|
|
138
|
+
|
|
115
139
|
/**
|
|
116
140
|
* Priority level (higher = more important)
|
|
117
141
|
*/
|
|
@@ -183,7 +207,11 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
|
|
|
183
207
|
}, limit);
|
|
184
208
|
}
|
|
185
209
|
|
|
186
|
-
private static async findLimited(
|
|
210
|
+
private static async findLimited(
|
|
211
|
+
filter: Record<string, any>,
|
|
212
|
+
limit: number,
|
|
213
|
+
sort?: Record<string, 1 | -1>,
|
|
214
|
+
): Promise<CachedEmail[]> {
|
|
187
215
|
const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 25;
|
|
188
216
|
if (safeLimit === 0) {
|
|
189
217
|
return [];
|
|
@@ -195,6 +223,9 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
|
|
|
195
223
|
filter,
|
|
196
224
|
CachedEmail as unknown as typeof plugins.smartdata.SmartDataDbDoc,
|
|
197
225
|
);
|
|
226
|
+
if (sort) {
|
|
227
|
+
cursor.mongodbCursor.sort(sort);
|
|
228
|
+
}
|
|
198
229
|
cursor.mongodbCursor.limit(safeLimit);
|
|
199
230
|
try {
|
|
200
231
|
return await cursor.toArray();
|
|
@@ -203,6 +234,32 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
|
|
|
203
234
|
}
|
|
204
235
|
}
|
|
205
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Find the most recently accepted emails, newest first.
|
|
239
|
+
*/
|
|
240
|
+
public static async findRecent(
|
|
241
|
+
limit = 200,
|
|
242
|
+
filter: { direction?: TCachedEmailDirection; recipientDomain?: string } = {},
|
|
243
|
+
): Promise<CachedEmail[]> {
|
|
244
|
+
const query: Record<string, any> = {};
|
|
245
|
+
if (filter.direction) {
|
|
246
|
+
query.direction = filter.direction;
|
|
247
|
+
}
|
|
248
|
+
if (filter.recipientDomain) {
|
|
249
|
+
query.recipientDomains = filter.recipientDomain.toLowerCase();
|
|
250
|
+
}
|
|
251
|
+
return await CachedEmail.findLimited(query, limit, { acceptedAt: -1 });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Find emails addressed to a recipient domain
|
|
256
|
+
*/
|
|
257
|
+
public static async findByRecipientDomain(domain: string): Promise<CachedEmail[]> {
|
|
258
|
+
return await CachedEmail.getInstances({
|
|
259
|
+
recipientDomains: { $all: [domain.toLowerCase()] },
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
206
263
|
/**
|
|
207
264
|
* Find emails by sender domain
|
|
208
265
|
*/
|
|
@@ -254,4 +311,18 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
|
|
|
254
311
|
}
|
|
255
312
|
}
|
|
256
313
|
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Derive recipient domains from the recipient address list
|
|
317
|
+
*/
|
|
318
|
+
public updateRecipientDomains(): void {
|
|
319
|
+
const domains = new Set<string>();
|
|
320
|
+
for (const address of this.to || []) {
|
|
321
|
+
const match = address.match(/@([^>]+)>?$/);
|
|
322
|
+
if (match) {
|
|
323
|
+
domains.add(match[1].toLowerCase());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
this.recipientDomains = [...domains];
|
|
327
|
+
}
|
|
257
328
|
}
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
IEmailDomainDkim,
|
|
5
5
|
IEmailDomainRateLimits,
|
|
6
6
|
IEmailDomainDnsStatus,
|
|
7
|
+
IEmailDomainRemoteIngress,
|
|
7
8
|
} from '../../../ts_interfaces/data/email-domain.js';
|
|
8
9
|
|
|
9
10
|
const getDb = () => DcRouterDb.getInstance().getDb();
|
|
@@ -29,6 +30,9 @@ export class EmailDomainDoc extends plugins.smartdata.SmartDataDbDoc<EmailDomain
|
|
|
29
30
|
@plugins.smartdata.svDb()
|
|
30
31
|
public rateLimits?: IEmailDomainRateLimits;
|
|
31
32
|
|
|
33
|
+
@plugins.smartdata.svDb()
|
|
34
|
+
public remoteIngress?: IEmailDomainRemoteIngress;
|
|
35
|
+
|
|
32
36
|
@plugins.smartdata.svDb()
|
|
33
37
|
public dnsStatus!: IEmailDomainDnsStatus;
|
|
34
38
|
|
|
@@ -18,7 +18,7 @@ export class EmailServerSettingsDoc extends plugins.smartdata.SmartDataDbDoc<Ema
|
|
|
18
18
|
public emailConfig?: IUnifiedEmailServerOptions;
|
|
19
19
|
|
|
20
20
|
@plugins.smartdata.svDb()
|
|
21
|
-
public outboundMode: TEmailOutboundMode = '
|
|
21
|
+
public outboundMode: TEmailOutboundMode = 'remoteIngress';
|
|
22
22
|
|
|
23
23
|
@plugins.smartdata.svDb()
|
|
24
24
|
public emailPortConfig?: IEmailPortConfig;
|
|
@@ -37,6 +37,15 @@ export class RemoteIngressEdgeDoc extends plugins.smartdata.SmartDataDbDoc<Remot
|
|
|
37
37
|
@plugins.smartdata.svDb()
|
|
38
38
|
public tags!: string[];
|
|
39
39
|
|
|
40
|
+
@plugins.smartdata.svDb()
|
|
41
|
+
public publicIp?: string;
|
|
42
|
+
|
|
43
|
+
@plugins.smartdata.svDb()
|
|
44
|
+
public publicIpV6?: string;
|
|
45
|
+
|
|
46
|
+
@plugins.smartdata.svDb()
|
|
47
|
+
public mailHostname?: string;
|
|
48
|
+
|
|
40
49
|
@plugins.smartdata.svDb()
|
|
41
50
|
public createdAt!: number;
|
|
42
51
|
|
|
@@ -446,6 +446,7 @@ export class DnsServerRuntime {
|
|
|
446
446
|
// Use explicitly configured public IP
|
|
447
447
|
publicIp = options.publicIp;
|
|
448
448
|
this.dcRouterRef.detectedPublicIp = publicIp;
|
|
449
|
+
this.dcRouterRef.remoteIngressManager?.setHubPublicIps([options.publicIp, publicIp]);
|
|
449
450
|
logger.log('info', `Using configured public IP for nameserver A records: ${publicIp}`);
|
|
450
451
|
} else {
|
|
451
452
|
// Auto-discover public IP using smartnetwork
|
|
@@ -457,6 +458,7 @@ export class DnsServerRuntime {
|
|
|
457
458
|
if (publicIps.v4) {
|
|
458
459
|
publicIp = publicIps.v4;
|
|
459
460
|
this.dcRouterRef.detectedPublicIp = publicIp;
|
|
461
|
+
this.dcRouterRef.remoteIngressManager?.setHubPublicIps([options.publicIp, publicIp]);
|
|
460
462
|
logger.log('info', `Auto-discovered public IPv4: ${publicIp}`);
|
|
461
463
|
} else {
|
|
462
464
|
logger.log('warn', 'Could not auto-discover public IPv4 address');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import { logger } from '../logger.js';
|
|
3
3
|
import { CachedEmail } from '../db/index.js';
|
|
4
|
+
import type { TCachedEmailDirection } from '../db/documents/classes.cached.email.js';
|
|
4
5
|
import type {
|
|
5
6
|
Email,
|
|
6
7
|
IExtendedSmtpSession,
|
|
@@ -70,6 +71,8 @@ export interface IAcceptRawEmailOptions {
|
|
|
70
71
|
messageId?: string;
|
|
71
72
|
subject?: string;
|
|
72
73
|
processAfterAccept?: boolean;
|
|
74
|
+
/** Explicit direction override for programmatic submitters. Defaults to the session-authenticated rule. */
|
|
75
|
+
direction?: TCachedEmailDirection;
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
/**
|
|
@@ -118,6 +121,8 @@ export class AcceptedEmailSpool {
|
|
|
118
121
|
cachedEmail.rawContent = this.setDcRouterCacheIdHeader(rawMessage.toString('utf8'), cachedEmail.id);
|
|
119
122
|
cachedEmail.status = 'pending';
|
|
120
123
|
cachedEmail.nextAttempt = new Date();
|
|
124
|
+
cachedEmail.direction = session.authenticated ? 'outbound' : 'inbound';
|
|
125
|
+
cachedEmail.acceptedAt = Date.now();
|
|
121
126
|
cachedEmail.routeData = JSON.stringify({
|
|
122
127
|
acceptedAt: new Date().toISOString(),
|
|
123
128
|
session: {
|
|
@@ -131,6 +136,7 @@ export class AcceptedEmailSpool {
|
|
|
131
136
|
},
|
|
132
137
|
});
|
|
133
138
|
cachedEmail.updateSenderDomain();
|
|
139
|
+
cachedEmail.updateRecipientDomains();
|
|
134
140
|
await cachedEmail.save();
|
|
135
141
|
if (context.abortSignal?.aborted) {
|
|
136
142
|
cachedEmail.markFailed('Message acceptance aborted before SMTP success');
|
|
@@ -171,6 +177,9 @@ export class AcceptedEmailSpool {
|
|
|
171
177
|
cachedEmail.rawContent = this.setDcRouterCacheIdHeader(rawMessage, cachedEmail.id);
|
|
172
178
|
cachedEmail.status = 'pending';
|
|
173
179
|
cachedEmail.nextAttempt = new Date();
|
|
180
|
+
cachedEmail.direction = optionsArg.direction
|
|
181
|
+
?? (optionsArg.session.authenticated ? 'outbound' : 'inbound');
|
|
182
|
+
cachedEmail.acceptedAt = Date.now();
|
|
174
183
|
cachedEmail.routeData = JSON.stringify({
|
|
175
184
|
acceptedAt: new Date().toISOString(),
|
|
176
185
|
session: {
|
|
@@ -184,6 +193,7 @@ export class AcceptedEmailSpool {
|
|
|
184
193
|
},
|
|
185
194
|
});
|
|
186
195
|
cachedEmail.updateSenderDomain();
|
|
196
|
+
cachedEmail.updateRecipientDomains();
|
|
187
197
|
await cachedEmail.save();
|
|
188
198
|
|
|
189
199
|
if (optionsArg.processAfterAccept !== false) {
|
|
@@ -506,7 +516,8 @@ export class AcceptedEmailSpool {
|
|
|
506
516
|
return cachedEmail.status === 'delivered' || cachedEmail.status === 'failed';
|
|
507
517
|
}
|
|
508
518
|
|
|
509
|
-
|
|
519
|
+
/** The CachedEmail row a live queue item was spooled from (via the cache-id header). */
|
|
520
|
+
public getCachedEmailIdFromQueueItem(item: TSmartMtaQueueItemLike): string | undefined {
|
|
510
521
|
return this.getHeaderValue(item.processingResult?.headers, DCROUTER_CACHE_ID_HEADER)
|
|
511
522
|
|| this.getHeaderValue(item.processingResult?.email?.headers, DCROUTER_CACHE_ID_HEADER);
|
|
512
523
|
}
|