@spfn/notification 0.1.0-beta.16 → 0.1.0-beta.18

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.
package/README.md CHANGED
@@ -22,8 +22,8 @@ pnpm add @spfn/notification
22
22
  Install providers as needed:
23
23
 
24
24
  ```bash
25
- # For AWS SES (Email)
26
- pnpm add @aws-sdk/client-ses
25
+ # For AWS SES v2 (Email)
26
+ pnpm add @aws-sdk/client-sesv2
27
27
 
28
28
  # For AWS SNS (SMS)
29
29
  pnpm add @aws-sdk/client-sns
@@ -131,11 +131,16 @@ await sendEmail({
131
131
  html: '<h1>HTML content</h1>',
132
132
  });
133
133
 
134
- // Bulk send
134
+ // Bulk send (in-process, parallel)
135
135
  await sendEmailBulk([
136
136
  { to: 'user1@example.com', template: 'welcome', data: { name: 'John' } },
137
137
  { to: 'user2@example.com', template: 'welcome', data: { name: 'Jane' } },
138
- ]);
138
+ ], { concurrency: 20 });
139
+
140
+ // Bulk send (distributed across instances via pg-boss)
141
+ const result = await sendEmailBulk(items, { distributed: true });
142
+ // result.batchId — UUID for tracking this batch
143
+ // Actual sending happens in background via pg-boss workers
139
144
  ```
140
145
 
141
146
  ### SMS
@@ -183,6 +188,47 @@ import { cancelNotification } from '@spfn/notification/server';
183
188
  await cancelNotification(result.notificationId);
184
189
  ```
185
190
 
191
+ ## Bulk Sending
192
+
193
+ All channels support bulk sending with two modes:
194
+
195
+ ### In-Process Mode (default)
196
+
197
+ Sends all items in the current process with concurrency control.
198
+ Suitable for single-instance deployments or smaller batches.
199
+
200
+ ```typescript
201
+ import { sendEmailBulk, sendSMSBulk, sendSlackBulk } from '@spfn/notification/server';
202
+
203
+ const result = await sendEmailBulk(items, { concurrency: 20 });
204
+ // result.results — SendResult[] in same order as input
205
+ // result.successCount / result.failureCount
206
+ // result.batchId — UUID for this batch
207
+ ```
208
+
209
+ ### Distributed Mode
210
+
211
+ Enqueues items to pg-boss for processing across multiple instances.
212
+ Each instance's worker fetches 50 items at a time and processes them in parallel.
213
+ Failed items are individually retried by pg-boss.
214
+
215
+ ```typescript
216
+ const result = await sendEmailBulk(items, { distributed: true });
217
+ // Returns immediately with pending results
218
+ // result.results[i].messageId === 'pending:{batchId}'
219
+ ```
220
+
221
+ **Distributed mode flow:**
222
+ 1. Validates and renders templates
223
+ 2. Batch inserts notification records (single query)
224
+ 3. Applies tracking (email only)
225
+ 4. Bulk inserts pg-boss jobs via `sendBatch()`
226
+ 5. Returns immediately — workers process in background
227
+
228
+ **Requirements:**
229
+ - `notificationJobRouter` must be registered (see Job Router Setup below)
230
+ - pg-boss must be initialized
231
+
186
232
  ### Job Router Setup
187
233
 
188
234
  Register the notification job router with your server:
@@ -493,16 +539,22 @@ export {
493
539
  sendEmail,
494
540
  sendEmailBulk,
495
541
  registerEmailProvider,
542
+ type BulkEmailResult,
543
+ type BulkEmailOptions,
496
544
 
497
545
  // SMS
498
546
  sendSMS,
499
547
  sendSMSBulk,
500
548
  registerSMSProvider,
549
+ type BulkSMSResult,
550
+ type BulkSMSOptions,
501
551
 
502
552
  // Slack
503
553
  sendSlack,
504
554
  sendSlackBulk,
505
555
  registerSlackProvider,
556
+ type BulkSlackResult,
557
+ type BulkSlackOptions,
506
558
 
507
559
  // Scheduling
508
560
  scheduleEmail,
@@ -528,6 +580,9 @@ export {
528
580
 
529
581
  // Jobs
530
582
  notificationJobRouter,
583
+ sendBulkEmailItemJob,
584
+ sendBulkSmsItemJob,
585
+ sendBulkSlackItemJob,
531
586
  };
532
587
  ```
533
588
 
package/dist/server.d.ts CHANGED
@@ -20,13 +20,37 @@ declare function registerEmailProvider(provider: EmailProvider): void;
20
20
  */
21
21
  declare function sendEmail(params: SendEmailParams): Promise<SendResult>;
22
22
  /**
23
- * Send bulk emails
23
+ * Bulk email result
24
24
  */
25
- declare function sendEmailBulk(items: SendEmailParams[]): Promise<{
25
+ interface BulkEmailResult {
26
26
  results: SendResult[];
27
27
  successCount: number;
28
28
  failureCount: number;
29
- }>;
29
+ batchId: string;
30
+ }
31
+ /**
32
+ * Bulk email options
33
+ */
34
+ interface BulkEmailOptions {
35
+ /**
36
+ * Max parallel sends when processing in-process (default: 10)
37
+ */
38
+ concurrency?: number;
39
+ /**
40
+ * When true, enqueue to pg-boss for distributed processing across instances.
41
+ * Returns immediately with pending results — actual sending happens in background.
42
+ * Requires notificationJobRouter to be registered.
43
+ * @default false
44
+ */
45
+ distributed?: boolean;
46
+ }
47
+ /**
48
+ * Send bulk emails with batch DB insert and concurrent sending.
49
+ *
50
+ * @param items - Email items to send
51
+ * @param options - concurrency, distributed
52
+ */
53
+ declare function sendEmailBulk(items: SendEmailParams[], options?: BulkEmailOptions): Promise<BulkEmailResult>;
30
54
 
31
55
  /**
32
56
  * @spfn/notification - SMS Channel
@@ -41,13 +65,28 @@ declare function registerSMSProvider(provider: SMSProvider): void;
41
65
  */
42
66
  declare function sendSMS(params: SendSMSParams): Promise<SendResult>;
43
67
  /**
44
- * Send bulk SMS
68
+ * Bulk SMS result
45
69
  */
46
- declare function sendSMSBulk(items: SendSMSParams[]): Promise<{
70
+ interface BulkSMSResult {
47
71
  results: SendResult[];
48
72
  successCount: number;
49
73
  failureCount: number;
50
- }>;
74
+ batchId: string;
75
+ }
76
+ /**
77
+ * Bulk SMS options
78
+ */
79
+ interface BulkSMSOptions {
80
+ concurrency?: number;
81
+ distributed?: boolean;
82
+ }
83
+ /**
84
+ * Send bulk SMS with batch DB insert and concurrent sending.
85
+ *
86
+ * @param items - SMS items to send
87
+ * @param options - concurrency, distributed
88
+ */
89
+ declare function sendSMSBulk(items: SendSMSParams[], options?: BulkSMSOptions): Promise<BulkSMSResult>;
51
90
 
52
91
  /**
53
92
  * @spfn/notification - Slack Channel
@@ -62,13 +101,28 @@ declare function registerSlackProvider(provider: SlackProvider): void;
62
101
  */
63
102
  declare function sendSlack(params: SendSlackParams): Promise<SendResult>;
64
103
  /**
65
- * Send bulk Slack messages
104
+ * Bulk Slack result
66
105
  */
67
- declare function sendSlackBulk(items: SendSlackParams[]): Promise<{
106
+ interface BulkSlackResult {
68
107
  results: SendResult[];
69
108
  successCount: number;
70
109
  failureCount: number;
71
- }>;
110
+ batchId: string;
111
+ }
112
+ /**
113
+ * Bulk Slack options
114
+ */
115
+ interface BulkSlackOptions {
116
+ concurrency?: number;
117
+ distributed?: boolean;
118
+ }
119
+ /**
120
+ * Send bulk Slack messages with batch DB insert and concurrent sending.
121
+ *
122
+ * @param items - Slack items to send
123
+ * @param options - concurrency, distributed
124
+ */
125
+ declare function sendSlackBulk(items: SendSlackParams[], options?: BulkSlackOptions): Promise<BulkSlackResult>;
72
126
 
73
127
  /**
74
128
  * @spfn/notification - Schedule Service
@@ -807,17 +861,17 @@ declare function cancelNotificationsByReference(referenceType: string, reference
807
861
  * Scheduled email sending job
808
862
  */
809
863
  declare const sendScheduledEmailJob: _spfn_core_job.JobDef<{
810
- from?: string | undefined;
811
- subject?: string | undefined;
812
- html?: string | undefined;
813
- text?: string | undefined;
814
864
  data?: {
815
865
  [x: string]: unknown;
816
866
  } | undefined;
817
- template?: string | undefined;
867
+ subject?: string | undefined;
868
+ from?: string | undefined;
869
+ html?: string | undefined;
870
+ text?: string | undefined;
818
871
  replyTo?: string | undefined;
819
- to: string | string[];
872
+ template?: string | undefined;
820
873
  notificationId: number;
874
+ to: string | string[];
821
875
  }, void>;
822
876
 
823
877
  /**
@@ -827,13 +881,56 @@ declare const sendScheduledEmailJob: _spfn_core_job.JobDef<{
827
881
  * Scheduled SMS sending job
828
882
  */
829
883
  declare const sendScheduledSmsJob: _spfn_core_job.JobDef<{
830
- message?: string | undefined;
831
884
  data?: {
832
885
  [x: string]: unknown;
833
886
  } | undefined;
887
+ message?: string | undefined;
834
888
  template?: string | undefined;
889
+ notificationId: number;
835
890
  to: string | string[];
891
+ }, void>;
892
+
893
+ /**
894
+ * @spfn/notification - Bulk Email Item Job
895
+ *
896
+ * Processes a single email item from a distributed bulk send.
897
+ * Notification record and tracking are already applied by sendEmailBulk().
898
+ * This job just sends via provider and updates history.
899
+ *
900
+ * Uses batchSize so pg-boss workers fetch multiple items at once
901
+ * and process them in parallel across instances.
902
+ */
903
+ declare const sendBulkEmailItemJob: _spfn_core_job.JobDef<{
904
+ html?: string | undefined;
905
+ text?: string | undefined;
906
+ replyTo?: string | undefined;
907
+ subject: string;
908
+ notificationId: number;
909
+ to: string[];
910
+ from: string;
911
+ }, void>;
912
+
913
+ /**
914
+ * @spfn/notification - Bulk SMS Item Job
915
+ *
916
+ * Processes a single SMS item from a distributed bulk send.
917
+ */
918
+ declare const sendBulkSmsItemJob: _spfn_core_job.JobDef<{
836
919
  notificationId: number;
920
+ to: string;
921
+ message: string;
922
+ }, void>;
923
+
924
+ /**
925
+ * @spfn/notification - Bulk Slack Item Job
926
+ *
927
+ * Processes a single Slack item from a distributed bulk send.
928
+ */
929
+ declare const sendBulkSlackItemJob: _spfn_core_job.JobDef<{
930
+ text?: string | undefined;
931
+ blocks?: unknown[] | undefined;
932
+ notificationId: number;
933
+ webhookUrl: string;
837
934
  }, void>;
838
935
 
839
936
  /**
@@ -858,26 +955,46 @@ declare const sendScheduledSmsJob: _spfn_core_job.JobDef<{
858
955
  */
859
956
  declare const notificationJobRouter: _spfn_core_job.JobRouter<{
860
957
  sendScheduledEmail: _spfn_core_job.JobDef<{
861
- from?: string | undefined;
862
- subject?: string | undefined;
863
- html?: string | undefined;
864
- text?: string | undefined;
865
958
  data?: {
866
959
  [x: string]: unknown;
867
960
  } | undefined;
868
- template?: string | undefined;
961
+ subject?: string | undefined;
962
+ from?: string | undefined;
963
+ html?: string | undefined;
964
+ text?: string | undefined;
869
965
  replyTo?: string | undefined;
870
- to: string | string[];
966
+ template?: string | undefined;
871
967
  notificationId: number;
968
+ to: string | string[];
872
969
  }, void>;
873
970
  sendScheduledSms: _spfn_core_job.JobDef<{
874
- message?: string | undefined;
875
971
  data?: {
876
972
  [x: string]: unknown;
877
973
  } | undefined;
974
+ message?: string | undefined;
878
975
  template?: string | undefined;
976
+ notificationId: number;
879
977
  to: string | string[];
978
+ }, void>;
979
+ sendBulkEmailItem: _spfn_core_job.JobDef<{
980
+ html?: string | undefined;
981
+ text?: string | undefined;
982
+ replyTo?: string | undefined;
983
+ subject: string;
984
+ notificationId: number;
985
+ to: string[];
986
+ from: string;
987
+ }, void>;
988
+ sendBulkSmsItem: _spfn_core_job.JobDef<{
989
+ notificationId: number;
990
+ to: string;
991
+ message: string;
992
+ }, void>;
993
+ sendBulkSlackItem: _spfn_core_job.JobDef<{
994
+ text?: string | undefined;
995
+ blocks?: unknown[] | undefined;
880
996
  notificationId: number;
997
+ webhookUrl: string;
881
998
  }, void>;
882
999
  }>;
883
1000
 
@@ -1052,4 +1169,4 @@ interface ClickDetail {
1052
1169
  */
1053
1170
  declare function getClickDetails(notificationId: number): Promise<ClickDetail[]>;
1054
1171
 
1055
- export { type CancelResult, type ClickDetail, EmailProvider, type EngagementStats, type ErrorSlackOptions, type FindNotificationsOptions, NOTIFICATION_CHANNELS, NOTIFICATION_STATUSES, type NewNotification, type Notification, NotificationChannel$1 as NotificationChannel, type NotificationStats, type NotificationStatus, SMSProvider, type ScheduleOptions, type ScheduleResult, SendEmailParams, SendResult, SendSMSParams, SendSlackParams, SlackProvider, TRACKING_EVENT_TYPES, TemplateData, TemplateDefinition, type TrackingEvent, type TrackingEventType, type TrackingStats, cancelNotification, cancelNotificationsByReference, cancelScheduledNotification, countNotifications, createErrorSlackNotifier, createNotificationRecord, createScheduledNotification, findNotificationByJobId, findNotifications, findScheduledNotifications, getClickDetails, getEngagementStats, getNotificationStats, getTemplate, getTemplateNames, getTrackingStats, hasTemplate, markNotificationFailed, markNotificationPending, markNotificationSent, notificationJobRouter, notifications, processTrackingHtml, registerBuiltinTemplates, registerEmailProvider, registerFilter, registerSMSProvider, registerSlackProvider, registerTemplate, renderTemplate, scheduleEmail, scheduleSMS, sendEmail, sendEmailBulk, sendSMS, sendSMSBulk, sendScheduledEmailJob, sendScheduledSmsJob, sendSlack, sendSlackBulk, trackingEvents, trackingRouter, updateNotificationJobId };
1172
+ export { type BulkEmailOptions, type BulkEmailResult, type BulkSMSOptions, type BulkSMSResult, type BulkSlackOptions, type BulkSlackResult, type CancelResult, type ClickDetail, EmailProvider, type EngagementStats, type ErrorSlackOptions, type FindNotificationsOptions, NOTIFICATION_CHANNELS, NOTIFICATION_STATUSES, type NewNotification, type Notification, NotificationChannel$1 as NotificationChannel, type NotificationStats, type NotificationStatus, SMSProvider, type ScheduleOptions, type ScheduleResult, SendEmailParams, SendResult, SendSMSParams, SendSlackParams, SlackProvider, TRACKING_EVENT_TYPES, TemplateData, TemplateDefinition, type TrackingEvent, type TrackingEventType, type TrackingStats, cancelNotification, cancelNotificationsByReference, cancelScheduledNotification, countNotifications, createErrorSlackNotifier, createNotificationRecord, createScheduledNotification, findNotificationByJobId, findNotifications, findScheduledNotifications, getClickDetails, getEngagementStats, getNotificationStats, getTemplate, getTemplateNames, getTrackingStats, hasTemplate, markNotificationFailed, markNotificationPending, markNotificationSent, notificationJobRouter, notifications, processTrackingHtml, registerBuiltinTemplates, registerEmailProvider, registerFilter, registerSMSProvider, registerSlackProvider, registerTemplate, renderTemplate, scheduleEmail, scheduleSMS, sendBulkEmailItemJob, sendBulkSlackItemJob, sendBulkSmsItemJob, sendEmail, sendEmailBulk, sendSMS, sendSMSBulk, sendScheduledEmailJob, sendScheduledSmsJob, sendSlack, sendSlackBulk, trackingEvents, trackingRouter, updateNotificationJobId };