@bernierllc/email-manager 0.1.1 → 0.1.4

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.
@@ -0,0 +1,866 @@
1
+ /*
2
+ Copyright (c) 2025 Bernier LLC
3
+
4
+ This file is licensed to the client under a limited-use license.
5
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
6
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
+ */
8
+ import { EmailManager } from './email-manager.js';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+ /**
11
+ * EnhancedEmailManager - Extended email management with template service,
12
+ * sender management, and variable validation integration.
13
+ *
14
+ * This class extends EmailManager to provide:
15
+ * - Advanced template management
16
+ * - Sender configuration and selection
17
+ * - Template variable validation
18
+ * - System bootstrap and health monitoring
19
+ *
20
+ * Note: This implementation provides built-in functionality that can be
21
+ * replaced with external services when available.
22
+ */
23
+ export class EnhancedEmailManager extends EmailManager {
24
+ constructor(config) {
25
+ super(config);
26
+ this.templateStore = new Map();
27
+ this.senderStore = new Map();
28
+ this.variableContexts = new Map();
29
+ this.initialized = false;
30
+ this.enhancedConfig = config;
31
+ }
32
+ // ======================
33
+ // Enhanced Email Sending
34
+ // ======================
35
+ /**
36
+ * Send a templated email with variable validation and smart sender selection
37
+ */
38
+ async sendEnhancedTemplatedEmail(templateId, templateData, recipients, options = {}) {
39
+ const results = [];
40
+ try {
41
+ // 1. Get template from internal store
42
+ const template = this.templateStore.get(templateId);
43
+ if (!template) {
44
+ throw new Error(`Template not found: ${templateId}`);
45
+ }
46
+ // 2. Validate template variables if enabled
47
+ if (options.validateVariables && template.context) {
48
+ const validation = await this.validateTemplateData(template.context, templateData);
49
+ if (!validation.isValid && options.strictMode) {
50
+ return [{
51
+ success: false,
52
+ provider: 'none',
53
+ sentAt: new Date(),
54
+ errors: validation.errors.map(e => ({
55
+ code: 'VALIDATION_ERROR',
56
+ message: e.message
57
+ }))
58
+ }];
59
+ }
60
+ }
61
+ // 3. Send to all recipients
62
+ for (const recipient of recipients) {
63
+ // Select best sender for this recipient
64
+ let sender = options.senderOverride;
65
+ if (!sender && options.selectBestSender) {
66
+ const selectedSender = await this.selectBestSender(recipient.email, options.preferredProvider, { allowUnverified: false });
67
+ sender = selectedSender || undefined;
68
+ }
69
+ // Create email data
70
+ const emailData = {
71
+ to: recipient.email,
72
+ subject: this.renderSimpleTemplate(template.subject, templateData),
73
+ html: this.renderSimpleTemplate(template.htmlContent, templateData),
74
+ text: template.textContent ? this.renderSimpleTemplate(template.textContent, templateData) : undefined,
75
+ templateId,
76
+ metadata: {
77
+ templateVersion: template.version,
78
+ senderId: sender?.id,
79
+ context: template.context,
80
+ recipientMetadata: recipient.metadata
81
+ }
82
+ };
83
+ // Send email
84
+ const result = await this.sendEmail(emailData);
85
+ results.push(result);
86
+ }
87
+ return results;
88
+ }
89
+ catch (error) {
90
+ return [{
91
+ success: false,
92
+ provider: 'none',
93
+ sentAt: new Date(),
94
+ errors: [{
95
+ code: 'ENHANCED_SEND_ERROR',
96
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
97
+ }]
98
+ }];
99
+ }
100
+ }
101
+ /**
102
+ * Send email with variable validation
103
+ */
104
+ async sendWithVariableValidation(email, context, _options) {
105
+ if (context && email.templateContext) {
106
+ const validation = await this.validateTemplateData(context, email.templateContext);
107
+ if (!validation.isValid) {
108
+ return {
109
+ success: false,
110
+ provider: 'none',
111
+ sentAt: new Date(),
112
+ errors: validation.errors.map(e => ({
113
+ code: 'VALIDATION_ERROR',
114
+ message: e.message
115
+ }))
116
+ };
117
+ }
118
+ }
119
+ return await this.sendEmail(email);
120
+ }
121
+ // ======================
122
+ // Template Management
123
+ // ======================
124
+ /**
125
+ * Create a template with validation
126
+ */
127
+ async createEnhancedTemplate(template) {
128
+ try {
129
+ // Validate template if context is provided
130
+ if (template.context) {
131
+ const validation = await this.validateEnhancedTemplate(template.htmlContent, template.context);
132
+ if (!validation.isValid) {
133
+ return {
134
+ success: false,
135
+ errors: validation.errors.map(e => e.message)
136
+ };
137
+ }
138
+ }
139
+ const id = uuidv4();
140
+ const now = new Date();
141
+ const internalTemplate = {
142
+ id,
143
+ name: template.name,
144
+ subject: template.subject,
145
+ htmlContent: template.htmlContent,
146
+ textContent: template.textContent,
147
+ context: template.context,
148
+ category: template.category,
149
+ version: '1.0.0',
150
+ isActive: true,
151
+ createdAt: now,
152
+ updatedAt: now
153
+ };
154
+ this.templateStore.set(id, internalTemplate);
155
+ return { success: true, templateId: id };
156
+ }
157
+ catch (error) {
158
+ return {
159
+ success: false,
160
+ errors: [error instanceof Error ? error.message : 'Failed to create template']
161
+ };
162
+ }
163
+ }
164
+ /**
165
+ * Get a template by ID from enhanced store
166
+ */
167
+ async getEnhancedTemplate(templateId) {
168
+ return this.templateStore.get(templateId) || null;
169
+ }
170
+ /**
171
+ * List all enhanced templates
172
+ */
173
+ async listEnhancedTemplates() {
174
+ const items = Array.from(this.templateStore.values());
175
+ return { items, total: items.length };
176
+ }
177
+ /**
178
+ * Sync a template to providers (placeholder for future integration)
179
+ */
180
+ async syncTemplate(templateId, providerId) {
181
+ const template = this.templateStore.get(templateId);
182
+ if (!template) {
183
+ return {
184
+ success: false,
185
+ action: 'failed',
186
+ localTemplateId: templateId,
187
+ error: 'Template not found'
188
+ };
189
+ }
190
+ // Placeholder - would integrate with actual provider plugins
191
+ return {
192
+ success: true,
193
+ action: providerId ? 'updated' : 'synced_all',
194
+ localTemplateId: templateId,
195
+ providerId,
196
+ metadata: { note: 'Sync placeholder - integrate with provider plugins' }
197
+ };
198
+ }
199
+ /**
200
+ * Validate a template against a variable context
201
+ */
202
+ async validateEnhancedTemplate(templateContent, context) {
203
+ const variableContext = this.variableContexts.get(context);
204
+ if (!variableContext) {
205
+ return {
206
+ isValid: true,
207
+ errors: [],
208
+ warnings: [],
209
+ info: [`No variable context '${context}' registered, validation skipped`]
210
+ };
211
+ }
212
+ // Extract variables from template (simple Handlebars-style extraction)
213
+ const variablePattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g;
214
+ const usedVariables = [];
215
+ let match;
216
+ while ((match = variablePattern.exec(templateContent)) !== null) {
217
+ usedVariables.push(match[1]);
218
+ }
219
+ // Check for required variables
220
+ const errors = [];
221
+ const definedPaths = variableContext.variables.map(v => v.path);
222
+ for (const variable of usedVariables) {
223
+ if (!definedPaths.includes(variable) && !this.isNestedPath(variable, definedPaths)) {
224
+ errors.push({
225
+ message: `Undefined variable: ${variable}`,
226
+ path: variable
227
+ });
228
+ }
229
+ }
230
+ return {
231
+ isValid: errors.length === 0,
232
+ errors,
233
+ warnings: []
234
+ };
235
+ }
236
+ /**
237
+ * Sync all templates to providers
238
+ */
239
+ async syncAllTemplates() {
240
+ const templates = Array.from(this.templateStore.values());
241
+ const results = [];
242
+ for (const template of templates) {
243
+ const result = await this.syncTemplate(template.id);
244
+ results.push(result);
245
+ }
246
+ const synced = results.filter(r => r.success).length;
247
+ return {
248
+ success: synced > 0,
249
+ total: results.length,
250
+ synced,
251
+ failed: results.length - synced,
252
+ results
253
+ };
254
+ }
255
+ // ======================
256
+ // Sender Management
257
+ // ======================
258
+ /**
259
+ * Create a new sender configuration
260
+ */
261
+ async createSender(sender) {
262
+ try {
263
+ // Validate email format
264
+ if (!this.validateEmail(sender.fromEmail)) {
265
+ throw new Error(`Invalid email format: ${sender.fromEmail}`);
266
+ }
267
+ const id = uuidv4();
268
+ const now = new Date();
269
+ const internalSender = {
270
+ id,
271
+ name: sender.name,
272
+ fromEmail: sender.fromEmail,
273
+ fromName: sender.fromName,
274
+ replyToEmail: sender.replyToEmail,
275
+ provider: sender.provider,
276
+ priority: sender.priority ?? 100,
277
+ isDefault: sender.isDefault ?? false,
278
+ isActive: true,
279
+ isVerified: sender.isVerified ?? false,
280
+ verificationStatus: sender.verificationStatus ?? 'pending',
281
+ createdAt: now,
282
+ updatedAt: now
283
+ };
284
+ this.senderStore.set(id, internalSender);
285
+ return this.mapToEnhancedSender(internalSender);
286
+ }
287
+ catch (error) {
288
+ console.error('Failed to create sender:', error);
289
+ return null;
290
+ }
291
+ }
292
+ /**
293
+ * Get a sender by ID
294
+ */
295
+ async getSender(senderId) {
296
+ const sender = this.senderStore.get(senderId);
297
+ return sender ? this.mapToEnhancedSender(sender) : null;
298
+ }
299
+ /**
300
+ * List all senders
301
+ */
302
+ async listSenders() {
303
+ const items = Array.from(this.senderStore.values()).map(s => this.mapToEnhancedSender(s));
304
+ return { items, total: items.length };
305
+ }
306
+ /**
307
+ * Select the best sender for an email
308
+ */
309
+ async selectBestSender(fromEmail, provider, options = {}) {
310
+ const senders = Array.from(this.senderStore.values())
311
+ .filter(s => s.isActive)
312
+ .filter(s => !options.excludeIds?.includes(s.id))
313
+ .filter(s => options.allowUnverified || s.isVerified)
314
+ .filter(s => !provider || s.provider === provider);
315
+ if (senders.length === 0) {
316
+ return null;
317
+ }
318
+ // Strategy selection
319
+ let selected;
320
+ const strategy = options.strategy || 'domain_match';
321
+ if (strategy === 'domain_match') {
322
+ // Try to match domain
323
+ const domain = this.extractDomain(fromEmail);
324
+ selected = senders.find(s => this.extractDomain(s.fromEmail) === domain);
325
+ }
326
+ // Fallback to priority-based selection
327
+ if (!selected) {
328
+ senders.sort((a, b) => a.priority - b.priority);
329
+ selected = senders[0];
330
+ }
331
+ return selected ? this.mapToEnhancedSender(selected) : null;
332
+ }
333
+ /**
334
+ * Validate a sender configuration
335
+ */
336
+ async validateSender(senderId) {
337
+ const sender = this.senderStore.get(senderId);
338
+ if (!sender) {
339
+ return {
340
+ isValid: false,
341
+ errors: [{ field: 'id', message: `Sender not found: ${senderId}` }],
342
+ warnings: []
343
+ };
344
+ }
345
+ const errors = [];
346
+ const warnings = [];
347
+ // Basic validation
348
+ if (!sender.fromEmail) {
349
+ errors.push({ field: 'fromEmail', message: 'From email is required' });
350
+ }
351
+ else if (!this.validateEmail(sender.fromEmail)) {
352
+ errors.push({ field: 'fromEmail', message: 'Invalid email format' });
353
+ }
354
+ if (!sender.fromName) {
355
+ warnings.push({ field: 'fromName', message: 'From name is recommended' });
356
+ }
357
+ if (!sender.isVerified) {
358
+ warnings.push({ field: 'isVerified', message: 'Sender is not verified' });
359
+ }
360
+ return {
361
+ isValid: errors.length === 0,
362
+ errors,
363
+ warnings,
364
+ metadata: {
365
+ lastValidated: new Date(),
366
+ validatedBy: 'email-manager',
367
+ checks: ['basic', 'email-format', 'verification-status']
368
+ }
369
+ };
370
+ }
371
+ // ======================
372
+ // Variable Management
373
+ // ======================
374
+ /**
375
+ * Register a variable context
376
+ */
377
+ async registerVariableContext(name, contextDef) {
378
+ this.variableContexts.set(name, {
379
+ name,
380
+ description: contextDef.description,
381
+ variables: contextDef.variables
382
+ });
383
+ }
384
+ /**
385
+ * Get a variable context
386
+ */
387
+ async getVariableContext(name) {
388
+ return this.variableContexts.get(name) || null;
389
+ }
390
+ /**
391
+ * Get variable suggestions for a context
392
+ */
393
+ async getVariableSuggestions(context, prefix) {
394
+ const variableContext = this.variableContexts.get(context);
395
+ if (!variableContext) {
396
+ return [];
397
+ }
398
+ let variables = variableContext.variables;
399
+ if (prefix) {
400
+ variables = variables.filter(v => v.path.startsWith(prefix));
401
+ }
402
+ return variables.map((v, index) => ({
403
+ path: v.path,
404
+ type: v.type,
405
+ description: v.description,
406
+ required: v.required ?? false,
407
+ score: 100 - index // Simple scoring based on order
408
+ }));
409
+ }
410
+ /**
411
+ * Validate template data against a context
412
+ */
413
+ async validateTemplateData(context, data) {
414
+ const variableContext = this.variableContexts.get(context);
415
+ if (!variableContext) {
416
+ return {
417
+ isValid: true,
418
+ errors: [],
419
+ warnings: [],
420
+ info: [`Variable context '${context}' not registered`]
421
+ };
422
+ }
423
+ const errors = [];
424
+ // Check required variables
425
+ for (const variable of variableContext.variables) {
426
+ if (variable.required) {
427
+ const value = this.getNestedValue(data, variable.path);
428
+ if (value === undefined) {
429
+ errors.push({
430
+ message: `Required variable missing: ${variable.path}`,
431
+ path: variable.path
432
+ });
433
+ }
434
+ }
435
+ }
436
+ return {
437
+ isValid: errors.length === 0,
438
+ errors,
439
+ warnings: []
440
+ };
441
+ }
442
+ // ======================
443
+ // System Operations
444
+ // ======================
445
+ /**
446
+ * Bootstrap the email manager system
447
+ */
448
+ async bootstrap() {
449
+ const result = {
450
+ success: true,
451
+ components: {},
452
+ errors: [],
453
+ warnings: []
454
+ };
455
+ try {
456
+ // 1. Bootstrap senders
457
+ if (this.enhancedConfig.bootstrap?.senders?.enabled) {
458
+ const senderResult = await this.bootstrapSenders();
459
+ result.components.senders = senderResult;
460
+ if (!senderResult.success) {
461
+ result.errors.push(...senderResult.errors);
462
+ }
463
+ }
464
+ // 2. Bootstrap variable contexts
465
+ if (this.enhancedConfig.bootstrap?.variables?.enabled) {
466
+ const variableResult = await this.bootstrapVariableContexts();
467
+ result.components.variables = variableResult;
468
+ if (!variableResult.success) {
469
+ result.errors.push(...variableResult.errors);
470
+ }
471
+ }
472
+ // 3. Bootstrap templates
473
+ if (this.enhancedConfig.bootstrap?.templates?.enabled) {
474
+ const templateResult = await this.bootstrapTemplates();
475
+ result.components.templates = templateResult;
476
+ if (!templateResult.success) {
477
+ result.errors.push(...templateResult.errors);
478
+ }
479
+ }
480
+ // 4. Bootstrap providers
481
+ if (this.enhancedConfig.bootstrap?.providers?.enabled) {
482
+ const providerResult = await this.bootstrapProviders();
483
+ result.components.providers = providerResult;
484
+ if (!providerResult.success) {
485
+ result.warnings.push(...providerResult.errors);
486
+ }
487
+ }
488
+ // 5. Final health check
489
+ const health = await this.getSystemHealth();
490
+ if (health.overall.status === 'unhealthy') {
491
+ result.warnings.push('System health check indicates issues after bootstrap');
492
+ }
493
+ this.initialized = true;
494
+ return result;
495
+ }
496
+ catch (error) {
497
+ result.success = false;
498
+ result.errors.push(`Bootstrap failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
499
+ return result;
500
+ }
501
+ }
502
+ /**
503
+ * Get comprehensive system health report
504
+ */
505
+ async getSystemHealth() {
506
+ const emailStats = await this.getEmailDeliveryStats();
507
+ const templateStats = await this.getTemplateSystemStats();
508
+ const senderStats = await this.getSenderSystemStats();
509
+ const providerStats = await this.getProviderHealthStats();
510
+ const overallHealth = this.calculateOverallHealth([
511
+ emailStats,
512
+ templateStats,
513
+ senderStats,
514
+ providerStats
515
+ ]);
516
+ return {
517
+ overall: overallHealth,
518
+ timestamp: new Date(),
519
+ components: {
520
+ email: emailStats,
521
+ templates: templateStats,
522
+ senders: senderStats,
523
+ providers: providerStats
524
+ },
525
+ recommendations: this.generateHealthRecommendations({
526
+ emailStats,
527
+ templateStats,
528
+ senderStats,
529
+ providerHealth: providerStats
530
+ })
531
+ };
532
+ }
533
+ // ======================
534
+ // Private Helper Methods
535
+ // ======================
536
+ async bootstrapSenders() {
537
+ const result = {
538
+ success: true,
539
+ created: [],
540
+ updated: [],
541
+ errors: []
542
+ };
543
+ const defaultSenders = this.enhancedConfig.bootstrap?.senders?.defaultSenders || [];
544
+ for (const senderDef of defaultSenders) {
545
+ try {
546
+ const created = await this.createSender(senderDef);
547
+ if (created) {
548
+ result.created.push(created.id);
549
+ }
550
+ }
551
+ catch (error) {
552
+ result.errors.push(`Failed to bootstrap sender "${senderDef.name}": ${error instanceof Error ? error.message : 'Unknown error'}`);
553
+ result.success = false;
554
+ }
555
+ }
556
+ return result;
557
+ }
558
+ async bootstrapVariableContexts() {
559
+ const result = {
560
+ success: true,
561
+ created: [],
562
+ updated: [],
563
+ errors: []
564
+ };
565
+ const contexts = this.enhancedConfig.bootstrap?.variables?.contexts || [];
566
+ for (const contextDef of contexts) {
567
+ try {
568
+ await this.registerVariableContext(contextDef.name, contextDef);
569
+ result.created.push(contextDef.name);
570
+ }
571
+ catch (error) {
572
+ result.errors.push(`Failed to bootstrap context "${contextDef.name}": ${error instanceof Error ? error.message : 'Unknown error'}`);
573
+ result.success = false;
574
+ }
575
+ }
576
+ return result;
577
+ }
578
+ async bootstrapTemplates() {
579
+ const result = {
580
+ success: true,
581
+ created: [],
582
+ updated: [],
583
+ errors: []
584
+ };
585
+ const defaultTemplates = this.enhancedConfig.bootstrap?.templates?.defaultTemplates || [];
586
+ for (const templateDef of defaultTemplates) {
587
+ try {
588
+ const createResult = await this.createEnhancedTemplate(templateDef);
589
+ if (createResult.success && createResult.templateId) {
590
+ result.created.push(createResult.templateId);
591
+ // Sync to providers if enabled
592
+ if (this.enhancedConfig.bootstrap?.templates?.syncToProviders) {
593
+ await this.syncTemplate(createResult.templateId);
594
+ }
595
+ }
596
+ else {
597
+ result.errors.push(...(createResult.errors || []));
598
+ }
599
+ }
600
+ catch (error) {
601
+ result.errors.push(`Failed to bootstrap template "${templateDef.name}": ${error instanceof Error ? error.message : 'Unknown error'}`);
602
+ result.success = false;
603
+ }
604
+ }
605
+ return result;
606
+ }
607
+ async bootstrapProviders() {
608
+ const result = {
609
+ success: true,
610
+ created: [],
611
+ updated: [],
612
+ errors: []
613
+ };
614
+ // Test provider connections if enabled
615
+ if (this.enhancedConfig.bootstrap?.providers?.testConnections) {
616
+ const providers = await this.listProviders();
617
+ for (const provider of providers.providers) {
618
+ try {
619
+ const testResult = await this.testConnection(provider.id);
620
+ if (testResult.success) {
621
+ result.updated.push(provider.id);
622
+ }
623
+ else {
624
+ result.errors.push(`Provider "${provider.name}" connection test failed: ${testResult.message}`);
625
+ }
626
+ }
627
+ catch (error) {
628
+ result.errors.push(`Provider "${provider.name}" test error: ${error instanceof Error ? error.message : 'Unknown error'}`);
629
+ }
630
+ }
631
+ }
632
+ return result;
633
+ }
634
+ async getEmailDeliveryStats() {
635
+ const summary = await this.getAnalyticsSummary(7);
636
+ const bounceRate = summary.bounceRate || 0;
637
+ const issues = [];
638
+ if (bounceRate > 5) {
639
+ issues.push({
640
+ severity: 'warning',
641
+ message: 'High bounce rate detected',
642
+ details: `Current bounce rate: ${bounceRate.toFixed(2)}%`
643
+ });
644
+ }
645
+ return {
646
+ status: bounceRate > 10 ? 'unhealthy' : bounceRate > 5 ? 'degraded' : 'healthy',
647
+ score: Math.max(0, 100 - bounceRate * 10),
648
+ metrics: {
649
+ totalSent: summary.totalSent || 0,
650
+ totalDelivered: summary.totalDelivered || 0,
651
+ bounceRate,
652
+ deliveryRate: summary.deliveryRate || 100
653
+ },
654
+ issues
655
+ };
656
+ }
657
+ async getTemplateSystemStats() {
658
+ const templates = Array.from(this.templateStore.values());
659
+ const activeTemplates = templates.filter(t => t.isActive);
660
+ return {
661
+ status: 'healthy',
662
+ score: 100,
663
+ metrics: {
664
+ totalTemplates: templates.length,
665
+ activeTemplates: activeTemplates.length
666
+ },
667
+ issues: []
668
+ };
669
+ }
670
+ async getSenderSystemStats() {
671
+ const senders = Array.from(this.senderStore.values());
672
+ const activeSenders = senders.filter(s => s.isActive);
673
+ const verifiedSenders = activeSenders.filter(s => s.isVerified);
674
+ const defaultSenders = senders.filter(s => s.isDefault);
675
+ const issues = [];
676
+ if (senders.length > 0 && defaultSenders.length === 0) {
677
+ issues.push({
678
+ severity: 'error',
679
+ message: 'No default sender configured'
680
+ });
681
+ }
682
+ const verificationRate = activeSenders.length > 0 ?
683
+ (verifiedSenders.length / activeSenders.length) * 100 : 100;
684
+ if (verificationRate < 50 && activeSenders.length > 0) {
685
+ issues.push({
686
+ severity: 'warning',
687
+ message: 'Low sender verification rate',
688
+ details: `Only ${Math.round(verificationRate)}% of active senders are verified`
689
+ });
690
+ }
691
+ return {
692
+ status: issues.some(i => i.severity === 'error') ? 'unhealthy' :
693
+ issues.length > 0 ? 'degraded' : 'healthy',
694
+ score: Math.max(0, 100 - issues.length * 20),
695
+ metrics: {
696
+ totalSenders: senders.length,
697
+ activeSenders: activeSenders.length,
698
+ verifiedSenders: verifiedSenders.length,
699
+ defaultSenders: defaultSenders.length,
700
+ verificationRate: Math.round(verificationRate)
701
+ },
702
+ issues
703
+ };
704
+ }
705
+ async getProviderHealthStats() {
706
+ const providers = await this.listProviders();
707
+ const activeProviders = providers.providers.filter(p => p.isActive);
708
+ const issues = [];
709
+ if (activeProviders.length === 0) {
710
+ issues.push({
711
+ severity: 'error',
712
+ message: 'No active email providers configured'
713
+ });
714
+ }
715
+ return {
716
+ status: activeProviders.length === 0 ? 'unhealthy' : 'healthy',
717
+ score: activeProviders.length > 0 ? 100 : 0,
718
+ metrics: {
719
+ totalProviders: providers.providers.length,
720
+ activeProviders: activeProviders.length
721
+ },
722
+ issues
723
+ };
724
+ }
725
+ calculateOverallHealth(components) {
726
+ const avgScore = components.reduce((sum, c) => sum + c.score, 0) / components.length;
727
+ const hasUnhealthy = components.some(c => c.status === 'unhealthy');
728
+ const hasDegraded = components.some(c => c.status === 'degraded');
729
+ return {
730
+ status: hasUnhealthy ? 'unhealthy' : hasDegraded ? 'degraded' : 'healthy',
731
+ score: Math.round(avgScore),
732
+ metrics: {
733
+ componentsHealthy: components.filter(c => c.status === 'healthy').length,
734
+ componentsDegraded: components.filter(c => c.status === 'degraded').length,
735
+ componentsUnhealthy: components.filter(c => c.status === 'unhealthy').length
736
+ },
737
+ issues: components.flatMap(c => c.issues)
738
+ };
739
+ }
740
+ generateHealthRecommendations(stats) {
741
+ const recommendations = [];
742
+ // Email delivery recommendations
743
+ if (stats.emailStats.metrics.bounceRate > 5) {
744
+ recommendations.push({
745
+ priority: 'high',
746
+ category: 'delivery',
747
+ title: 'High Bounce Rate Detected',
748
+ description: 'Consider reviewing sender reputation and email list quality',
749
+ action: 'Review and clean email lists, check sender verification status'
750
+ });
751
+ }
752
+ // Sender verification recommendations
753
+ if (stats.senderStats.metrics.verificationRate < 80) {
754
+ recommendations.push({
755
+ priority: 'medium',
756
+ category: 'senders',
757
+ title: 'Low Sender Verification Rate',
758
+ description: 'Improve email deliverability by verifying more senders',
759
+ action: 'Complete sender verification process with email providers'
760
+ });
761
+ }
762
+ // Provider health recommendations
763
+ if (stats.providerHealth.metrics.activeProviders === 0) {
764
+ recommendations.push({
765
+ priority: 'high',
766
+ category: 'providers',
767
+ title: 'No Active Providers',
768
+ description: 'Email sending will fail without active providers',
769
+ action: 'Configure and activate at least one email provider'
770
+ });
771
+ }
772
+ return recommendations;
773
+ }
774
+ renderSimpleTemplate(template, data) {
775
+ let result = template;
776
+ const flattenObject = (obj, prefix = '') => {
777
+ const flattened = {};
778
+ for (const [key, value] of Object.entries(obj)) {
779
+ const path = prefix ? `${prefix}.${key}` : key;
780
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
781
+ Object.assign(flattened, flattenObject(value, path));
782
+ }
783
+ else {
784
+ flattened[path] = String(value ?? '');
785
+ }
786
+ }
787
+ return flattened;
788
+ };
789
+ const flatData = flattenObject(data);
790
+ for (const [key, value] of Object.entries(flatData)) {
791
+ const patterns = [
792
+ new RegExp(`\\{\\{\\s*${key.replace(/\./g, '\\.')}\\s*\\}\\}`, 'g')
793
+ ];
794
+ for (const pattern of patterns) {
795
+ result = result.replace(pattern, value);
796
+ }
797
+ }
798
+ return result;
799
+ }
800
+ mapToEnhancedSender(sender) {
801
+ return {
802
+ id: sender.id,
803
+ name: sender.name,
804
+ fromEmail: sender.fromEmail,
805
+ fromName: sender.fromName,
806
+ replyToEmail: sender.replyToEmail,
807
+ provider: sender.provider,
808
+ priority: sender.priority,
809
+ isDefault: sender.isDefault,
810
+ isActive: sender.isActive,
811
+ isVerified: sender.isVerified,
812
+ verificationStatus: sender.verificationStatus,
813
+ createdAt: sender.createdAt,
814
+ updatedAt: sender.updatedAt
815
+ };
816
+ }
817
+ extractDomain(email) {
818
+ const parts = email.split('@');
819
+ return parts.length > 1 ? parts[1].toLowerCase() : '';
820
+ }
821
+ isNestedPath(path, definedPaths) {
822
+ // Check if the path is a child of any defined paths
823
+ for (const defined of definedPaths) {
824
+ if (path.startsWith(defined + '.')) {
825
+ return true;
826
+ }
827
+ }
828
+ return false;
829
+ }
830
+ getNestedValue(obj, path) {
831
+ const parts = path.split('.');
832
+ let current = obj;
833
+ for (const part of parts) {
834
+ if (current === null || current === undefined || typeof current !== 'object') {
835
+ return undefined;
836
+ }
837
+ current = current[part];
838
+ }
839
+ return current;
840
+ }
841
+ /**
842
+ * Check if the enhanced manager is initialized
843
+ */
844
+ isInitialized() {
845
+ return this.initialized;
846
+ }
847
+ /**
848
+ * Get template store size
849
+ */
850
+ getTemplateCount() {
851
+ return this.templateStore.size;
852
+ }
853
+ /**
854
+ * Get sender store size
855
+ */
856
+ getSenderCount() {
857
+ return this.senderStore.size;
858
+ }
859
+ /**
860
+ * Get variable context count
861
+ */
862
+ getVariableContextCount() {
863
+ return this.variableContexts.size;
864
+ }
865
+ }
866
+ //# sourceMappingURL=enhanced-email-manager.js.map