@serve.zone/dcrouter 16.5.0 → 17.1.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.
Files changed (153) hide show
  1. package/.smartconfig.json +5 -2
  2. package/deno.json +1 -1
  3. package/dist_serve/bundle.js +1316 -1151
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/classes.dcrouter.d.ts +6 -1
  6. package/dist_ts/classes.dcrouter.js +119 -41
  7. package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
  8. package/dist_ts/db/classes.cache.cleaner.js +7 -3
  9. package/dist_ts/db/classes.dcrouter-db.js +1 -2
  10. package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
  11. package/dist_ts/db/documents/classes.cached.email.js +36 -5
  12. package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
  13. package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +8 -0
  14. package/dist_ts/db/documents/classes.smartmta-storage.doc.js +91 -0
  15. package/dist_ts/db/documents/index.d.ts +1 -0
  16. package/dist_ts/db/documents/index.js +2 -1
  17. package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
  18. package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
  19. package/dist_ts/dns/index.d.ts +1 -0
  20. package/dist_ts/dns/index.js +2 -1
  21. package/dist_ts/dns/manager.dns.d.ts +1 -0
  22. package/dist_ts/dns/manager.dns.js +49 -14
  23. package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
  24. package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
  25. package/dist_ts/dns/txt-record-presentation.js +131 -0
  26. package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
  27. package/dist_ts/email/classes.accepted-email-spool.js +174 -13
  28. package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
  29. package/dist_ts/email/classes.email-domain.manager.js +151 -8
  30. package/dist_ts/email/classes.email-settings.manager.js +2 -1
  31. package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
  32. package/dist_ts/email/classes.mail-dns-sync.js +508 -117
  33. package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +27 -0
  34. package/dist_ts/email/classes.smartmta-blob-storage-manager.js +110 -0
  35. package/dist_ts/email/classes.smartmta-storage-manager.d.ts +6 -8
  36. package/dist_ts/email/classes.smartmta-storage-manager.js +63 -86
  37. package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
  38. package/dist_ts/email/classes.workapp-mail-manager.js +31 -4
  39. package/dist_ts/email/index.d.ts +4 -0
  40. package/dist_ts/email/index.js +5 -1
  41. package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
  42. package/dist_ts/email/mail-dns-txt-policy.js +466 -0
  43. package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
  44. package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
  45. package/dist_ts/email/smartmta-storage-policy.d.ts +9 -0
  46. package/dist_ts/email/smartmta-storage-policy.js +58 -0
  47. package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
  48. package/dist_ts/opsserver/classes.opsserver.js +34 -1
  49. package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
  50. package/dist_ts/opsserver/handlers/config.handler.js +9 -5
  51. package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
  52. package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
  53. package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
  54. package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
  55. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
  56. package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
  57. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  58. package/dist_ts/opsserver/handlers/index.js +2 -1
  59. package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
  60. package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
  61. package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
  62. package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
  63. package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
  64. package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
  65. package/dist_ts/opsserver/handlers/stats.handler.js +3 -5
  66. package/dist_ts/paths.d.ts +0 -11
  67. package/dist_ts/paths.js +1 -22
  68. package/dist_ts/plugins.d.ts +2 -35
  69. package/dist_ts/plugins.js +3 -63
  70. package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
  71. package/dist_ts_interfaces/requests/config.d.ts +1 -1
  72. package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
  73. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  74. package/dist_ts_interfaces/requests/index.js +2 -1
  75. package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
  76. package/dist_ts_interfaces/requests/realtime.js +11 -0
  77. package/dist_ts_migrations/index.d.ts +8 -0
  78. package/dist_ts_migrations/index.js +489 -12
  79. package/dist_ts_web/00_commitinfo_data.js +1 -1
  80. package/dist_ts_web/appstate/certificates.js +9 -2
  81. package/dist_ts_web/appstate/domains.js +16 -5
  82. package/dist_ts_web/appstate/email-domains.d.ts +3 -0
  83. package/dist_ts_web/appstate/email-domains.js +47 -14
  84. package/dist_ts_web/appstate/email-ops.d.ts +1 -0
  85. package/dist_ts_web/appstate/email-ops.js +20 -2
  86. package/dist_ts_web/appstate/realtime.d.ts +27 -0
  87. package/dist_ts_web/appstate/realtime.js +71 -0
  88. package/dist_ts_web/appstate/remoteingress.js +9 -3
  89. package/dist_ts_web/appstate/routes.js +16 -5
  90. package/dist_ts_web/appstate/runtime.js +135 -7
  91. package/dist_ts_web/appstate.d.ts +1 -0
  92. package/dist_ts_web/appstate.js +2 -1
  93. package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
  94. package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
  95. package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
  96. package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
  97. package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
  98. package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
  99. package/package.json +23 -19
  100. package/ts/00_commitinfo_data.ts +1 -1
  101. package/ts/classes.dcrouter.ts +136 -44
  102. package/ts/db/classes.cache.cleaner.ts +18 -3
  103. package/ts/db/classes.dcrouter-db.ts +0 -2
  104. package/ts/db/documents/classes.cached.email.ts +78 -4
  105. package/ts/db/documents/classes.dns-record.doc.ts +1 -0
  106. package/ts/db/documents/classes.smartmta-storage.doc.ts +25 -0
  107. package/ts/db/documents/index.ts +1 -0
  108. package/ts/dns/classes.dns-server-runtime.ts +9 -45
  109. package/ts/dns/index.ts +1 -0
  110. package/ts/dns/manager.dns.ts +50 -16
  111. package/ts/dns/providers/cloudflare.provider.ts +21 -3
  112. package/ts/dns/txt-record-presentation.ts +134 -0
  113. package/ts/email/classes.accepted-email-spool.ts +220 -14
  114. package/ts/email/classes.email-domain.manager.ts +201 -7
  115. package/ts/email/classes.email-settings.manager.ts +1 -0
  116. package/ts/email/classes.mail-dns-sync.ts +588 -122
  117. package/ts/email/classes.smartmta-blob-storage-manager.ts +139 -0
  118. package/ts/email/classes.smartmta-storage-manager.ts +86 -83
  119. package/ts/email/classes.workapp-mail-manager.ts +47 -4
  120. package/ts/email/index.ts +4 -0
  121. package/ts/email/mail-dns-txt-policy.ts +505 -0
  122. package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
  123. package/ts/email/smartmta-storage-policy.ts +73 -0
  124. package/ts/opsserver/classes.opsserver.ts +42 -0
  125. package/ts/opsserver/handlers/certificate.handler.ts +24 -3
  126. package/ts/opsserver/handlers/config.handler.ts +9 -5
  127. package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
  128. package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
  129. package/ts/opsserver/handlers/domain.handler.ts +36 -2
  130. package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
  131. package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
  132. package/ts/opsserver/handlers/index.ts +1 -0
  133. package/ts/opsserver/handlers/logs.handler.ts +2 -2
  134. package/ts/opsserver/handlers/realtime.handler.ts +222 -0
  135. package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
  136. package/ts/opsserver/handlers/route-management.handler.ts +46 -4
  137. package/ts/opsserver/handlers/stats.handler.ts +2 -4
  138. package/ts/paths.ts +0 -25
  139. package/ts/plugins.ts +2 -69
  140. package/ts_web/00_commitinfo_data.ts +1 -1
  141. package/ts_web/appstate/certificates.ts +6 -1
  142. package/ts_web/appstate/domains.ts +11 -4
  143. package/ts_web/appstate/email-domains.ts +68 -14
  144. package/ts_web/appstate/email-ops.ts +20 -1
  145. package/ts_web/appstate/realtime.ts +109 -0
  146. package/ts_web/appstate/remoteingress.ts +6 -2
  147. package/ts_web/appstate/routes.ts +11 -4
  148. package/ts_web/appstate/runtime.ts +176 -7
  149. package/ts_web/appstate.ts +1 -0
  150. package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
  151. package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
  152. package/ts_web/elements/email/ops-view-emails.ts +23 -16
  153. package/ts_web/elements/overview/ops-view-config.ts +4 -4
@@ -0,0 +1,139 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { IBlobStorageManager } from '@push.rocks/smartmta';
3
+
4
+ const SMARTMTA_BLOB_NAMESPACES = [
5
+ '/email/queue/',
6
+ '/email/attachments/',
7
+ '/email/messages/',
8
+ ] as const;
9
+ const DEFAULT_OBJECT_PREFIX = 'dcrouter/smartmta/';
10
+ const MAX_STORAGE_KEY_BYTES = 1024;
11
+
12
+ type TBlobBucket = Pick<
13
+ plugins.smartbucket.Bucket,
14
+ 'fastExists' | 'fastGet' | 'fastPut' | 'fastRemove' | 'listAllObjects'
15
+ >;
16
+
17
+ export interface ISmartMtaBlobStorageConfig {
18
+ storageDescriptor: plugins.tsclass.storage.IStorageDescriptor;
19
+ bucketName: string;
20
+ objectPrefix?: string;
21
+ createBucketIfMissing?: boolean;
22
+ }
23
+
24
+ function normalizeObjectPrefix(prefixArg = DEFAULT_OBJECT_PREFIX): string {
25
+ const prefix = prefixArg.trim().replace(/^\/+|\/+$/g, '');
26
+ if (!prefix || prefix.includes('\\') || prefix.includes('\0')) {
27
+ throw new Error('SmartMTA blob objectPrefix must be a non-empty object-key prefix');
28
+ }
29
+ const segments = prefix.split('/');
30
+ if (segments.some((segment) => !segment || segment === '.' || segment === '..')) {
31
+ throw new Error('SmartMTA blob objectPrefix contains an invalid path segment');
32
+ }
33
+ return `${prefix}/`;
34
+ }
35
+
36
+ function normalizeBlobKey(keyArg: string, allowNamespaceRoot = false): string {
37
+ if (
38
+ typeof keyArg !== 'string' ||
39
+ !keyArg.startsWith('/') ||
40
+ keyArg.includes('\\') ||
41
+ keyArg.includes('\0') ||
42
+ Buffer.byteLength(keyArg, 'utf8') > MAX_STORAGE_KEY_BYTES
43
+ ) {
44
+ throw new Error('Invalid SmartMTA blob storage key');
45
+ }
46
+ const namespace = SMARTMTA_BLOB_NAMESPACES.find((candidate) => keyArg.startsWith(candidate));
47
+ if (!namespace) {
48
+ throw new Error(`Unsupported SmartMTA blob storage namespace: ${keyArg}`);
49
+ }
50
+ const remainder = keyArg.slice(namespace.length);
51
+ if (!allowNamespaceRoot && !remainder) {
52
+ throw new Error('SmartMTA blob storage key must identify an object');
53
+ }
54
+ if (remainder && remainder.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
55
+ throw new Error('SmartMTA blob storage key contains an invalid path segment');
56
+ }
57
+ return keyArg;
58
+ }
59
+
60
+ /**
61
+ * SmartMTA blob persistence backed by a SmartBucket bucket.
62
+ *
63
+ * The adapter accepts only SmartMTA queue, attachment, and retained-message
64
+ * namespaces. Every key remains a SmartBucket object key.
65
+ */
66
+ export class SmartMtaBlobStorageManager implements IBlobStorageManager {
67
+ private readonly objectPrefix: string;
68
+
69
+ public constructor(
70
+ private readonly bucketRef: TBlobBucket,
71
+ objectPrefix = DEFAULT_OBJECT_PREFIX,
72
+ ) {
73
+ this.objectPrefix = normalizeObjectPrefix(objectPrefix);
74
+ }
75
+
76
+ public static async create(
77
+ configArg: ISmartMtaBlobStorageConfig,
78
+ ): Promise<SmartMtaBlobStorageManager> {
79
+ const bucketName = configArg.bucketName?.trim();
80
+ if (!bucketName) {
81
+ throw new Error('SmartMTA blob storage requires a bucketName');
82
+ }
83
+
84
+ const smartBucket = new plugins.smartbucket.SmartBucket(configArg.storageDescriptor);
85
+ let bucket: plugins.smartbucket.Bucket;
86
+ if (configArg.createBucketIfMissing) {
87
+ bucket = await smartBucket.bucketExists(bucketName)
88
+ ? await smartBucket.getBucketByName(bucketName)
89
+ : await smartBucket.createBucket(bucketName);
90
+ } else {
91
+ bucket = await smartBucket.getBucketByName(bucketName);
92
+ }
93
+
94
+ return new SmartMtaBlobStorageManager(bucket, configArg.objectPrefix);
95
+ }
96
+
97
+ public async get(keyArg: string): Promise<Buffer | null> {
98
+ const objectKey = this.toObjectKey(normalizeBlobKey(keyArg));
99
+ if (!(await this.bucketRef.fastExists({ path: objectKey }))) {
100
+ return null;
101
+ }
102
+ return Buffer.from(await this.bucketRef.fastGet({ path: objectKey }));
103
+ }
104
+
105
+ public async set(keyArg: string, valueArg: Buffer): Promise<void> {
106
+ if (!Buffer.isBuffer(valueArg)) {
107
+ throw new TypeError('SmartMTA blob storage values must be Buffers');
108
+ }
109
+ const objectKey = this.toObjectKey(normalizeBlobKey(keyArg));
110
+ await this.bucketRef.fastPut({
111
+ path: objectKey,
112
+ contents: Buffer.from(valueArg),
113
+ overwrite: true,
114
+ });
115
+ }
116
+
117
+ public async list(prefixArg: string): Promise<string[]> {
118
+ const prefix = normalizeBlobKey(prefixArg, true);
119
+ const objectPrefix = this.toObjectKey(prefix);
120
+ const keys: string[] = [];
121
+ for await (const objectKey of this.bucketRef.listAllObjects(objectPrefix)) {
122
+ if (!objectKey.startsWith(this.objectPrefix)) {
123
+ throw new Error(`SmartBucket returned an object outside the configured prefix: ${objectKey}`);
124
+ }
125
+ const key = `/${objectKey.slice(this.objectPrefix.length)}`;
126
+ keys.push(normalizeBlobKey(key));
127
+ }
128
+ return keys.sort();
129
+ }
130
+
131
+ public async delete(keyArg: string): Promise<void> {
132
+ const objectKey = this.toObjectKey(normalizeBlobKey(keyArg));
133
+ await this.bucketRef.fastRemove({ path: objectKey });
134
+ }
135
+
136
+ private toObjectKey(keyArg: string): string {
137
+ return `${this.objectPrefix}${keyArg.slice(1)}`;
138
+ }
139
+ }
@@ -1,108 +1,111 @@
1
1
  import * as plugins from '../plugins.js';
2
- import type { IStorageManagerLike } from '@push.rocks/smartmta';
2
+ import type { IStorageManager } from '@push.rocks/smartmta';
3
+ import {
4
+ getSmartMtaTextNamespace,
5
+ hashSmartMtaStorageKey,
6
+ normalizeSmartMtaStorageKey,
7
+ validateSmartMtaTextValue,
8
+ } from './smartmta-storage-policy.js';
3
9
 
4
- export class SmartMtaStorageManager implements IStorageManagerLike {
5
- private readonly resolvedRootDir: string;
10
+ interface ISmartMtaStorageDocument {
11
+ _id: string;
12
+ id: string;
13
+ key: string;
14
+ value: string;
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ }
6
18
 
7
- constructor(private rootDir: string) {
8
- this.resolvedRootDir = plugins.path.resolve(rootDir);
9
- plugins.fsUtils.ensureDirSync(this.resolvedRootDir);
10
- }
19
+ export class SmartMtaStorageManager implements IStorageManager {
20
+ private readonly collection: any;
11
21
 
12
- private normalizeKey(key: string): string {
13
- return key.replace(/^\/+/, '').replace(/\\/g, '/');
22
+ constructor(smartdataDb: plugins.smartdata.SmartdataDb) {
23
+ this.collection = smartdataDb.mongoDb.collection('SmartMtaStorageDoc');
14
24
  }
15
25
 
16
- private resolvePathForKey(key: string): string {
17
- const normalizedKey = this.normalizeKey(key);
18
- const resolvedPath = plugins.path.resolve(this.resolvedRootDir, normalizedKey);
26
+ private validateDocument(
27
+ document: ISmartMtaStorageDocument,
28
+ expectedKey?: string,
29
+ ): ISmartMtaStorageDocument {
30
+ const normalizedKey = normalizeSmartMtaStorageKey(document.key);
31
+ const expectedId = hashSmartMtaStorageKey(normalizedKey);
19
32
  if (
20
- resolvedPath !== this.resolvedRootDir
21
- && !resolvedPath.startsWith(`${this.resolvedRootDir}${plugins.path.sep}`)
33
+ document._id !== expectedId
34
+ || document.id !== expectedId
35
+ || document.key !== normalizedKey
36
+ || (expectedKey && document.key !== expectedKey)
37
+ || typeof document.value !== 'string'
22
38
  ) {
23
- throw new Error(`Storage key escapes root directory: ${key}`);
39
+ throw new Error(`Corrupt SmartMTA storage document: ${document._id}`);
24
40
  }
25
- return resolvedPath;
26
- }
27
-
28
- private toStorageKey(filePath: string): string {
29
- const relativePath = plugins.path.relative(this.resolvedRootDir, filePath).split(plugins.path.sep).join('/');
30
- return `/${relativePath}`;
41
+ validateSmartMtaTextValue(document.key, document.value);
42
+ return document;
31
43
  }
32
44
 
33
45
  public async get(key: string): Promise<string | null> {
34
- const filePath = this.resolvePathForKey(key);
35
- try {
36
- return await plugins.fs.promises.readFile(filePath, 'utf8');
37
- } catch (error: unknown) {
38
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
39
- return null;
40
- }
41
- throw error;
42
- }
46
+ const normalizedKey = normalizeSmartMtaStorageKey(key);
47
+ getSmartMtaTextNamespace(normalizedKey);
48
+ const id = hashSmartMtaStorageKey(normalizedKey);
49
+ const document = await this.collection.findOne({ _id: id });
50
+ return document ? this.validateDocument(document, normalizedKey).value : null;
43
51
  }
44
52
 
45
53
  public async set(key: string, value: string): Promise<void> {
46
- const filePath = this.resolvePathForKey(key);
47
- await plugins.fs.promises.mkdir(plugins.path.dirname(filePath), { recursive: true });
48
- await plugins.fs.promises.writeFile(filePath, value, 'utf8');
49
- }
54
+ const normalizedKey = normalizeSmartMtaStorageKey(key);
55
+ validateSmartMtaTextValue(normalizedKey, value);
56
+ const id = hashSmartMtaStorageKey(normalizedKey);
57
+ const existing = await this.collection.findOne({ _id: id });
58
+ if (existing) {
59
+ this.validateDocument(existing, normalizedKey);
60
+ }
50
61
 
51
- public async list(prefix: string): Promise<string[]> {
52
- const prefixPath = this.resolvePathForKey(prefix);
53
- try {
54
- const stat = await plugins.fs.promises.stat(prefixPath);
55
- if (stat.isFile()) {
56
- return [this.toStorageKey(prefixPath)];
57
- }
58
- } catch (error: unknown) {
59
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
60
- return [];
61
- }
62
- throw error;
62
+ const now = new Date().toISOString();
63
+ await this.collection.updateOne(
64
+ { _id: id },
65
+ {
66
+ $set: {
67
+ id,
68
+ key: normalizedKey,
69
+ value,
70
+ updatedAt: now,
71
+ },
72
+ $setOnInsert: {
73
+ createdAt: now,
74
+ },
75
+ },
76
+ { upsert: true },
77
+ );
78
+
79
+ const stored = await this.collection.findOne({ _id: id });
80
+ if (!stored || this.validateDocument(stored, normalizedKey).value !== value) {
81
+ throw new Error(`SmartMTA storage write verification failed: ${normalizedKey}`);
63
82
  }
83
+ }
64
84
 
65
- const results: string[] = [];
66
- const walk = async (currentPath: string): Promise<void> => {
67
- const entries = await plugins.fs.promises.readdir(currentPath, { withFileTypes: true });
68
- for (const entry of entries) {
69
- const entryPath = plugins.path.join(currentPath, entry.name);
70
- if (entry.isDirectory()) {
71
- await walk(entryPath);
72
- } else if (entry.isFile()) {
73
- results.push(this.toStorageKey(entryPath));
74
- }
75
- }
76
- };
85
+ public async list(prefix: string): Promise<string[]> {
86
+ const normalizedPrefix = normalizeSmartMtaStorageKey(prefix);
87
+ getSmartMtaTextNamespace(normalizedPrefix);
88
+ const documents = await this.collection.find({
89
+ key: {
90
+ $gte: normalizedPrefix,
91
+ $lt: `${normalizedPrefix}\uffff`,
92
+ },
93
+ }).toArray();
77
94
 
78
- await walk(prefixPath);
79
- return results.sort();
95
+ return documents
96
+ .map((document: ISmartMtaStorageDocument) => this.validateDocument(document).key)
97
+ .filter((key: string) => key.startsWith(normalizedPrefix))
98
+ .sort();
80
99
  }
81
100
 
82
101
  public async delete(key: string): Promise<void> {
83
- const targetPath = this.resolvePathForKey(key);
84
- try {
85
- const stat = await plugins.fs.promises.stat(targetPath);
86
- if (stat.isDirectory()) {
87
- await plugins.fs.promises.rm(targetPath, { recursive: true, force: true });
88
- } else {
89
- await plugins.fs.promises.unlink(targetPath);
90
- }
91
- } catch (error: unknown) {
92
- if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
93
- return;
94
- }
95
- throw error;
96
- }
97
-
98
- let currentDir = plugins.path.dirname(targetPath);
99
- while (currentDir.startsWith(this.resolvedRootDir) && currentDir !== this.resolvedRootDir) {
100
- const entries = await plugins.fs.promises.readdir(currentDir);
101
- if (entries.length > 0) {
102
- break;
103
- }
104
- await plugins.fs.promises.rmdir(currentDir);
105
- currentDir = plugins.path.dirname(currentDir);
102
+ const normalizedKey = normalizeSmartMtaStorageKey(key);
103
+ getSmartMtaTextNamespace(normalizedKey);
104
+ const id = hashSmartMtaStorageKey(normalizedKey);
105
+ const existing = await this.collection.findOne({ _id: id });
106
+ if (existing) {
107
+ this.validateDocument(existing, normalizedKey);
108
+ await this.collection.deleteOne({ _id: id });
106
109
  }
107
110
  }
108
111
  }
@@ -4,10 +4,12 @@ import type {
4
4
  IMessageAcceptanceContext,
5
5
  IMessageAcceptanceDecision,
6
6
  IUnifiedEmailServerOptions,
7
+ IStorageManagerLike,
7
8
  } from '@push.rocks/smartmta';
8
9
  import type { Buffer } from 'node:buffer';
9
10
  import * as plugins from '../plugins.js';
10
11
  import { CachedEmail } from '../db/index.js';
12
+ import type { ICachedEmailSmtpTransaction } from '../db/documents/classes.cached.email.js';
11
13
  import type { DcRouter } from '../classes.dcrouter.js';
12
14
  import type * as interfaces from '../../ts_interfaces/index.js';
13
15
 
@@ -42,6 +44,7 @@ type TSmartMtaStatusQueueItem = {
42
44
  attempts?: number;
43
45
  nextAttempt?: Date;
44
46
  lastError?: string;
47
+ smtpTransactions?: ICachedEmailSmtpTransaction[];
45
48
  processingResult?: {
46
49
  headers?: Record<string, string>;
47
50
  email?: {
@@ -611,11 +614,20 @@ export class WorkAppMailManager {
611
614
  await storageManager.set(this.storageKey, JSON.stringify(state, null, 2));
612
615
  }
613
616
 
614
- private getRequiredStorageManager() {
615
- if (!plugins.smartmta.hasStorageManagerMethods(this.dcRouterRef.storageManager, ['get', 'set'])) {
617
+ private getRequiredStorageManager(): IStorageManagerLike & Required<
618
+ Pick<IStorageManagerLike, 'get' | 'set'>
619
+ > {
620
+ const storageManager = this.dcRouterRef.storageManager;
621
+ if (
622
+ !storageManager
623
+ || typeof storageManager.get !== 'function'
624
+ || typeof storageManager.set !== 'function'
625
+ ) {
616
626
  throw new Error('DcRouter storage manager does not support managed mail identity persistence');
617
627
  }
618
- return this.dcRouterRef.storageManager;
628
+ return storageManager as IStorageManagerLike & Required<
629
+ Pick<IStorageManagerLike, 'get' | 'set'>
630
+ >;
619
631
  }
620
632
 
621
633
  private mergeIdentitiesIntoEmailConfig<TConfig extends IUnifiedEmailServerOptions>(
@@ -1044,10 +1056,41 @@ export class WorkAppMailManager {
1044
1056
  createdAt,
1045
1057
  updatedAt,
1046
1058
  },
1047
- attempts: [],
1059
+ attempts: this.mapDeliveryAttempts(
1060
+ queueItem?.smtpTransactions || cachedEmailArg.smtpTransactions || [],
1061
+ cachedEmailArg.id,
1062
+ ),
1048
1063
  };
1049
1064
  }
1050
1065
 
1066
+ private mapDeliveryAttempts(
1067
+ transactionsArg: ICachedEmailSmtpTransaction[],
1068
+ spoolItemIdArg: string,
1069
+ ): plugins.servezoneInterfaces.data.IMailDeliveryAttempt[] {
1070
+ return transactionsArg.map((transaction) => ({
1071
+ id: transaction.id,
1072
+ spoolItemId: spoolItemIdArg,
1073
+ target: {
1074
+ type: 'mtaRelay',
1075
+ label: transaction.recipientDomain || `${transaction.targetHost}:${transaction.targetPort}`,
1076
+ host: transaction.targetHost,
1077
+ port: transaction.targetPort,
1078
+ },
1079
+ status: transaction.outcome === 'succeeded'
1080
+ ? 'delivered'
1081
+ : transaction.retryable
1082
+ ? 'deferred'
1083
+ : 'failed',
1084
+ startedAt: this.toTimestamp(transaction.startedAt) || 0,
1085
+ completedAt: this.toTimestamp(transaction.completedAt),
1086
+ smtpCode: transaction.smtpCode,
1087
+ response: transaction.recipientResults?.map((result) => {
1088
+ return `${result.recipient}: ${result.responseCode} ${result.accepted ? 'accepted' : 'rejected'}`;
1089
+ }).join('; '),
1090
+ error: transaction.error,
1091
+ }));
1092
+ }
1093
+
1051
1094
  private findQueueItemForCachedEmail(cachedEmailIdArg: string): TSmartMtaStatusQueueItem | undefined {
1052
1095
  const emailServer = this.dcRouterRef.emailServer;
1053
1096
  if (!emailServer?.getQueueItems) {
package/ts/email/index.ts CHANGED
@@ -2,9 +2,13 @@ export * from './classes.accepted-email-spool.js';
2
2
  export * from './classes.email-domain.manager.js';
3
3
  export * from './classes.email-route-builder.js';
4
4
  export * from './classes.mail-dns-sync.js';
5
+ export * from './mail-dns-txt-policy.js';
6
+ export * from './reconcile-mail-dns-singleton.js';
5
7
  export * from './classes.mail-egress-coordinator.js';
6
8
  export * from './classes.mail-edge-eligibility.js';
7
9
  export * from './classes.email-settings.manager.js';
10
+ export * from './classes.smartmta-blob-storage-manager.js';
8
11
  export * from './classes.smartmta-storage-manager.js';
12
+ export * from './smartmta-storage-policy.js';
9
13
  export * from './classes.workapp-mail-manager.js';
10
14
  export * from './email-dns-records.js';