@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,239 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { logger } from '../../logger.js';
3
+ import { LRUCache } from 'lru-cache';
4
+
5
+ export interface IEmailValidationResult {
6
+ isValid: boolean;
7
+ hasMx: boolean;
8
+ hasSpamMarkings: boolean;
9
+ score: number;
10
+ details?: {
11
+ formatValid?: boolean;
12
+ mxRecords?: string[];
13
+ disposable?: boolean;
14
+ role?: boolean;
15
+ spamIndicators?: string[];
16
+ errorMessage?: string;
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Advanced email validator class using smartmail's capabilities
22
+ */
23
+ export class EmailValidator {
24
+ private validator: plugins.smartmail.EmailAddressValidator;
25
+ private dnsCache: LRUCache<string, string[]>;
26
+
27
+ constructor(options?: {
28
+ maxCacheSize?: number;
29
+ cacheTTL?: number;
30
+ }) {
31
+ this.validator = new plugins.smartmail.EmailAddressValidator();
32
+
33
+ // Initialize LRU cache for DNS records
34
+ this.dnsCache = new LRUCache<string, string[]>({
35
+ // Default to 1000 entries (reasonable for most applications)
36
+ max: options?.maxCacheSize || 1000,
37
+ // Default TTL of 1 hour (DNS records don't change frequently)
38
+ ttl: options?.cacheTTL || 60 * 60 * 1000,
39
+ // Optional cache monitoring
40
+ allowStale: false,
41
+ updateAgeOnGet: true,
42
+ // Add logging for cache events in production environments
43
+ disposeAfter: (value, key) => {
44
+ logger.log('debug', `DNS cache entry expired for domain: ${key}`);
45
+ },
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Validates an email address using comprehensive checks
51
+ * @param email The email to validate
52
+ * @param options Validation options
53
+ * @returns Validation result with details
54
+ */
55
+ public async validate(
56
+ email: string,
57
+ options: {
58
+ checkMx?: boolean;
59
+ checkDisposable?: boolean;
60
+ checkRole?: boolean;
61
+ checkSyntaxOnly?: boolean;
62
+ } = {}
63
+ ): Promise<IEmailValidationResult> {
64
+ try {
65
+ const result: IEmailValidationResult = {
66
+ isValid: false,
67
+ hasMx: false,
68
+ hasSpamMarkings: false,
69
+ score: 0,
70
+ details: {
71
+ formatValid: false,
72
+ spamIndicators: []
73
+ }
74
+ };
75
+
76
+ // Always check basic format
77
+ result.details.formatValid = this.validator.isValidEmailFormat(email);
78
+ if (!result.details.formatValid) {
79
+ result.details.errorMessage = 'Invalid email format';
80
+ return result;
81
+ }
82
+
83
+ // If syntax-only check is requested, return early
84
+ if (options.checkSyntaxOnly) {
85
+ result.isValid = true;
86
+ result.score = 0.5;
87
+ return result;
88
+ }
89
+
90
+ // Get domain for additional checks
91
+ const domain = email.split('@')[1];
92
+
93
+ // Check MX records
94
+ if (options.checkMx !== false) {
95
+ try {
96
+ const mxRecords = await this.getMxRecords(domain);
97
+ result.details.mxRecords = mxRecords;
98
+ result.hasMx = mxRecords && mxRecords.length > 0;
99
+
100
+ if (!result.hasMx) {
101
+ result.details.spamIndicators.push('No MX records');
102
+ result.details.errorMessage = 'Domain has no MX records';
103
+ }
104
+ } catch (error) {
105
+ logger.log('error', `Error checking MX records: ${error.message}`);
106
+ result.details.errorMessage = 'Unable to check MX records';
107
+ }
108
+ }
109
+
110
+ // Check if domain is disposable
111
+ if (options.checkDisposable !== false) {
112
+ result.details.disposable = await this.validator.isDisposableEmail(email);
113
+ if (result.details.disposable) {
114
+ result.details.spamIndicators.push('Disposable email');
115
+ }
116
+ }
117
+
118
+ // Check if email is a role account
119
+ if (options.checkRole !== false) {
120
+ result.details.role = this.validator.isRoleAccount(email);
121
+ if (result.details.role) {
122
+ result.details.spamIndicators.push('Role account');
123
+ }
124
+ }
125
+
126
+ // Calculate spam score and final validity
127
+ result.hasSpamMarkings = result.details.spamIndicators.length > 0;
128
+
129
+ // Calculate a score between 0-1 based on checks
130
+ let scoreFactors = 0;
131
+ let scoreTotal = 0;
132
+
133
+ // Format check (highest weight)
134
+ scoreFactors += 0.4;
135
+ if (result.details.formatValid) scoreTotal += 0.4;
136
+
137
+ // MX check (high weight)
138
+ if (options.checkMx !== false) {
139
+ scoreFactors += 0.3;
140
+ if (result.hasMx) scoreTotal += 0.3;
141
+ }
142
+
143
+ // Disposable check (medium weight)
144
+ if (options.checkDisposable !== false) {
145
+ scoreFactors += 0.2;
146
+ if (!result.details.disposable) scoreTotal += 0.2;
147
+ }
148
+
149
+ // Role account check (low weight)
150
+ if (options.checkRole !== false) {
151
+ scoreFactors += 0.1;
152
+ if (!result.details.role) scoreTotal += 0.1;
153
+ }
154
+
155
+ // Normalize score based on factors actually checked
156
+ result.score = scoreFactors > 0 ? scoreTotal / scoreFactors : 0;
157
+
158
+ // Email is valid if score is above 0.7 (configurable threshold)
159
+ result.isValid = result.score >= 0.7;
160
+
161
+ return result;
162
+ } catch (error) {
163
+ logger.log('error', `Email validation error: ${error.message}`);
164
+ return {
165
+ isValid: false,
166
+ hasMx: false,
167
+ hasSpamMarkings: true,
168
+ score: 0,
169
+ details: {
170
+ formatValid: false,
171
+ errorMessage: `Validation error: ${error.message}`,
172
+ spamIndicators: ['Validation error']
173
+ }
174
+ };
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Gets MX records for a domain with caching
180
+ * @param domain Domain to check
181
+ * @returns Array of MX records
182
+ */
183
+ private async getMxRecords(domain: string): Promise<string[]> {
184
+ // Check cache first
185
+ const cachedRecords = this.dnsCache.get(domain);
186
+ if (cachedRecords) {
187
+ logger.log('debug', `Using cached MX records for domain: ${domain}`);
188
+ return cachedRecords;
189
+ }
190
+
191
+ try {
192
+ // Use smartmail's getMxRecords method
193
+ const records = await this.validator.getMxRecords(domain);
194
+
195
+ // Store in cache (TTL is handled by the LRU cache configuration)
196
+ this.dnsCache.set(domain, records);
197
+ logger.log('debug', `Cached MX records for domain: ${domain}`);
198
+
199
+ return records;
200
+ } catch (error) {
201
+ logger.log('error', `Error fetching MX records for ${domain}: ${error.message}`);
202
+ return [];
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Validates multiple email addresses in batch
208
+ * @param emails Array of emails to validate
209
+ * @param options Validation options
210
+ * @returns Object with email addresses as keys and validation results as values
211
+ */
212
+ public async validateBatch(
213
+ emails: string[],
214
+ options: {
215
+ checkMx?: boolean;
216
+ checkDisposable?: boolean;
217
+ checkRole?: boolean;
218
+ checkSyntaxOnly?: boolean;
219
+ } = {}
220
+ ): Promise<Record<string, IEmailValidationResult>> {
221
+ const results: Record<string, IEmailValidationResult> = {};
222
+
223
+ for (const email of emails) {
224
+ results[email] = await this.validate(email, options);
225
+ }
226
+
227
+ return results;
228
+ }
229
+
230
+ /**
231
+ * Quick check if an email format is valid (synchronous, no DNS checks)
232
+ * @param email Email to check
233
+ * @returns Boolean indicating if format is valid
234
+ */
235
+ public isValidFormat(email: string): boolean {
236
+ return this.validator.isValidEmailFormat(email);
237
+ }
238
+
239
+ }
@@ -0,0 +1,320 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import * as paths from '../../paths.js';
3
+ import { logger } from '../../logger.js';
4
+ import { Email, type IEmailOptions, type IAttachment } from './classes.email.js';
5
+
6
+ /**
7
+ * Email template type definition
8
+ */
9
+ export interface IEmailTemplate<T = any> {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ from: string;
14
+ subject: string;
15
+ bodyHtml: string;
16
+ bodyText?: string;
17
+ category?: string;
18
+ sampleData?: T;
19
+ attachments?: Array<{
20
+ name: string;
21
+ path: string;
22
+ contentType?: string;
23
+ }>;
24
+ }
25
+
26
+ /**
27
+ * Email template context - data used to render the template
28
+ */
29
+ export interface ITemplateContext {
30
+ [key: string]: any;
31
+ }
32
+
33
+ /**
34
+ * Template category definitions
35
+ */
36
+ export enum TemplateCategory {
37
+ NOTIFICATION = 'notification',
38
+ TRANSACTIONAL = 'transactional',
39
+ MARKETING = 'marketing',
40
+ SYSTEM = 'system'
41
+ }
42
+
43
+ /**
44
+ * Enhanced template manager using Email class for template rendering
45
+ */
46
+ export class TemplateManager {
47
+ private templates: Map<string, IEmailTemplate> = new Map();
48
+ private defaultConfig: {
49
+ from: string;
50
+ replyTo?: string;
51
+ footerHtml?: string;
52
+ footerText?: string;
53
+ };
54
+
55
+ constructor(defaultConfig?: {
56
+ from?: string;
57
+ replyTo?: string;
58
+ footerHtml?: string;
59
+ footerText?: string;
60
+ }) {
61
+ // Set default configuration
62
+ this.defaultConfig = {
63
+ from: defaultConfig?.from || 'noreply@mail.lossless.com',
64
+ replyTo: defaultConfig?.replyTo,
65
+ footerHtml: defaultConfig?.footerHtml || '',
66
+ footerText: defaultConfig?.footerText || ''
67
+ };
68
+
69
+ // Initialize with built-in templates
70
+ this.registerBuiltinTemplates();
71
+ }
72
+
73
+ /**
74
+ * Register built-in email templates
75
+ */
76
+ private registerBuiltinTemplates(): void {
77
+ // Welcome email
78
+ this.registerTemplate<{
79
+ firstName: string;
80
+ accountUrl: string;
81
+ }>({
82
+ id: 'welcome',
83
+ name: 'Welcome Email',
84
+ description: 'Sent to users when they first sign up',
85
+ from: this.defaultConfig.from,
86
+ subject: 'Welcome to {{serviceName}}!',
87
+ category: TemplateCategory.TRANSACTIONAL,
88
+ bodyHtml: `
89
+ <h1>Welcome, {{firstName}}!</h1>
90
+ <p>Thank you for joining {{serviceName}}. We're excited to have you on board.</p>
91
+ <p>To get started, <a href="{{accountUrl}}">visit your account</a>.</p>
92
+ `,
93
+ bodyText:
94
+ `Welcome, {{firstName}}!
95
+
96
+ Thank you for joining {{serviceName}}. We're excited to have you on board.
97
+
98
+ To get started, visit your account: {{accountUrl}}
99
+ `,
100
+ sampleData: {
101
+ firstName: 'John',
102
+ accountUrl: 'https://example.com/account'
103
+ }
104
+ });
105
+
106
+ // Password reset
107
+ this.registerTemplate<{
108
+ resetUrl: string;
109
+ expiryHours: number;
110
+ }>({
111
+ id: 'password-reset',
112
+ name: 'Password Reset',
113
+ description: 'Sent when a user requests a password reset',
114
+ from: this.defaultConfig.from,
115
+ subject: 'Password Reset Request',
116
+ category: TemplateCategory.TRANSACTIONAL,
117
+ bodyHtml: `
118
+ <h2>Password Reset Request</h2>
119
+ <p>You recently requested to reset your password. Click the link below to reset it:</p>
120
+ <p><a href="{{resetUrl}}">Reset Password</a></p>
121
+ <p>This link will expire in {{expiryHours}} hours.</p>
122
+ <p>If you didn't request a password reset, please ignore this email.</p>
123
+ `,
124
+ sampleData: {
125
+ resetUrl: 'https://example.com/reset-password?token=abc123',
126
+ expiryHours: 24
127
+ }
128
+ });
129
+
130
+ // System notification
131
+ this.registerTemplate({
132
+ id: 'system-notification',
133
+ name: 'System Notification',
134
+ description: 'General system notification template',
135
+ from: this.defaultConfig.from,
136
+ subject: '{{subject}}',
137
+ category: TemplateCategory.SYSTEM,
138
+ bodyHtml: `
139
+ <h2>{{title}}</h2>
140
+ <div>{{message}}</div>
141
+ `,
142
+ sampleData: {
143
+ subject: 'Important System Notification',
144
+ title: 'System Maintenance',
145
+ message: 'The system will be undergoing maintenance on Saturday from 2-4am UTC.'
146
+ }
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Register a new email template
152
+ * @param template The email template to register
153
+ */
154
+ public registerTemplate<T = any>(template: IEmailTemplate<T>): void {
155
+ if (this.templates.has(template.id)) {
156
+ logger.log('warn', `Template with ID '${template.id}' already exists and will be overwritten`);
157
+ }
158
+
159
+ // Add footer to templates if configured
160
+ if (this.defaultConfig.footerHtml && template.bodyHtml) {
161
+ template.bodyHtml += this.defaultConfig.footerHtml;
162
+ }
163
+
164
+ if (this.defaultConfig.footerText && template.bodyText) {
165
+ template.bodyText += this.defaultConfig.footerText;
166
+ }
167
+
168
+ this.templates.set(template.id, template);
169
+ logger.log('info', `Registered email template: ${template.id}`);
170
+ }
171
+
172
+ /**
173
+ * Get an email template by ID
174
+ * @param templateId The template ID
175
+ * @returns The template or undefined if not found
176
+ */
177
+ public getTemplate<T = any>(templateId: string): IEmailTemplate<T> | undefined {
178
+ return this.templates.get(templateId) as IEmailTemplate<T>;
179
+ }
180
+
181
+ /**
182
+ * List all available templates
183
+ * @param category Optional category filter
184
+ * @returns Array of email templates
185
+ */
186
+ public listTemplates(category?: TemplateCategory): IEmailTemplate[] {
187
+ const templates = Array.from(this.templates.values());
188
+ if (category) {
189
+ return templates.filter(template => template.category === category);
190
+ }
191
+ return templates;
192
+ }
193
+
194
+ /**
195
+ * Create an Email instance from a template
196
+ * @param templateId The template ID
197
+ * @param context The template context data
198
+ * @returns A configured Email instance
199
+ */
200
+ public async createEmail<T = any>(
201
+ templateId: string,
202
+ context?: ITemplateContext
203
+ ): Promise<Email> {
204
+ const template = this.getTemplate(templateId);
205
+
206
+ if (!template) {
207
+ throw new Error(`Template with ID '${templateId}' not found`);
208
+ }
209
+
210
+ // Build attachments array for Email
211
+ const attachments: IAttachment[] = [];
212
+
213
+ if (template.attachments && template.attachments.length > 0) {
214
+ for (const attachment of template.attachments) {
215
+ try {
216
+ const attachmentPath = plugins.path.isAbsolute(attachment.path)
217
+ ? attachment.path
218
+ : plugins.path.join(paths.MtaAttachmentsDir, attachment.path);
219
+
220
+ // Read the file
221
+ const fileBuffer = await plugins.fs.promises.readFile(attachmentPath);
222
+
223
+ attachments.push({
224
+ filename: attachment.name,
225
+ content: fileBuffer,
226
+ contentType: attachment.contentType || 'application/octet-stream'
227
+ });
228
+ } catch (error) {
229
+ logger.log('error', `Failed to add attachment '${attachment.name}': ${error.message}`);
230
+ }
231
+ }
232
+ }
233
+
234
+ // Create Email instance with template content
235
+ const emailOptions: IEmailOptions = {
236
+ from: template.from || this.defaultConfig.from,
237
+ subject: template.subject,
238
+ text: template.bodyText || '',
239
+ html: template.bodyHtml,
240
+ // Note: 'to' is intentionally omitted for templates
241
+ attachments,
242
+ variables: context || {}
243
+ };
244
+
245
+ return new Email(emailOptions);
246
+ }
247
+
248
+ /**
249
+ * Create and completely process an Email instance from a template
250
+ * @param templateId The template ID
251
+ * @param context The template context data
252
+ * @returns A complete, processed Email instance ready to send
253
+ */
254
+ public async prepareEmail<T = any>(
255
+ templateId: string,
256
+ context: ITemplateContext = {}
257
+ ): Promise<Email> {
258
+ const email = await this.createEmail<T>(templateId, context);
259
+
260
+ // Email class processes variables when needed, no pre-compilation required
261
+
262
+ return email;
263
+ }
264
+
265
+ /**
266
+ * Create a MIME-formatted email from a template
267
+ * @param templateId The template ID
268
+ * @param context The template context data
269
+ * @returns A MIME-formatted email string
270
+ */
271
+ public async createMimeEmail(
272
+ templateId: string,
273
+ context: ITemplateContext = {}
274
+ ): Promise<string> {
275
+ const email = await this.prepareEmail(templateId, context);
276
+ return email.toRFC822String(context);
277
+ }
278
+
279
+
280
+ /**
281
+ * Load templates from a directory
282
+ * @param directory The directory containing template JSON files
283
+ */
284
+ public async loadTemplatesFromDirectory(directory: string): Promise<void> {
285
+ try {
286
+ // Ensure directory exists
287
+ if (!plugins.fs.existsSync(directory)) {
288
+ logger.log('error', `Template directory does not exist: ${directory}`);
289
+ return;
290
+ }
291
+
292
+ // Get all JSON files
293
+ const files = plugins.fs.readdirSync(directory)
294
+ .filter(file => file.endsWith('.json'));
295
+
296
+ for (const file of files) {
297
+ try {
298
+ const filePath = plugins.path.join(directory, file);
299
+ const content = plugins.fs.readFileSync(filePath, 'utf8');
300
+ const template = JSON.parse(content) as IEmailTemplate;
301
+
302
+ // Validate template
303
+ if (!template.id || !template.subject || (!template.bodyHtml && !template.bodyText)) {
304
+ logger.log('warn', `Invalid template in ${file}: missing required fields`);
305
+ continue;
306
+ }
307
+
308
+ this.registerTemplate(template);
309
+ } catch (error) {
310
+ logger.log('error', `Error loading template from ${file}: ${error.message}`);
311
+ }
312
+ }
313
+
314
+ logger.log('info', `Loaded ${this.templates.size} email templates`);
315
+ } catch (error) {
316
+ logger.log('error', `Failed to load templates from directory: ${error.message}`);
317
+ throw error;
318
+ }
319
+ }
320
+ }
@@ -0,0 +1,5 @@
1
+ // Core email components
2
+ export * from './classes.email.js';
3
+ export * from './classes.emailvalidator.js';
4
+ export * from './classes.templatemanager.js';
5
+ export * from './classes.bouncemanager.js';