@bernierllc/email-manager 0.4.0 → 0.4.2

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 (68) hide show
  1. package/README.md +653 -0
  2. package/dist/capabilities/degradation-logger.d.ts +59 -0
  3. package/dist/capabilities/degradation-logger.d.ts.map +1 -0
  4. package/dist/capabilities/degradation-logger.js +106 -0
  5. package/dist/capabilities/degradation-logger.js.map +1 -0
  6. package/dist/capabilities/feature-router.d.ts +55 -0
  7. package/dist/capabilities/feature-router.d.ts.map +1 -0
  8. package/dist/capabilities/feature-router.js +143 -0
  9. package/dist/capabilities/feature-router.js.map +1 -0
  10. package/dist/capabilities/in-memory-resolver.d.ts +27 -0
  11. package/dist/capabilities/in-memory-resolver.d.ts.map +1 -0
  12. package/dist/capabilities/in-memory-resolver.js +79 -0
  13. package/dist/capabilities/in-memory-resolver.js.map +1 -0
  14. package/dist/capabilities/index.d.ts +13 -0
  15. package/dist/capabilities/index.d.ts.map +1 -0
  16. package/dist/capabilities/index.js +21 -0
  17. package/dist/capabilities/index.js.map +1 -0
  18. package/dist/capabilities/matrix.d.ts +66 -0
  19. package/dist/capabilities/matrix.d.ts.map +1 -0
  20. package/dist/capabilities/matrix.js +247 -0
  21. package/dist/capabilities/matrix.js.map +1 -0
  22. package/dist/capabilities/redis-resolver.d.ts +95 -0
  23. package/dist/capabilities/redis-resolver.d.ts.map +1 -0
  24. package/dist/capabilities/redis-resolver.js +227 -0
  25. package/dist/capabilities/redis-resolver.js.map +1 -0
  26. package/dist/capabilities/resolver-factory.d.ts +30 -0
  27. package/dist/capabilities/resolver-factory.d.ts.map +1 -0
  28. package/dist/capabilities/resolver-factory.js +70 -0
  29. package/dist/capabilities/resolver-factory.js.map +1 -0
  30. package/dist/capabilities/resolver.d.ts +40 -0
  31. package/dist/capabilities/resolver.d.ts.map +1 -0
  32. package/dist/capabilities/resolver.js +18 -0
  33. package/dist/capabilities/resolver.js.map +1 -0
  34. package/dist/capabilities/routing-metadata.d.ts +16 -0
  35. package/dist/capabilities/routing-metadata.d.ts.map +1 -0
  36. package/dist/capabilities/routing-metadata.js +17 -0
  37. package/dist/capabilities/routing-metadata.js.map +1 -0
  38. package/dist/capabilities/safe-resolver.d.ts +24 -0
  39. package/dist/capabilities/safe-resolver.d.ts.map +1 -0
  40. package/dist/capabilities/safe-resolver.js +48 -0
  41. package/dist/capabilities/safe-resolver.js.map +1 -0
  42. package/dist/config/schema.d.ts +99 -4
  43. package/dist/config/schema.d.ts.map +1 -1
  44. package/dist/config/schema.js +17 -0
  45. package/dist/config/schema.js.map +1 -1
  46. package/dist/email-manager.d.ts.map +1 -1
  47. package/dist/email-manager.js +28 -1
  48. package/dist/email-manager.js.map +1 -1
  49. package/dist/enhanced-email-manager.d.ts +163 -1
  50. package/dist/enhanced-email-manager.d.ts.map +1 -1
  51. package/dist/enhanced-email-manager.js +412 -8
  52. package/dist/enhanced-email-manager.js.map +1 -1
  53. package/dist/errors.d.ts +11 -0
  54. package/dist/errors.d.ts.map +1 -1
  55. package/dist/errors.js +13 -0
  56. package/dist/errors.js.map +1 -1
  57. package/dist/index.d.ts +13 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +7 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/managers/provider-manager.d.ts +43 -0
  62. package/dist/managers/provider-manager.d.ts.map +1 -1
  63. package/dist/managers/provider-manager.js +102 -4
  64. package/dist/managers/provider-manager.js.map +1 -1
  65. package/dist/types.d.ts +66 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js.map +1 -1
  68. package/package.json +38 -25
@@ -1,5 +1,8 @@
1
1
  import { EmailManager } from './email-manager.js';
2
- import { EnhancedEmailManagerConfig, EmailRecipient, SendTemplateOptions, EnhancedSenderConfiguration, CreateSenderRequest, SenderSelectionOptions, EnhancedSenderValidationResult, EnhancedTemplateValidationResult, VariableContextDefinition, EnhancedVariableSuggestion, TemplateSyncResult, BatchTemplateSyncResult, BootstrapResult, SystemHealthReport, SendResult, EmailData, EnhancedSendOptions, VariableDefinition } from './types.js';
2
+ import type { CalendarEvent, CalendarAttendee } from '@bernierllc/email-calendar';
3
+ import type { BatchSenderOptions, BatchResult, BatchEmailInput } from '@bernierllc/email-batch-sender';
4
+ import type { SubscriptionStore, SubscriberList, Subscriber, AddSubscriberInput, CreateListOptions, BulkAddResult } from '@bernierllc/email-subscription';
5
+ import { EnhancedEmailManagerConfig, EmailRecipient, SendTemplateOptions, EnhancedSenderConfiguration, CreateSenderRequest, SenderSelectionOptions, EnhancedSenderValidationResult, EnhancedTemplateValidationResult, VariableContextDefinition, EnhancedVariableSuggestion, TemplateSyncResult, BatchTemplateSyncResult, BootstrapResult, SystemHealthReport, SendResult, EmailData, EnhancedSendOptions, VariableDefinition, CalendarInviteSendOptions } from './types.js';
3
6
  /**
4
7
  * Internal template store for enhanced template management
5
8
  */
@@ -43,6 +46,10 @@ export declare class EnhancedEmailManager extends EmailManager {
43
46
  private senderStore;
44
47
  private variableContexts;
45
48
  private initialized;
49
+ private subscriptionStore;
50
+ private subscriptionListManager;
51
+ private unsubscribeManager;
52
+ private suppressionManager;
46
53
  constructor(config: EnhancedEmailManagerConfig);
47
54
  /**
48
55
  * Send a templated email with variable validation and smart sender selection
@@ -129,6 +136,159 @@ export declare class EnhancedEmailManager extends EmailManager {
129
136
  * Validate template data against a context
130
137
  */
131
138
  validateTemplateData(context: string, data: Record<string, unknown>): Promise<EnhancedTemplateValidationResult>;
139
+ /**
140
+ * Send a calendar invite email to recipients.
141
+ *
142
+ * Generates an ICS file from the CalendarEvent and attaches it to the email.
143
+ * The ICS attachment uses METHOD:REQUEST by default.
144
+ *
145
+ * @param event - The calendar event to send
146
+ * @param recipients - Array of recipient email addresses
147
+ * @param options - Optional subject, body, and provider overrides
148
+ * @returns SendResult from the email send operation
149
+ *
150
+ * @throws {Error} When the calendar event is invalid (missing title, bad dates, no attendees)
151
+ * @throws {EmailValidationError} When recipient validation fails
152
+ */
153
+ sendCalendarInvite(event: CalendarEvent, recipients: string[], options?: CalendarInviteSendOptions): Promise<SendResult>;
154
+ /**
155
+ * Send a calendar cancellation email.
156
+ *
157
+ * Generates a METHOD:CANCEL ICS and sends it to the specified attendees.
158
+ *
159
+ * @param eventUid - The UID of the event to cancel
160
+ * @param organizer - The organizer attendee
161
+ * @param attendees - The attendees to notify
162
+ * @param options - Optional subject and body overrides
163
+ * @returns SendResult from the email send operation
164
+ */
165
+ sendCalendarCancel(eventUid: string, organizer: CalendarAttendee, attendees: CalendarAttendee[], options?: CalendarInviteSendOptions): Promise<SendResult>;
166
+ /**
167
+ * Send a batch of emails with concurrency control, rate limiting, and retry.
168
+ *
169
+ * Wraps the existing email sending mechanism with the BatchSender from
170
+ * @bernierllc/email-batch-sender, providing efficient bulk email operations.
171
+ *
172
+ * @param emails - Array of batch email inputs
173
+ * @param options - Batch sender configuration (concurrency, rate limits, retry, etc.)
174
+ * @returns BatchResult with per-recipient results, timing, and abort status
175
+ *
176
+ * @throws {BatchSenderError} When batch input is invalid (missing toEmail/subject)
177
+ */
178
+ sendBatch(emails: BatchEmailInput[], options?: BatchSenderOptions): Promise<BatchResult>;
179
+ /**
180
+ * Create a new subscriber list.
181
+ *
182
+ * @param name - The list name
183
+ * @param options - Optional description and metadata
184
+ * @returns The created SubscriberList
185
+ */
186
+ createSubscriberList(name: string, options?: CreateListOptions): Promise<SubscriberList>;
187
+ /**
188
+ * Get a subscriber list by ID.
189
+ *
190
+ * @param listId - The list ID
191
+ * @returns The SubscriberList or null if not found
192
+ */
193
+ getSubscriberList(listId: string): Promise<SubscriberList | null>;
194
+ /**
195
+ * Delete a subscriber list and all its subscribers.
196
+ *
197
+ * @param listId - The list ID
198
+ * @returns true if the list was deleted
199
+ */
200
+ deleteSubscriberList(listId: string): Promise<boolean>;
201
+ /**
202
+ * Add a subscriber to a list.
203
+ *
204
+ * Checks for suppression and duplicates before adding.
205
+ *
206
+ * @param listId - The list ID
207
+ * @param input - Subscriber email, name, and metadata
208
+ * @throws {SubscriptionError} If list not found, email suppressed, or duplicate
209
+ */
210
+ addSubscriber(listId: string, input: AddSubscriberInput): Promise<void>;
211
+ /**
212
+ * Add multiple subscribers to a list in bulk.
213
+ *
214
+ * Skips duplicates and suppressed emails without throwing.
215
+ *
216
+ * @param listId - The list ID
217
+ * @param subscribers - Array of subscriber inputs
218
+ * @returns BulkAddResult with added/skipped counts
219
+ */
220
+ addSubscribers(listId: string, subscribers: ReadonlyArray<{
221
+ email: string;
222
+ name?: string;
223
+ }>): Promise<BulkAddResult>;
224
+ /**
225
+ * Remove a subscriber from a list.
226
+ *
227
+ * @param listId - The list ID
228
+ * @param email - The subscriber email
229
+ * @returns true if the subscriber was removed
230
+ */
231
+ removeSubscriber(listId: string, email: string): Promise<boolean>;
232
+ /**
233
+ * Get a subscriber from a list.
234
+ *
235
+ * @param listId - The list ID
236
+ * @param email - The subscriber email
237
+ * @returns The Subscriber or null if not found
238
+ */
239
+ getSubscriber(listId: string, email: string): Promise<Subscriber | null>;
240
+ /**
241
+ * Generate an unsubscribe URL for a subscriber.
242
+ *
243
+ * Requires subscription.unsubscribe configuration in the manager config.
244
+ *
245
+ * @param email - The subscriber email
246
+ * @param listId - The list ID
247
+ * @returns The signed unsubscribe URL
248
+ * @throws {Error} If unsubscribe manager is not configured
249
+ */
250
+ generateUnsubscribeUrl(email: string, listId: string): string;
251
+ /**
252
+ * Process an unsubscribe request from a signed token.
253
+ *
254
+ * Verifies the token, updates subscriber status, and adds suppression.
255
+ *
256
+ * @param token - The unsubscribe token from the URL
257
+ * @throws {Error} If unsubscribe manager is not configured
258
+ * @throws {SubscriptionError} If token is invalid or expired
259
+ */
260
+ processUnsubscribe(token: string): Promise<void>;
261
+ /**
262
+ * Check if an email address is suppressed.
263
+ *
264
+ * @param email - The email address to check
265
+ * @param listId - Optional list ID for scope-specific checks
266
+ * @returns true if the email is suppressed
267
+ */
268
+ isSuppressed(email: string, listId?: string): Promise<boolean>;
269
+ /**
270
+ * Send an email with List-Unsubscribe headers automatically added.
271
+ *
272
+ * Generates unsubscribe URL and adds RFC 2369/8058 headers to the email.
273
+ * Requires subscription.unsubscribe configuration.
274
+ *
275
+ * @param email - The email data to send
276
+ * @param listId - The list ID for unsubscribe scoping
277
+ * @param recipientEmail - The recipient email for unsubscribe token
278
+ * @param oneClick - Whether to include RFC 8058 one-click unsubscribe
279
+ * @returns SendResult from the email send operation
280
+ * @throws {Error} If unsubscribe manager is not configured
281
+ */
282
+ sendWithUnsubscribe(email: EmailData, listId: string, recipientEmail: string, oneClick?: boolean): Promise<SendResult>;
283
+ /**
284
+ * Set a custom subscription store (e.g., database-backed).
285
+ *
286
+ * Replaces the default InMemorySubscriptionStore and reinitializes
287
+ * all subscription managers to use the new store.
288
+ *
289
+ * @param store - A SubscriptionStore implementation
290
+ */
291
+ setSubscriptionStore(store: SubscriptionStore): void;
132
292
  /**
133
293
  * Bootstrap the email manager system
134
294
  */
@@ -145,6 +305,8 @@ export declare class EnhancedEmailManager extends EmailManager {
145
305
  private getTemplateSystemStats;
146
306
  private getSenderSystemStats;
147
307
  private getProviderHealthStats;
308
+ private getSubscriptionHealthStats;
309
+ private getBatchSenderHealthStats;
148
310
  private calculateOverallHealth;
149
311
  private generateHealthRecommendations;
150
312
  private renderSimpleTemplate;
@@ -1 +1 @@
1
- {"version":3,"file":"enhanced-email-manager.d.ts","sourceRoot":"","sources":["../src/enhanced-email-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"enhanced-email-manager.d.ts","sourceRoot":"","sources":["../src/enhanced-email-manager.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,10 @@ Redistribution or use in other products or commercial offerings is not permitted
7
7
  */
8
8
  import { EmailManager } from './email-manager.js';
9
9
  import { v4 as uuidv4 } from 'uuid';
10
+ import { createCalendarInvite, createCalendarCancel, toCalendarAttachment } from '@bernierllc/email-calendar';
11
+ import { createBatchSender } from '@bernierllc/email-batch-sender';
12
+ import { SubscriptionListManager, UnsubscribeManager, SuppressionManager, InMemorySubscriptionStore, } from '@bernierllc/email-subscription';
13
+ import { createListUnsubscribeHeaders } from '@bernierllc/email-headers';
10
14
  /**
11
15
  * EnhancedEmailManager - Extended email management with template service,
12
16
  * sender management, and variable validation integration.
@@ -27,7 +31,15 @@ export class EnhancedEmailManager extends EmailManager {
27
31
  this.senderStore = new Map();
28
32
  this.variableContexts = new Map();
29
33
  this.initialized = false;
34
+ this.unsubscribeManager = null;
30
35
  this.enhancedConfig = config;
36
+ // Initialize subscription components
37
+ this.subscriptionStore = new InMemorySubscriptionStore();
38
+ this.subscriptionListManager = new SubscriptionListManager(this.subscriptionStore);
39
+ this.suppressionManager = new SuppressionManager(this.subscriptionStore);
40
+ if (config.subscription?.unsubscribe) {
41
+ this.unsubscribeManager = new UnsubscribeManager(this.subscriptionStore, config.subscription.unsubscribe);
42
+ }
31
43
  }
32
44
  // ======================
33
45
  // Enhanced Email Sending
@@ -440,6 +452,305 @@ export class EnhancedEmailManager extends EmailManager {
440
452
  };
441
453
  }
442
454
  // ======================
455
+ // Calendar Invite Methods
456
+ // ======================
457
+ /**
458
+ * Send a calendar invite email to recipients.
459
+ *
460
+ * Generates an ICS file from the CalendarEvent and attaches it to the email.
461
+ * The ICS attachment uses METHOD:REQUEST by default.
462
+ *
463
+ * @param event - The calendar event to send
464
+ * @param recipients - Array of recipient email addresses
465
+ * @param options - Optional subject, body, and provider overrides
466
+ * @returns SendResult from the email send operation
467
+ *
468
+ * @throws {Error} When the calendar event is invalid (missing title, bad dates, no attendees)
469
+ * @throws {EmailValidationError} When recipient validation fails
470
+ */
471
+ async sendCalendarInvite(event, recipients, options) {
472
+ if (!recipients || recipients.length === 0) {
473
+ throw new Error('At least one recipient is required');
474
+ }
475
+ // Generate ICS content
476
+ const icsContent = createCalendarInvite(event);
477
+ // Convert to email attachment
478
+ const calendarAttachment = toCalendarAttachment(icsContent, 'REQUEST');
479
+ // Build subject line
480
+ const subject = options?.subject ?? `Calendar Invite: ${event.title}`;
481
+ // Build default body
482
+ const textBody = options?.textBody ??
483
+ `You have been invited to: ${event.title}\nWhen: ${event.start.toISOString()} - ${event.end.toISOString()}${event.location ? `\nWhere: ${event.location}` : ''}${event.description ? `\n\n${event.description}` : ''}`;
484
+ const htmlBody = options?.htmlBody ??
485
+ `<h2>${event.title}</h2><p><strong>When:</strong> ${event.start.toISOString()} - ${event.end.toISOString()}</p>${event.location ? `<p><strong>Where:</strong> ${event.location}</p>` : ''}${event.description ? `<p>${event.description}</p>` : ''}`;
486
+ // Build email data with calendar attachment
487
+ const emailData = {
488
+ to: recipients,
489
+ subject,
490
+ text: textBody,
491
+ html: htmlBody,
492
+ attachments: [{
493
+ filename: calendarAttachment.filename,
494
+ content: calendarAttachment.content,
495
+ contentType: calendarAttachment.contentType,
496
+ }],
497
+ provider: options?.provider,
498
+ metadata: {
499
+ ...options?.metadata,
500
+ calendarEvent: true,
501
+ eventTitle: event.title,
502
+ },
503
+ };
504
+ return this.sendEmail(emailData);
505
+ }
506
+ /**
507
+ * Send a calendar cancellation email.
508
+ *
509
+ * Generates a METHOD:CANCEL ICS and sends it to the specified attendees.
510
+ *
511
+ * @param eventUid - The UID of the event to cancel
512
+ * @param organizer - The organizer attendee
513
+ * @param attendees - The attendees to notify
514
+ * @param options - Optional subject and body overrides
515
+ * @returns SendResult from the email send operation
516
+ */
517
+ async sendCalendarCancel(eventUid, organizer, attendees, options) {
518
+ if (!attendees || attendees.length === 0) {
519
+ throw new Error('At least one attendee is required');
520
+ }
521
+ const icsContent = createCalendarCancel(eventUid, organizer, attendees);
522
+ const calendarAttachment = toCalendarAttachment(icsContent, 'CANCEL');
523
+ const subject = options?.subject ?? 'Event Cancelled';
524
+ const recipients = attendees.map(a => a.email);
525
+ const emailData = {
526
+ to: recipients,
527
+ subject,
528
+ text: options?.textBody ?? 'This event has been cancelled.',
529
+ html: options?.htmlBody ?? '<p>This event has been cancelled.</p>',
530
+ attachments: [{
531
+ filename: calendarAttachment.filename,
532
+ content: calendarAttachment.content,
533
+ contentType: calendarAttachment.contentType,
534
+ }],
535
+ provider: options?.provider,
536
+ metadata: {
537
+ ...options?.metadata,
538
+ calendarCancel: true,
539
+ eventUid,
540
+ },
541
+ };
542
+ return this.sendEmail(emailData);
543
+ }
544
+ // ======================
545
+ // Batch Send Methods
546
+ // ======================
547
+ /**
548
+ * Send a batch of emails with concurrency control, rate limiting, and retry.
549
+ *
550
+ * Wraps the existing email sending mechanism with the BatchSender from
551
+ * @bernierllc/email-batch-sender, providing efficient bulk email operations.
552
+ *
553
+ * @param emails - Array of batch email inputs
554
+ * @param options - Batch sender configuration (concurrency, rate limits, retry, etc.)
555
+ * @returns BatchResult with per-recipient results, timing, and abort status
556
+ *
557
+ * @throws {BatchSenderError} When batch input is invalid (missing toEmail/subject)
558
+ */
559
+ async sendBatch(emails, options) {
560
+ // Create an adapter that wraps our sendEmail into the EmailSenderInterface
561
+ const senderAdapter = {
562
+ send: async (message) => {
563
+ try {
564
+ const emailData = {
565
+ to: message.toEmail,
566
+ subject: message.subject,
567
+ html: message.htmlContent,
568
+ text: message.textContent,
569
+ metadata: message.metadata,
570
+ };
571
+ const result = await this.sendEmail(emailData);
572
+ return {
573
+ success: result.success,
574
+ messageId: result.messageId,
575
+ provider: result.provider,
576
+ };
577
+ }
578
+ catch (error) {
579
+ return {
580
+ success: false,
581
+ errorMessage: error instanceof Error ? error.message : 'Unknown send error',
582
+ };
583
+ }
584
+ }
585
+ };
586
+ const batchSender = createBatchSender(senderAdapter, options);
587
+ return batchSender.send(emails);
588
+ }
589
+ // ======================
590
+ // Subscription Management Methods
591
+ // ======================
592
+ /**
593
+ * Create a new subscriber list.
594
+ *
595
+ * @param name - The list name
596
+ * @param options - Optional description and metadata
597
+ * @returns The created SubscriberList
598
+ */
599
+ async createSubscriberList(name, options) {
600
+ return this.subscriptionListManager.createList(name, options);
601
+ }
602
+ /**
603
+ * Get a subscriber list by ID.
604
+ *
605
+ * @param listId - The list ID
606
+ * @returns The SubscriberList or null if not found
607
+ */
608
+ async getSubscriberList(listId) {
609
+ return this.subscriptionListManager.getList(listId);
610
+ }
611
+ /**
612
+ * Delete a subscriber list and all its subscribers.
613
+ *
614
+ * @param listId - The list ID
615
+ * @returns true if the list was deleted
616
+ */
617
+ async deleteSubscriberList(listId) {
618
+ return this.subscriptionListManager.deleteList(listId);
619
+ }
620
+ /**
621
+ * Add a subscriber to a list.
622
+ *
623
+ * Checks for suppression and duplicates before adding.
624
+ *
625
+ * @param listId - The list ID
626
+ * @param input - Subscriber email, name, and metadata
627
+ * @throws {SubscriptionError} If list not found, email suppressed, or duplicate
628
+ */
629
+ async addSubscriber(listId, input) {
630
+ return this.subscriptionListManager.addSubscriber(listId, input);
631
+ }
632
+ /**
633
+ * Add multiple subscribers to a list in bulk.
634
+ *
635
+ * Skips duplicates and suppressed emails without throwing.
636
+ *
637
+ * @param listId - The list ID
638
+ * @param subscribers - Array of subscriber inputs
639
+ * @returns BulkAddResult with added/skipped counts
640
+ */
641
+ async addSubscribers(listId, subscribers) {
642
+ return this.subscriptionListManager.addSubscribers(listId, subscribers);
643
+ }
644
+ /**
645
+ * Remove a subscriber from a list.
646
+ *
647
+ * @param listId - The list ID
648
+ * @param email - The subscriber email
649
+ * @returns true if the subscriber was removed
650
+ */
651
+ async removeSubscriber(listId, email) {
652
+ return this.subscriptionListManager.removeSubscriber(listId, email);
653
+ }
654
+ /**
655
+ * Get a subscriber from a list.
656
+ *
657
+ * @param listId - The list ID
658
+ * @param email - The subscriber email
659
+ * @returns The Subscriber or null if not found
660
+ */
661
+ async getSubscriber(listId, email) {
662
+ return this.subscriptionListManager.getSubscriber(listId, email);
663
+ }
664
+ /**
665
+ * Generate an unsubscribe URL for a subscriber.
666
+ *
667
+ * Requires subscription.unsubscribe configuration in the manager config.
668
+ *
669
+ * @param email - The subscriber email
670
+ * @param listId - The list ID
671
+ * @returns The signed unsubscribe URL
672
+ * @throws {Error} If unsubscribe manager is not configured
673
+ */
674
+ generateUnsubscribeUrl(email, listId) {
675
+ if (!this.unsubscribeManager) {
676
+ throw new Error('UnsubscribeManager is not configured. Provide subscription.unsubscribe config.');
677
+ }
678
+ return this.unsubscribeManager.generateUnsubscribeUrl(email, listId);
679
+ }
680
+ /**
681
+ * Process an unsubscribe request from a signed token.
682
+ *
683
+ * Verifies the token, updates subscriber status, and adds suppression.
684
+ *
685
+ * @param token - The unsubscribe token from the URL
686
+ * @throws {Error} If unsubscribe manager is not configured
687
+ * @throws {SubscriptionError} If token is invalid or expired
688
+ */
689
+ async processUnsubscribe(token) {
690
+ if (!this.unsubscribeManager) {
691
+ throw new Error('UnsubscribeManager is not configured. Provide subscription.unsubscribe config.');
692
+ }
693
+ return this.unsubscribeManager.processUnsubscribe(token);
694
+ }
695
+ /**
696
+ * Check if an email address is suppressed.
697
+ *
698
+ * @param email - The email address to check
699
+ * @param listId - Optional list ID for scope-specific checks
700
+ * @returns true if the email is suppressed
701
+ */
702
+ async isSuppressed(email, listId) {
703
+ return this.suppressionManager.isSuppressed(email, listId);
704
+ }
705
+ /**
706
+ * Send an email with List-Unsubscribe headers automatically added.
707
+ *
708
+ * Generates unsubscribe URL and adds RFC 2369/8058 headers to the email.
709
+ * Requires subscription.unsubscribe configuration.
710
+ *
711
+ * @param email - The email data to send
712
+ * @param listId - The list ID for unsubscribe scoping
713
+ * @param recipientEmail - The recipient email for unsubscribe token
714
+ * @param oneClick - Whether to include RFC 8058 one-click unsubscribe
715
+ * @returns SendResult from the email send operation
716
+ * @throws {Error} If unsubscribe manager is not configured
717
+ */
718
+ async sendWithUnsubscribe(email, listId, recipientEmail, oneClick = true) {
719
+ if (!this.unsubscribeManager) {
720
+ throw new Error('UnsubscribeManager is not configured. Provide subscription.unsubscribe config.');
721
+ }
722
+ // Generate unsubscribe URL
723
+ const unsubscribeUrl = this.unsubscribeManager.generateUnsubscribeUrl(recipientEmail, listId);
724
+ // Create List-Unsubscribe headers
725
+ const unsubscribeHeaders = createListUnsubscribeHeaders(unsubscribeUrl, undefined, oneClick);
726
+ // Merge unsubscribe headers into email metadata (headers)
727
+ const emailWithHeaders = {
728
+ ...email,
729
+ metadata: {
730
+ ...email.metadata,
731
+ unsubscribeHeaders,
732
+ listId,
733
+ },
734
+ };
735
+ return this.sendEmail(emailWithHeaders);
736
+ }
737
+ /**
738
+ * Set a custom subscription store (e.g., database-backed).
739
+ *
740
+ * Replaces the default InMemorySubscriptionStore and reinitializes
741
+ * all subscription managers to use the new store.
742
+ *
743
+ * @param store - A SubscriptionStore implementation
744
+ */
745
+ setSubscriptionStore(store) {
746
+ this.subscriptionStore = store;
747
+ this.subscriptionListManager = new SubscriptionListManager(store);
748
+ this.suppressionManager = new SuppressionManager(store);
749
+ if (this.enhancedConfig.subscription?.unsubscribe) {
750
+ this.unsubscribeManager = new UnsubscribeManager(store, this.enhancedConfig.subscription.unsubscribe);
751
+ }
752
+ }
753
+ // ======================
443
754
  // System Operations
444
755
  // ======================
445
756
  /**
@@ -507,21 +818,39 @@ export class EnhancedEmailManager extends EmailManager {
507
818
  const templateStats = await this.getTemplateSystemStats();
508
819
  const senderStats = await this.getSenderSystemStats();
509
820
  const providerStats = await this.getProviderHealthStats();
510
- const overallHealth = this.calculateOverallHealth([
821
+ const subscriptionStats = await this.getSubscriptionHealthStats();
822
+ const batchSenderStats = this.getBatchSenderHealthStats();
823
+ const allComponents = [
511
824
  emailStats,
512
825
  templateStats,
513
826
  senderStats,
514
827
  providerStats
515
- ]);
828
+ ];
829
+ // Only include subscription and batch sender in overall health
830
+ // calculation if they are configured
831
+ if (subscriptionStats) {
832
+ allComponents.push(subscriptionStats);
833
+ }
834
+ if (batchSenderStats) {
835
+ allComponents.push(batchSenderStats);
836
+ }
837
+ const overallHealth = this.calculateOverallHealth(allComponents);
838
+ const components = {
839
+ email: emailStats,
840
+ templates: templateStats,
841
+ senders: senderStats,
842
+ providers: providerStats
843
+ };
844
+ if (subscriptionStats) {
845
+ components.subscription = subscriptionStats;
846
+ }
847
+ if (batchSenderStats) {
848
+ components.batchSender = batchSenderStats;
849
+ }
516
850
  return {
517
851
  overall: overallHealth,
518
852
  timestamp: new Date(),
519
- components: {
520
- email: emailStats,
521
- templates: templateStats,
522
- senders: senderStats,
523
- providers: providerStats
524
- },
853
+ components,
525
854
  recommendations: this.generateHealthRecommendations({
526
855
  emailStats,
527
856
  templateStats,
@@ -722,6 +1051,81 @@ export class EnhancedEmailManager extends EmailManager {
722
1051
  issues
723
1052
  };
724
1053
  }
1054
+ async getSubscriptionHealthStats() {
1055
+ if (!this.enhancedConfig.subscription?.enabled) {
1056
+ return null;
1057
+ }
1058
+ try {
1059
+ const lists = await this.subscriptionListManager.listLists();
1060
+ const suppressions = await this.suppressionManager.listSuppressions();
1061
+ const totalSubscribers = lists.items.reduce((sum, list) => sum + list.subscriberCount, 0);
1062
+ const issues = [];
1063
+ if (lists.total === 0) {
1064
+ issues.push({
1065
+ severity: 'info',
1066
+ message: 'No subscription lists configured',
1067
+ details: 'Create subscription lists to manage email recipients'
1068
+ });
1069
+ }
1070
+ if (suppressions.total > 0 && lists.total > 0) {
1071
+ const suppressionRatio = suppressions.total / Math.max(totalSubscribers, 1);
1072
+ if (suppressionRatio > 0.3) {
1073
+ issues.push({
1074
+ severity: 'warning',
1075
+ message: 'High suppression ratio',
1076
+ details: `${suppressions.total} suppressions vs ${totalSubscribers} subscribers`
1077
+ });
1078
+ }
1079
+ }
1080
+ const hasDegraded = issues.some(i => i.severity === 'warning');
1081
+ const score = Math.max(0, 100 - issues.filter(i => i.severity === 'warning').length * 20);
1082
+ return {
1083
+ status: hasDegraded ? 'degraded' : 'healthy',
1084
+ score,
1085
+ metrics: {
1086
+ listCount: lists.total,
1087
+ totalSubscribers,
1088
+ suppressionCount: suppressions.total
1089
+ },
1090
+ issues
1091
+ };
1092
+ }
1093
+ catch (error) {
1094
+ return {
1095
+ status: 'degraded',
1096
+ score: 50,
1097
+ metrics: {
1098
+ listCount: 0,
1099
+ totalSubscribers: 0,
1100
+ suppressionCount: 0
1101
+ },
1102
+ issues: [{
1103
+ severity: 'warning',
1104
+ message: 'Failed to retrieve subscription health',
1105
+ details: error instanceof Error ? error.message : 'Unknown error'
1106
+ }]
1107
+ };
1108
+ }
1109
+ }
1110
+ getBatchSenderHealthStats() {
1111
+ // The batch sender is available if the email-batch-sender package is
1112
+ // imported and the manager has at least one active provider configured.
1113
+ // Since createBatchSender is a factory that wraps an EmailSenderInterface,
1114
+ // it is always structurally available but only useful with providers.
1115
+ const hasProviders = this.enhancedConfig.providers.length > 0;
1116
+ if (!hasProviders) {
1117
+ return null;
1118
+ }
1119
+ return {
1120
+ status: 'healthy',
1121
+ score: 100,
1122
+ metrics: {
1123
+ available: 1,
1124
+ configured: 1
1125
+ },
1126
+ issues: []
1127
+ };
1128
+ }
725
1129
  calculateOverallHealth(components) {
726
1130
  const avgScore = components.reduce((sum, c) => sum + c.score, 0) / components.length;
727
1131
  const hasUnhealthy = components.some(c => c.status === 'unhealthy');