@push.rocks/smartmta 5.1.2 → 5.2.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 (99) hide show
  1. package/changelog.md +14 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/index.d.ts +3 -0
  5. package/dist_ts/index.js +4 -0
  6. package/dist_ts/logger.d.ts +17 -0
  7. package/dist_ts/logger.js +76 -0
  8. package/dist_ts/mail/core/classes.bouncemanager.d.ts +185 -0
  9. package/dist_ts/mail/core/classes.bouncemanager.js +569 -0
  10. package/dist_ts/mail/core/classes.email.d.ts +291 -0
  11. package/dist_ts/mail/core/classes.email.js +802 -0
  12. package/dist_ts/mail/core/classes.emailvalidator.d.ts +61 -0
  13. package/dist_ts/mail/core/classes.emailvalidator.js +184 -0
  14. package/dist_ts/mail/core/classes.templatemanager.d.ts +95 -0
  15. package/dist_ts/mail/core/classes.templatemanager.js +240 -0
  16. package/dist_ts/mail/core/index.d.ts +4 -0
  17. package/dist_ts/mail/core/index.js +6 -0
  18. package/dist_ts/mail/delivery/classes.delivery.queue.d.ts +163 -0
  19. package/dist_ts/mail/delivery/classes.delivery.queue.js +488 -0
  20. package/dist_ts/mail/delivery/classes.delivery.system.d.ts +160 -0
  21. package/dist_ts/mail/delivery/classes.delivery.system.js +630 -0
  22. package/dist_ts/mail/delivery/classes.unified.rate.limiter.d.ts +200 -0
  23. package/dist_ts/mail/delivery/classes.unified.rate.limiter.js +820 -0
  24. package/dist_ts/mail/delivery/index.d.ts +4 -0
  25. package/dist_ts/mail/delivery/index.js +6 -0
  26. package/dist_ts/mail/delivery/interfaces.d.ts +140 -0
  27. package/dist_ts/mail/delivery/interfaces.js +17 -0
  28. package/dist_ts/mail/index.d.ts +7 -0
  29. package/dist_ts/mail/index.js +12 -0
  30. package/dist_ts/mail/routing/classes.dkim.manager.d.ts +25 -0
  31. package/dist_ts/mail/routing/classes.dkim.manager.js +127 -0
  32. package/dist_ts/mail/routing/classes.dns.manager.d.ts +79 -0
  33. package/dist_ts/mail/routing/classes.dns.manager.js +415 -0
  34. package/dist_ts/mail/routing/classes.domain.registry.d.ts +54 -0
  35. package/dist_ts/mail/routing/classes.domain.registry.js +119 -0
  36. package/dist_ts/mail/routing/classes.email.action.executor.d.ts +33 -0
  37. package/dist_ts/mail/routing/classes.email.action.executor.js +137 -0
  38. package/dist_ts/mail/routing/classes.email.router.d.ts +171 -0
  39. package/dist_ts/mail/routing/classes.email.router.js +494 -0
  40. package/dist_ts/mail/routing/classes.unified.email.server.d.ts +241 -0
  41. package/dist_ts/mail/routing/classes.unified.email.server.js +935 -0
  42. package/dist_ts/mail/routing/index.d.ts +7 -0
  43. package/dist_ts/mail/routing/index.js +9 -0
  44. package/dist_ts/mail/routing/interfaces.d.ts +187 -0
  45. package/dist_ts/mail/routing/interfaces.js +2 -0
  46. package/dist_ts/mail/security/classes.dkimcreator.d.ts +72 -0
  47. package/dist_ts/mail/security/classes.dkimcreator.js +360 -0
  48. package/dist_ts/mail/security/classes.spfverifier.d.ts +62 -0
  49. package/dist_ts/mail/security/classes.spfverifier.js +87 -0
  50. package/dist_ts/mail/security/index.d.ts +2 -0
  51. package/dist_ts/mail/security/index.js +4 -0
  52. package/dist_ts/paths.d.ts +14 -0
  53. package/dist_ts/paths.js +39 -0
  54. package/dist_ts/plugins.d.ts +24 -0
  55. package/dist_ts/plugins.js +28 -0
  56. package/dist_ts/security/classes.contentscanner.d.ts +130 -0
  57. package/dist_ts/security/classes.contentscanner.js +338 -0
  58. package/dist_ts/security/classes.ipreputationchecker.d.ts +73 -0
  59. package/dist_ts/security/classes.ipreputationchecker.js +263 -0
  60. package/dist_ts/security/classes.rustsecuritybridge.d.ts +398 -0
  61. package/dist_ts/security/classes.rustsecuritybridge.js +484 -0
  62. package/dist_ts/security/classes.securitylogger.d.ts +140 -0
  63. package/dist_ts/security/classes.securitylogger.js +235 -0
  64. package/dist_ts/security/index.d.ts +4 -0
  65. package/dist_ts/security/index.js +5 -0
  66. package/package.json +6 -1
  67. package/readme.md +52 -9
  68. package/ts/00_commitinfo_data.ts +8 -0
  69. package/ts/index.ts +3 -0
  70. package/ts/logger.ts +91 -0
  71. package/ts/mail/core/classes.bouncemanager.ts +731 -0
  72. package/ts/mail/core/classes.email.ts +942 -0
  73. package/ts/mail/core/classes.emailvalidator.ts +239 -0
  74. package/ts/mail/core/classes.templatemanager.ts +320 -0
  75. package/ts/mail/core/index.ts +5 -0
  76. package/ts/mail/delivery/classes.delivery.queue.ts +645 -0
  77. package/ts/mail/delivery/classes.delivery.system.ts +816 -0
  78. package/ts/mail/delivery/classes.unified.rate.limiter.ts +1053 -0
  79. package/ts/mail/delivery/index.ts +5 -0
  80. package/ts/mail/delivery/interfaces.ts +167 -0
  81. package/ts/mail/index.ts +17 -0
  82. package/ts/mail/routing/classes.dkim.manager.ts +157 -0
  83. package/ts/mail/routing/classes.dns.manager.ts +573 -0
  84. package/ts/mail/routing/classes.domain.registry.ts +139 -0
  85. package/ts/mail/routing/classes.email.action.executor.ts +175 -0
  86. package/ts/mail/routing/classes.email.router.ts +575 -0
  87. package/ts/mail/routing/classes.unified.email.server.ts +1207 -0
  88. package/ts/mail/routing/index.ts +9 -0
  89. package/ts/mail/routing/interfaces.ts +202 -0
  90. package/ts/mail/security/classes.dkimcreator.ts +447 -0
  91. package/ts/mail/security/classes.spfverifier.ts +126 -0
  92. package/ts/mail/security/index.ts +3 -0
  93. package/ts/paths.ts +48 -0
  94. package/ts/plugins.ts +53 -0
  95. package/ts/security/classes.contentscanner.ts +400 -0
  96. package/ts/security/classes.ipreputationchecker.ts +315 -0
  97. package/ts/security/classes.rustsecuritybridge.ts +943 -0
  98. package/ts/security/classes.securitylogger.ts +299 -0
  99. package/ts/security/index.ts +40 -0
@@ -0,0 +1,569 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as paths from '../../paths.js';
3
+ import { logger } from '../../logger.js';
4
+ import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
5
+ import { RustSecurityBridge } from '../../security/classes.rustsecuritybridge.js';
6
+ import { LRUCache } from 'lru-cache';
7
+ /**
8
+ * Bounce types for categorizing the reasons for bounces
9
+ */
10
+ export var BounceType;
11
+ (function (BounceType) {
12
+ // Hard bounces (permanent failures)
13
+ BounceType["INVALID_RECIPIENT"] = "invalid_recipient";
14
+ BounceType["DOMAIN_NOT_FOUND"] = "domain_not_found";
15
+ BounceType["MAILBOX_FULL"] = "mailbox_full";
16
+ BounceType["MAILBOX_INACTIVE"] = "mailbox_inactive";
17
+ BounceType["BLOCKED"] = "blocked";
18
+ BounceType["SPAM_RELATED"] = "spam_related";
19
+ BounceType["POLICY_RELATED"] = "policy_related";
20
+ // Soft bounces (temporary failures)
21
+ BounceType["SERVER_UNAVAILABLE"] = "server_unavailable";
22
+ BounceType["TEMPORARY_FAILURE"] = "temporary_failure";
23
+ BounceType["QUOTA_EXCEEDED"] = "quota_exceeded";
24
+ BounceType["NETWORK_ERROR"] = "network_error";
25
+ BounceType["TIMEOUT"] = "timeout";
26
+ // Special cases
27
+ BounceType["AUTO_RESPONSE"] = "auto_response";
28
+ BounceType["CHALLENGE_RESPONSE"] = "challenge_response";
29
+ BounceType["UNKNOWN"] = "unknown";
30
+ })(BounceType || (BounceType = {}));
31
+ /**
32
+ * Hard vs soft bounce classification
33
+ */
34
+ export var BounceCategory;
35
+ (function (BounceCategory) {
36
+ BounceCategory["HARD"] = "hard";
37
+ BounceCategory["SOFT"] = "soft";
38
+ BounceCategory["AUTO_RESPONSE"] = "auto_response";
39
+ BounceCategory["UNKNOWN"] = "unknown";
40
+ })(BounceCategory || (BounceCategory = {}));
41
+ /**
42
+ * Manager for handling email bounces
43
+ */
44
+ export class BounceManager {
45
+ // Retry strategy with exponential backoff
46
+ retryStrategy = {
47
+ maxRetries: 5,
48
+ initialDelay: 15 * 60 * 1000, // 15 minutes
49
+ maxDelay: 24 * 60 * 60 * 1000, // 24 hours
50
+ backoffFactor: 2
51
+ };
52
+ // Store of bounced emails
53
+ bounceStore = [];
54
+ // Cache of recently bounced email addresses to avoid sending to known bad addresses
55
+ bounceCache;
56
+ // Suppression list for addresses that should not receive emails
57
+ suppressionList = new Map();
58
+ storageManager; // StorageManager instance
59
+ constructor(options) {
60
+ // Set retry strategy with defaults
61
+ if (options?.retryStrategy) {
62
+ this.retryStrategy = {
63
+ ...this.retryStrategy,
64
+ ...options.retryStrategy
65
+ };
66
+ }
67
+ // Initialize bounce cache with LRU (least recently used) caching
68
+ this.bounceCache = new LRUCache({
69
+ max: options?.maxCacheSize || 10000,
70
+ ttl: options?.cacheTTL || 30 * 24 * 60 * 60 * 1000, // 30 days default
71
+ });
72
+ // Store storage manager reference
73
+ this.storageManager = options?.storageManager;
74
+ // Load suppression list from storage
75
+ // Note: This is async but we can't await in constructor
76
+ // The suppression list will be loaded asynchronously
77
+ this.loadSuppressionList().catch(error => {
78
+ logger.log('error', `Failed to load suppression list on startup: ${error.message}`);
79
+ });
80
+ }
81
+ /**
82
+ * Process a bounce notification
83
+ * @param bounceData Bounce data to process
84
+ * @returns Processed bounce record
85
+ */
86
+ async processBounce(bounceData) {
87
+ try {
88
+ // Add required fields if missing
89
+ const bounce = {
90
+ id: bounceData.id || plugins.uuid.v4(),
91
+ recipient: bounceData.recipient,
92
+ sender: bounceData.sender,
93
+ domain: bounceData.domain || bounceData.recipient.split('@')[1],
94
+ subject: bounceData.subject,
95
+ bounceType: bounceData.bounceType || BounceType.UNKNOWN,
96
+ bounceCategory: bounceData.bounceCategory || BounceCategory.UNKNOWN,
97
+ timestamp: bounceData.timestamp || Date.now(),
98
+ smtpResponse: bounceData.smtpResponse,
99
+ diagnosticCode: bounceData.diagnosticCode,
100
+ statusCode: bounceData.statusCode,
101
+ headers: bounceData.headers,
102
+ processed: false,
103
+ originalEmailId: bounceData.originalEmailId,
104
+ retryCount: bounceData.retryCount || 0,
105
+ nextRetryTime: bounceData.nextRetryTime
106
+ };
107
+ // Determine bounce type and category via Rust bridge if not provided
108
+ if (!bounceData.bounceType || bounceData.bounceType === BounceType.UNKNOWN) {
109
+ const bridge = RustSecurityBridge.getInstance();
110
+ const rustResult = await bridge.detectBounce({
111
+ smtpResponse: bounce.smtpResponse,
112
+ diagnosticCode: bounce.diagnosticCode,
113
+ statusCode: bounce.statusCode,
114
+ });
115
+ bounce.bounceType = rustResult.bounce_type;
116
+ bounce.bounceCategory = rustResult.category;
117
+ }
118
+ // Process the bounce based on category
119
+ switch (bounce.bounceCategory) {
120
+ case BounceCategory.HARD:
121
+ // Handle hard bounce - add to suppression list
122
+ await this.handleHardBounce(bounce);
123
+ break;
124
+ case BounceCategory.SOFT:
125
+ // Handle soft bounce - schedule retry if eligible
126
+ await this.handleSoftBounce(bounce);
127
+ break;
128
+ case BounceCategory.AUTO_RESPONSE:
129
+ // Handle auto-response - typically no action needed
130
+ logger.log('info', `Auto-response detected for ${bounce.recipient}`);
131
+ break;
132
+ default:
133
+ // Unknown bounce type - log for investigation
134
+ logger.log('warn', `Unknown bounce type for ${bounce.recipient}`, {
135
+ bounceType: bounce.bounceType,
136
+ smtpResponse: bounce.smtpResponse
137
+ });
138
+ break;
139
+ }
140
+ // Store the bounce record
141
+ bounce.processed = true;
142
+ this.bounceStore.push(bounce);
143
+ // Update the bounce cache
144
+ this.updateBounceCache(bounce);
145
+ // Log the bounce
146
+ logger.log(bounce.bounceCategory === BounceCategory.HARD ? 'warn' : 'info', `Email bounce processed: ${bounce.bounceCategory} bounce for ${bounce.recipient}`, {
147
+ bounceType: bounce.bounceType,
148
+ domain: bounce.domain,
149
+ category: bounce.bounceCategory
150
+ });
151
+ // Enhanced security logging
152
+ SecurityLogger.getInstance().logEvent({
153
+ level: bounce.bounceCategory === BounceCategory.HARD
154
+ ? SecurityLogLevel.WARN
155
+ : SecurityLogLevel.INFO,
156
+ type: SecurityEventType.EMAIL_VALIDATION,
157
+ message: `Email bounce detected: ${bounce.bounceCategory} bounce for recipient`,
158
+ domain: bounce.domain,
159
+ details: {
160
+ recipient: bounce.recipient,
161
+ bounceType: bounce.bounceType,
162
+ smtpResponse: bounce.smtpResponse,
163
+ diagnosticCode: bounce.diagnosticCode,
164
+ statusCode: bounce.statusCode
165
+ },
166
+ success: false
167
+ });
168
+ return bounce;
169
+ }
170
+ catch (error) {
171
+ logger.log('error', `Error processing bounce: ${error.message}`, {
172
+ error: error.message,
173
+ bounceData
174
+ });
175
+ throw error;
176
+ }
177
+ }
178
+ /**
179
+ * Process an SMTP failure as a bounce
180
+ * @param recipient Recipient email
181
+ * @param smtpResponse SMTP error response
182
+ * @param options Additional options
183
+ * @returns Processed bounce record
184
+ */
185
+ async processSmtpFailure(recipient, smtpResponse, options = {}) {
186
+ // Create bounce data from SMTP failure
187
+ const bounceData = {
188
+ recipient,
189
+ sender: options.sender || '',
190
+ domain: recipient.split('@')[1],
191
+ smtpResponse,
192
+ statusCode: options.statusCode,
193
+ headers: options.headers,
194
+ originalEmailId: options.originalEmailId,
195
+ timestamp: Date.now()
196
+ };
197
+ // Process as a regular bounce
198
+ return this.processBounce(bounceData);
199
+ }
200
+ /**
201
+ * Process a bounce notification email
202
+ * @param bounceEmail The email containing bounce information
203
+ * @returns Processed bounce record or null if not a bounce
204
+ */
205
+ async processBounceEmail(bounceEmail) {
206
+ try {
207
+ // Check if this is a bounce notification
208
+ const subject = bounceEmail.getSubject();
209
+ const body = bounceEmail.getBody();
210
+ // Check for common bounce notification subject patterns
211
+ const isBounceSubject = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject);
212
+ if (!isBounceSubject) {
213
+ // Not a bounce notification based on subject
214
+ return null;
215
+ }
216
+ // Extract original recipient from the body or headers
217
+ let recipient = '';
218
+ let originalMessageId = '';
219
+ // Extract recipient from common bounce formats
220
+ const recipientMatch = body.match(/(?:failed recipient|to[:=]\s*|recipient:|delivery failed:)\s*<?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>?/i);
221
+ if (recipientMatch && recipientMatch[1]) {
222
+ recipient = recipientMatch[1];
223
+ }
224
+ // Extract diagnostic code
225
+ let diagnosticCode = '';
226
+ const diagnosticMatch = body.match(/diagnostic(?:-|\\s+)code:\s*(.+)(?:\n|$)/i);
227
+ if (diagnosticMatch && diagnosticMatch[1]) {
228
+ diagnosticCode = diagnosticMatch[1].trim();
229
+ }
230
+ // Extract SMTP status code
231
+ let statusCode = '';
232
+ const statusMatch = body.match(/status(?:-|\\s+)code:\s*([0-9.]+)/i);
233
+ if (statusMatch && statusMatch[1]) {
234
+ statusCode = statusMatch[1].trim();
235
+ }
236
+ // If recipient not found in standard patterns, try DSN (Delivery Status Notification) format
237
+ if (!recipient) {
238
+ // Look for DSN format with Original-Recipient or Final-Recipient fields
239
+ const originalRecipientMatch = body.match(/original-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
240
+ const finalRecipientMatch = body.match(/final-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
241
+ if (originalRecipientMatch && originalRecipientMatch[1]) {
242
+ recipient = originalRecipientMatch[1];
243
+ }
244
+ else if (finalRecipientMatch && finalRecipientMatch[1]) {
245
+ recipient = finalRecipientMatch[1];
246
+ }
247
+ }
248
+ // If still no recipient, can't process as bounce
249
+ if (!recipient) {
250
+ logger.log('warn', 'Could not extract recipient from bounce notification', {
251
+ subject,
252
+ sender: bounceEmail.from
253
+ });
254
+ return null;
255
+ }
256
+ // Extract original message ID if available
257
+ const messageIdMatch = body.match(/original[ -]message[ -]id:[ \t]*<?([^>]+)>?/i);
258
+ if (messageIdMatch && messageIdMatch[1]) {
259
+ originalMessageId = messageIdMatch[1].trim();
260
+ }
261
+ // Create bounce data
262
+ const bounceData = {
263
+ recipient,
264
+ sender: bounceEmail.from,
265
+ domain: recipient.split('@')[1],
266
+ subject: bounceEmail.getSubject(),
267
+ diagnosticCode,
268
+ statusCode,
269
+ timestamp: Date.now(),
270
+ headers: {}
271
+ };
272
+ // Process as a regular bounce
273
+ return this.processBounce(bounceData);
274
+ }
275
+ catch (error) {
276
+ logger.log('error', `Error processing bounce email: ${error.message}`);
277
+ return null;
278
+ }
279
+ }
280
+ /**
281
+ * Handle a hard bounce by adding to suppression list
282
+ * @param bounce The bounce record
283
+ */
284
+ async handleHardBounce(bounce) {
285
+ // Add to suppression list permanently (no expiry)
286
+ this.addToSuppressionList(bounce.recipient, `Hard bounce: ${bounce.bounceType}`, undefined);
287
+ // Increment bounce count in cache
288
+ this.updateBounceCache(bounce);
289
+ // Save to permanent storage
290
+ await this.saveBounceRecord(bounce);
291
+ // Log hard bounce for monitoring
292
+ logger.log('warn', `Hard bounce for ${bounce.recipient}: ${bounce.bounceType}`, {
293
+ domain: bounce.domain,
294
+ smtpResponse: bounce.smtpResponse,
295
+ diagnosticCode: bounce.diagnosticCode
296
+ });
297
+ }
298
+ /**
299
+ * Handle a soft bounce by scheduling a retry if eligible
300
+ * @param bounce The bounce record
301
+ */
302
+ async handleSoftBounce(bounce) {
303
+ // Check if we've exceeded max retries
304
+ if (bounce.retryCount >= this.retryStrategy.maxRetries) {
305
+ logger.log('warn', `Max retries exceeded for ${bounce.recipient}, treating as hard bounce`);
306
+ // Convert to hard bounce after max retries
307
+ bounce.bounceCategory = BounceCategory.HARD;
308
+ await this.handleHardBounce(bounce);
309
+ return;
310
+ }
311
+ // Calculate next retry time with exponential backoff
312
+ const delay = Math.min(this.retryStrategy.initialDelay * Math.pow(this.retryStrategy.backoffFactor, bounce.retryCount), this.retryStrategy.maxDelay);
313
+ bounce.retryCount++;
314
+ bounce.nextRetryTime = Date.now() + delay;
315
+ // Add to suppression list temporarily (with expiry)
316
+ this.addToSuppressionList(bounce.recipient, `Soft bounce: ${bounce.bounceType}`, bounce.nextRetryTime);
317
+ // Log the retry schedule
318
+ logger.log('info', `Scheduled retry ${bounce.retryCount} for ${bounce.recipient} at ${new Date(bounce.nextRetryTime).toISOString()}`, {
319
+ bounceType: bounce.bounceType,
320
+ retryCount: bounce.retryCount,
321
+ nextRetry: bounce.nextRetryTime
322
+ });
323
+ }
324
+ /**
325
+ * Add an email address to the suppression list
326
+ * @param email Email address to suppress
327
+ * @param reason Reason for suppression
328
+ * @param expiresAt Expiration timestamp (undefined for permanent)
329
+ */
330
+ addToSuppressionList(email, reason, expiresAt) {
331
+ this.suppressionList.set(email.toLowerCase(), {
332
+ reason,
333
+ timestamp: Date.now(),
334
+ expiresAt
335
+ });
336
+ // Save asynchronously without blocking
337
+ this.saveSuppressionList().catch(error => {
338
+ logger.log('error', `Failed to save suppression list after adding ${email}: ${error.message}`);
339
+ });
340
+ logger.log('info', `Added ${email} to suppression list`, {
341
+ reason,
342
+ expiresAt: expiresAt ? new Date(expiresAt).toISOString() : 'permanent'
343
+ });
344
+ }
345
+ /**
346
+ * Remove an email address from the suppression list
347
+ * @param email Email address to remove
348
+ */
349
+ removeFromSuppressionList(email) {
350
+ const wasRemoved = this.suppressionList.delete(email.toLowerCase());
351
+ if (wasRemoved) {
352
+ // Save asynchronously without blocking
353
+ this.saveSuppressionList().catch(error => {
354
+ logger.log('error', `Failed to save suppression list after removing ${email}: ${error.message}`);
355
+ });
356
+ logger.log('info', `Removed ${email} from suppression list`);
357
+ }
358
+ }
359
+ /**
360
+ * Check if an email is on the suppression list
361
+ * @param email Email address to check
362
+ * @returns Whether the email is suppressed
363
+ */
364
+ isEmailSuppressed(email) {
365
+ const lowercaseEmail = email.toLowerCase();
366
+ const suppression = this.suppressionList.get(lowercaseEmail);
367
+ if (!suppression) {
368
+ return false;
369
+ }
370
+ // Check if suppression has expired
371
+ if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
372
+ this.suppressionList.delete(lowercaseEmail);
373
+ // Save asynchronously without blocking
374
+ this.saveSuppressionList().catch(error => {
375
+ logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`);
376
+ });
377
+ return false;
378
+ }
379
+ return true;
380
+ }
381
+ /**
382
+ * Get suppression information for an email
383
+ * @param email Email address to check
384
+ * @returns Suppression information or null if not suppressed
385
+ */
386
+ getSuppressionInfo(email) {
387
+ const lowercaseEmail = email.toLowerCase();
388
+ const suppression = this.suppressionList.get(lowercaseEmail);
389
+ if (!suppression) {
390
+ return null;
391
+ }
392
+ // Check if suppression has expired
393
+ if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
394
+ this.suppressionList.delete(lowercaseEmail);
395
+ // Save asynchronously without blocking
396
+ this.saveSuppressionList().catch(error => {
397
+ logger.log('error', `Failed to save suppression list after expiry cleanup: ${error.message}`);
398
+ });
399
+ return null;
400
+ }
401
+ return suppression;
402
+ }
403
+ /**
404
+ * Save suppression list to disk
405
+ */
406
+ async saveSuppressionList() {
407
+ try {
408
+ const suppressionData = JSON.stringify(Array.from(this.suppressionList.entries()));
409
+ if (this.storageManager) {
410
+ // Use storage manager
411
+ await this.storageManager.set('/email/bounces/suppression-list.json', suppressionData);
412
+ }
413
+ else {
414
+ // Fall back to filesystem
415
+ await plugins.smartfs.file(plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json')).write(suppressionData);
416
+ }
417
+ }
418
+ catch (error) {
419
+ logger.log('error', `Failed to save suppression list: ${error.message}`);
420
+ }
421
+ }
422
+ /**
423
+ * Load suppression list from disk
424
+ */
425
+ async loadSuppressionList() {
426
+ try {
427
+ let entries = null;
428
+ let needsMigration = false;
429
+ if (this.storageManager) {
430
+ // Try to load from storage manager first
431
+ const suppressionData = await this.storageManager.get('/email/bounces/suppression-list.json');
432
+ if (suppressionData) {
433
+ entries = JSON.parse(suppressionData);
434
+ }
435
+ else {
436
+ // Check if data exists in filesystem and migrate
437
+ const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
438
+ if (plugins.fs.existsSync(suppressionPath)) {
439
+ const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
440
+ entries = JSON.parse(data);
441
+ needsMigration = true;
442
+ logger.log('info', 'Migrating suppression list from filesystem to StorageManager');
443
+ }
444
+ }
445
+ }
446
+ else {
447
+ // No storage manager, use filesystem directly
448
+ const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
449
+ if (plugins.fs.existsSync(suppressionPath)) {
450
+ const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
451
+ entries = JSON.parse(data);
452
+ }
453
+ }
454
+ if (entries) {
455
+ this.suppressionList = new Map(entries);
456
+ // Clean expired entries
457
+ const now = Date.now();
458
+ let expiredCount = 0;
459
+ for (const [email, info] of this.suppressionList.entries()) {
460
+ if (info.expiresAt && now > info.expiresAt) {
461
+ this.suppressionList.delete(email);
462
+ expiredCount++;
463
+ }
464
+ }
465
+ if (expiredCount > 0 || needsMigration) {
466
+ logger.log('info', `Cleaned ${expiredCount} expired entries from suppression list`);
467
+ await this.saveSuppressionList();
468
+ }
469
+ logger.log('info', `Loaded ${this.suppressionList.size} entries from suppression list`);
470
+ }
471
+ }
472
+ catch (error) {
473
+ logger.log('error', `Failed to load suppression list: ${error.message}`);
474
+ }
475
+ }
476
+ /**
477
+ * Save bounce record to disk
478
+ * @param bounce Bounce record to save
479
+ */
480
+ async saveBounceRecord(bounce) {
481
+ try {
482
+ const bounceData = JSON.stringify(bounce, null, 2);
483
+ if (this.storageManager) {
484
+ // Use storage manager
485
+ await this.storageManager.set(`/email/bounces/records/${bounce.id}.json`, bounceData);
486
+ }
487
+ else {
488
+ // Fall back to filesystem
489
+ const bouncePath = plugins.path.join(paths.dataDir, 'emails', 'bounces', `${bounce.id}.json`);
490
+ // Ensure directory exists
491
+ const bounceDir = plugins.path.join(paths.dataDir, 'emails', 'bounces');
492
+ await plugins.smartfs.directory(bounceDir).recursive().create();
493
+ await plugins.smartfs.file(bouncePath).write(bounceData);
494
+ }
495
+ }
496
+ catch (error) {
497
+ logger.log('error', `Failed to save bounce record: ${error.message}`);
498
+ }
499
+ }
500
+ /**
501
+ * Update bounce cache with new bounce information
502
+ * @param bounce Bounce record to update cache with
503
+ */
504
+ updateBounceCache(bounce) {
505
+ const email = bounce.recipient.toLowerCase();
506
+ const existing = this.bounceCache.get(email);
507
+ if (existing) {
508
+ // Update existing cache entry
509
+ existing.lastBounce = bounce.timestamp;
510
+ existing.count++;
511
+ existing.type = bounce.bounceType;
512
+ existing.category = bounce.bounceCategory;
513
+ }
514
+ else {
515
+ // Create new cache entry
516
+ this.bounceCache.set(email, {
517
+ lastBounce: bounce.timestamp,
518
+ count: 1,
519
+ type: bounce.bounceType,
520
+ category: bounce.bounceCategory
521
+ });
522
+ }
523
+ }
524
+ /**
525
+ * Check bounce history for an email address
526
+ * @param email Email address to check
527
+ * @returns Bounce information or null if no bounces
528
+ */
529
+ getBounceInfo(email) {
530
+ return this.bounceCache.get(email.toLowerCase()) || null;
531
+ }
532
+ /**
533
+ * Get all known hard bounced addresses
534
+ * @returns Array of hard bounced email addresses
535
+ */
536
+ getHardBouncedAddresses() {
537
+ const hardBounced = [];
538
+ for (const [email, info] of this.bounceCache.entries()) {
539
+ if (info.category === BounceCategory.HARD) {
540
+ hardBounced.push(email);
541
+ }
542
+ }
543
+ return hardBounced;
544
+ }
545
+ /**
546
+ * Get suppression list
547
+ * @returns Array of suppressed email addresses
548
+ */
549
+ getSuppressionList() {
550
+ return Array.from(this.suppressionList.keys());
551
+ }
552
+ /**
553
+ * Clear old bounce records (for maintenance)
554
+ * @param olderThan Timestamp to remove records older than
555
+ * @returns Number of records removed
556
+ */
557
+ clearOldBounceRecords(olderThan) {
558
+ let removed = 0;
559
+ this.bounceStore = this.bounceStore.filter(bounce => {
560
+ if (bounce.timestamp < olderThan) {
561
+ removed++;
562
+ return false;
563
+ }
564
+ return true;
565
+ });
566
+ return removed;
567
+ }
568
+ }
569
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ib3VuY2VtYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvbWFpbC9jb3JlL2NsYXNzZXMuYm91bmNlbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM5RixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw4Q0FBOEMsQ0FBQztBQUNsRixPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBR3JDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksVUFxQlg7QUFyQkQsV0FBWSxVQUFVO0lBQ3BCLG9DQUFvQztJQUNwQyxxREFBdUMsQ0FBQTtJQUN2QyxtREFBcUMsQ0FBQTtJQUNyQywyQ0FBNkIsQ0FBQTtJQUM3QixtREFBcUMsQ0FBQTtJQUNyQyxpQ0FBbUIsQ0FBQTtJQUNuQiwyQ0FBNkIsQ0FBQTtJQUM3QiwrQ0FBaUMsQ0FBQTtJQUVqQyxvQ0FBb0M7SUFDcEMsdURBQXlDLENBQUE7SUFDekMscURBQXVDLENBQUE7SUFDdkMsK0NBQWlDLENBQUE7SUFDakMsNkNBQStCLENBQUE7SUFDL0IsaUNBQW1CLENBQUE7SUFFbkIsZ0JBQWdCO0lBQ2hCLDZDQUErQixDQUFBO0lBQy9CLHVEQUF5QyxDQUFBO0lBQ3pDLGlDQUFtQixDQUFBO0FBQ3JCLENBQUMsRUFyQlcsVUFBVSxLQUFWLFVBQVUsUUFxQnJCO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLENBQU4sSUFBWSxjQUtYO0FBTEQsV0FBWSxjQUFjO0lBQ3hCLCtCQUFhLENBQUE7SUFDYiwrQkFBYSxD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