@spfn/notification 0.1.0-beta.1 → 0.1.0-beta.10

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
@@ -4,7 +4,7 @@ Multi-channel notification system for SPFN applications.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Multi-channel support**: Email, SMS (Slack, Push coming soon)
7
+ - **Multi-channel support**: Email, SMS, Slack (Push coming soon)
8
8
  - **Provider pattern**: Pluggable providers (AWS SES, AWS SNS, etc.)
9
9
  - **Template system**: Variable substitution with filters
10
10
  - **Scheduled delivery**: Schedule notifications for later via pg-boss
@@ -282,6 +282,32 @@ const twilioProvider: SMSProvider = {
282
282
  registerSMSProvider(twilioProvider);
283
283
  ```
284
284
 
285
+ ## Logging & Error Handling
286
+
287
+ All channels log via `@spfn/core/logger`. Logs are tagged by channel:
288
+
289
+ - `@spfn/notification:email` — Email send success/failure, validation errors
290
+ - `@spfn/notification:sms` — SMS send success/failure, validation errors
291
+ - `@spfn/notification:slack` — Slack send success/failure, validation errors
292
+ - `@spfn/notification:ses` — AWS SES client lifecycle, provider errors
293
+ - `@spfn/notification:sns` — AWS SNS client lifecycle, provider errors
294
+
295
+ `sendEmail`, `sendSMS`, `sendSlack` are designed to **not throw** — they return a `SendResult` object. Always check the result:
296
+
297
+ ```typescript
298
+ const result = await sendEmail({
299
+ to: 'user@example.com',
300
+ template: 'welcome',
301
+ data: { name: 'John' },
302
+ });
303
+
304
+ if (!result.success)
305
+ {
306
+ // result.error contains the failure reason
307
+ console.error('Email failed:', result.error);
308
+ }
309
+ ```
310
+
285
311
  ## Notification History
286
312
 
287
313
  Enable history tracking to store all notifications in the database:
@@ -333,6 +359,7 @@ export type {
333
359
  SendResult,
334
360
  SendEmailParams,
335
361
  SendSMSParams,
362
+ SendSlackParams,
336
363
  TemplateDefinition,
337
364
  TemplateData,
338
365
  };
@@ -353,6 +380,11 @@ export {
353
380
  sendSMSBulk,
354
381
  registerSMSProvider,
355
382
 
383
+ // Slack
384
+ sendSlack,
385
+ sendSlackBulk,
386
+ registerSlackProvider,
387
+
356
388
  // Scheduling
357
389
  scheduleEmail,
358
390
  scheduleSMS,
@@ -10,6 +10,7 @@ declare const notificationEnvSchema: {
10
10
  required: boolean;
11
11
  examples: string[];
12
12
  type: "string";
13
+ validator: (value: string) => string;
13
14
  } & {
14
15
  key: "SPFN_NOTIFICATION_EMAIL_PROVIDER";
15
16
  };
@@ -18,6 +19,7 @@ declare const notificationEnvSchema: {
18
19
  required: boolean;
19
20
  examples: string[];
20
21
  type: "string";
22
+ validator: (value: string) => string;
21
23
  } & {
22
24
  key: "SPFN_NOTIFICATION_EMAIL_FROM";
23
25
  };
@@ -27,6 +29,7 @@ declare const notificationEnvSchema: {
27
29
  required: boolean;
28
30
  examples: string[];
29
31
  type: "string";
32
+ validator: (value: string) => string;
30
33
  } & {
31
34
  key: "SPFN_NOTIFICATION_SMS_PROVIDER";
32
35
  };
@@ -35,6 +38,7 @@ declare const notificationEnvSchema: {
35
38
  required: boolean;
36
39
  examples: string[];
37
40
  type: "string";
41
+ validator: (value: string) => string;
38
42
  } & {
39
43
  key: "SPFN_NOTIFICATION_SLACK_WEBHOOK_URL";
40
44
  };
@@ -44,6 +48,7 @@ declare const notificationEnvSchema: {
44
48
  required: boolean;
45
49
  examples: string[];
46
50
  type: "string";
51
+ validator: (value: string) => string;
47
52
  } & {
48
53
  key: "AWS_REGION";
49
54
  };
@@ -52,6 +57,7 @@ declare const notificationEnvSchema: {
52
57
  required: boolean;
53
58
  sensitive: boolean;
54
59
  type: "string";
60
+ validator: (value: string) => string;
55
61
  } & {
56
62
  key: "AWS_ACCESS_KEY_ID";
57
63
  };
@@ -60,6 +66,7 @@ declare const notificationEnvSchema: {
60
66
  required: boolean;
61
67
  sensitive: boolean;
62
68
  type: "string";
69
+ validator: (value: string) => string;
63
70
  } & {
64
71
  key: "AWS_SECRET_ACCESS_KEY";
65
72
  };
@@ -72,6 +79,7 @@ declare const env: _spfn_core_env.InferEnvType<{
72
79
  required: boolean;
73
80
  examples: string[];
74
81
  type: "string";
82
+ validator: (value: string) => string;
75
83
  } & {
76
84
  key: "SPFN_NOTIFICATION_EMAIL_PROVIDER";
77
85
  };
@@ -80,6 +88,7 @@ declare const env: _spfn_core_env.InferEnvType<{
80
88
  required: boolean;
81
89
  examples: string[];
82
90
  type: "string";
91
+ validator: (value: string) => string;
83
92
  } & {
84
93
  key: "SPFN_NOTIFICATION_EMAIL_FROM";
85
94
  };
@@ -89,6 +98,7 @@ declare const env: _spfn_core_env.InferEnvType<{
89
98
  required: boolean;
90
99
  examples: string[];
91
100
  type: "string";
101
+ validator: (value: string) => string;
92
102
  } & {
93
103
  key: "SPFN_NOTIFICATION_SMS_PROVIDER";
94
104
  };
@@ -97,6 +107,7 @@ declare const env: _spfn_core_env.InferEnvType<{
97
107
  required: boolean;
98
108
  examples: string[];
99
109
  type: "string";
110
+ validator: (value: string) => string;
100
111
  } & {
101
112
  key: "SPFN_NOTIFICATION_SLACK_WEBHOOK_URL";
102
113
  };
@@ -106,6 +117,7 @@ declare const env: _spfn_core_env.InferEnvType<{
106
117
  required: boolean;
107
118
  examples: string[];
108
119
  type: "string";
120
+ validator: (value: string) => string;
109
121
  } & {
110
122
  key: "AWS_REGION";
111
123
  };
@@ -114,6 +126,7 @@ declare const env: _spfn_core_env.InferEnvType<{
114
126
  required: boolean;
115
127
  sensitive: boolean;
116
128
  type: "string";
129
+ validator: (value: string) => string;
117
130
  } & {
118
131
  key: "AWS_ACCESS_KEY_ID";
119
132
  };
@@ -122,6 +135,7 @@ declare const env: _spfn_core_env.InferEnvType<{
122
135
  required: boolean;
123
136
  sensitive: boolean;
124
137
  type: "string";
138
+ validator: (value: string) => string;
125
139
  } & {
126
140
  key: "AWS_SECRET_ACCESS_KEY";
127
141
  };
@@ -119,6 +119,50 @@ interface InternalSendSMSParams {
119
119
  message: string;
120
120
  }
121
121
 
122
+ /**
123
+ * @spfn/notification - Slack Channel Types
124
+ */
125
+
126
+ /**
127
+ * Parameters for sending Slack message
128
+ */
129
+ interface SendSlackParams {
130
+ /**
131
+ * Webhook URL (overrides env/config default)
132
+ */
133
+ webhookUrl?: string;
134
+ /**
135
+ * Plain text message
136
+ */
137
+ text?: string;
138
+ /**
139
+ * Slack Block Kit blocks
140
+ */
141
+ blocks?: unknown[];
142
+ /**
143
+ * Template name
144
+ */
145
+ template?: string;
146
+ /**
147
+ * Template data for variable substitution
148
+ */
149
+ data?: Record<string, unknown>;
150
+ }
151
+ /**
152
+ * Slack provider interface
153
+ */
154
+ interface SlackProvider extends ChannelProvider<InternalSendSlackParams> {
155
+ name: 'webhook' | string;
156
+ }
157
+ /**
158
+ * Internal send Slack params (after template rendering)
159
+ */
160
+ interface InternalSendSlackParams {
161
+ webhookUrl: string;
162
+ text?: string;
163
+ blocks?: unknown[];
164
+ }
165
+
122
166
  /**
123
167
  * @spfn/notification - Template Types
124
168
  */
@@ -167,4 +211,4 @@ interface RenderedTemplate {
167
211
  */
168
212
  type TemplateData = Record<string, unknown>;
169
213
 
170
- export type { EmailProvider as E, NotificationChannel as N, RenderedTemplate as R, SendEmailParams as S, TemplateDefinition as T, SendResult as a, SendSMSParams as b, SMSProvider as c, TemplateData as d, EmailTemplateContent as e, SmsTemplateContent as f, SlackTemplateContent as g };
214
+ export type { EmailProvider as E, NotificationChannel as N, RenderedTemplate as R, SendEmailParams as S, TemplateDefinition as T, SendResult as a, SendSMSParams as b, SMSProvider as c, SendSlackParams as d, SlackProvider as e, TemplateData as f, EmailTemplateContent as g, SmsTemplateContent as h, SlackTemplateContent as i };
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { e as EmailTemplateContent, N as NotificationChannel, S as SendEmailParams, a as SendResult, b as SendSMSParams, g as SlackTemplateContent, f as SmsTemplateContent, d as TemplateData, T as TemplateDefinition } from './index-D_XTwHmO.js';
1
+ export { g as EmailTemplateContent, N as NotificationChannel, S as SendEmailParams, a as SendResult, b as SendSMSParams, d as SendSlackParams, i as SlackTemplateContent, h as SmsTemplateContent, f as TemplateData, T as TemplateDefinition } from './index-BAe1ZBYQ.js';
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { NotificationConfig, configureNotification, getAppName, getEmailFrom, getEmailReplyTo, getNotificationConfig, getSmsDefaultCountryCode } from './config/index.js';
2
- import { S as SendEmailParams, a as SendResult, E as EmailProvider, b as SendSMSParams, c as SMSProvider, T as TemplateDefinition, d as TemplateData, N as NotificationChannel$1, R as RenderedTemplate } from './index-D_XTwHmO.js';
3
- export { e as EmailTemplateContent, g as SlackTemplateContent, f as SmsTemplateContent } from './index-D_XTwHmO.js';
2
+ import { S as SendEmailParams, a as SendResult, E as EmailProvider, b as SendSMSParams, c as SMSProvider, d as SendSlackParams, e as SlackProvider, T as TemplateDefinition, f as TemplateData, N as NotificationChannel$1, R as RenderedTemplate } from './index-BAe1ZBYQ.js';
3
+ export { g as EmailTemplateContent, i as SlackTemplateContent, h as SmsTemplateContent } from './index-BAe1ZBYQ.js';
4
4
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
5
5
  import * as _spfn_core_job from '@spfn/core/job';
6
6
  import '@spfn/core/env';
@@ -47,6 +47,27 @@ declare function sendSMSBulk(items: SendSMSParams[]): Promise<{
47
47
  failureCount: number;
48
48
  }>;
49
49
 
50
+ /**
51
+ * @spfn/notification - Slack Channel
52
+ */
53
+
54
+ /**
55
+ * Register custom Slack provider
56
+ */
57
+ declare function registerSlackProvider(provider: SlackProvider): void;
58
+ /**
59
+ * Send Slack message
60
+ */
61
+ declare function sendSlack(params: SendSlackParams): Promise<SendResult>;
62
+ /**
63
+ * Send bulk Slack messages
64
+ */
65
+ declare function sendSlackBulk(items: SendSlackParams[]): Promise<{
66
+ results: SendResult[];
67
+ successCount: number;
68
+ failureCount: number;
69
+ }>;
70
+
50
71
  /**
51
72
  * @spfn/notification - Schedule Service
52
73
  *
@@ -606,13 +627,13 @@ declare function cancelNotificationsByReference(referenceType: string, reference
606
627
  * Scheduled email sending job
607
628
  */
608
629
  declare const sendScheduledEmailJob: _spfn_core_job.JobDef<{
630
+ from?: string | undefined;
609
631
  subject?: string | undefined;
610
632
  html?: string | undefined;
611
633
  text?: string | undefined;
612
634
  data?: {
613
635
  [x: string]: unknown;
614
636
  } | undefined;
615
- from?: string | undefined;
616
637
  template?: string | undefined;
617
638
  replyTo?: string | undefined;
618
639
  to: string | string[];
@@ -657,13 +678,13 @@ declare const sendScheduledSmsJob: _spfn_core_job.JobDef<{
657
678
  */
658
679
  declare const notificationJobRouter: _spfn_core_job.JobRouter<{
659
680
  sendScheduledEmail: _spfn_core_job.JobDef<{
681
+ from?: string | undefined;
660
682
  subject?: string | undefined;
661
683
  html?: string | undefined;
662
684
  text?: string | undefined;
663
685
  data?: {
664
686
  [x: string]: unknown;
665
687
  } | undefined;
666
- from?: string | undefined;
667
688
  template?: string | undefined;
668
689
  replyTo?: string | undefined;
669
690
  to: string | string[];
@@ -680,4 +701,80 @@ declare const notificationJobRouter: _spfn_core_job.JobRouter<{
680
701
  }, void>;
681
702
  }>;
682
703
 
683
- export { type CancelResult, EmailProvider, 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, TemplateData, TemplateDefinition, cancelNotification, cancelNotificationsByReference, cancelScheduledNotification, countNotifications, createNotificationRecord, createScheduledNotification, findNotificationByJobId, findNotifications, findScheduledNotifications, getNotificationStats, getTemplate, getTemplateNames, hasTemplate, markNotificationFailed, markNotificationPending, markNotificationSent, notificationJobRouter, notifications, registerBuiltinTemplates, registerEmailProvider, registerFilter, registerSMSProvider, registerTemplate, renderTemplate, scheduleEmail, scheduleSMS, sendEmail, sendEmailBulk, sendSMS, sendSMSBulk, sendScheduledEmailJob, sendScheduledSmsJob, updateNotificationJobId };
704
+ /**
705
+ * @spfn/notification - Error → Slack Integration
706
+ *
707
+ * Factory for creating an onError callback that sends error notifications to Slack.
708
+ *
709
+ * @example
710
+ * ```typescript
711
+ * import { createServer } from '@spfn/core/server';
712
+ * import { createErrorSlackNotifier } from '@spfn/notification/server';
713
+ *
714
+ * await createServer({
715
+ * middleware: {
716
+ * onError: createErrorSlackNotifier({ minStatusCode: 500 }),
717
+ * },
718
+ * });
719
+ * ```
720
+ */
721
+ interface ErrorContext {
722
+ statusCode: number;
723
+ path: string;
724
+ method: string;
725
+ requestId?: string;
726
+ timestamp: string;
727
+ userId?: string;
728
+ request: {
729
+ headers: Record<string, string>;
730
+ query: Record<string, string>;
731
+ };
732
+ }
733
+ interface ErrorSlackOptions {
734
+ /**
735
+ * Minimum status code to trigger notification
736
+ * @default 500
737
+ */
738
+ minStatusCode?: number;
739
+ /**
740
+ * Throttle window in milliseconds.
741
+ * Duplicate errors (same name + statusCode + path) within this window are suppressed.
742
+ * @default 60_000
743
+ */
744
+ throttleMs?: number;
745
+ /**
746
+ * Webhook URL override (defaults to env/config)
747
+ */
748
+ webhookUrl?: string;
749
+ /**
750
+ * Custom message formatter
751
+ */
752
+ formatMessage?: (err: Error, ctx: ErrorContext) => {
753
+ text?: string;
754
+ blocks?: unknown[];
755
+ };
756
+ }
757
+ /**
758
+ * Create an onError callback that sends Slack notifications
759
+ *
760
+ * Returns a function matching ErrorHandler's onError signature.
761
+ * Duplicate errors (same name + statusCode + path) within `throttleMs` are suppressed.
762
+ *
763
+ * @deprecated Use `createMonitorErrorHandler()` from `@spfn/monitor/server` instead.
764
+ * It provides DB-backed error tracking with fingerprint deduplication,
765
+ * state-based notifications (new/reopened), and an admin dashboard.
766
+ *
767
+ * Migration:
768
+ * ```typescript
769
+ * // Before
770
+ * import { createErrorSlackNotifier } from '@spfn/notification/server';
771
+ * middleware: { onError: createErrorSlackNotifier() }
772
+ *
773
+ * // After
774
+ * import { createMonitorErrorHandler } from '@spfn/monitor/server';
775
+ * middleware: { onError: createMonitorErrorHandler() }
776
+ * ```
777
+ */
778
+ declare function createErrorSlackNotifier(options?: ErrorSlackOptions): (err: Error, ctx: ErrorContext) => Promise<void>;
779
+
780
+ export { type CancelResult, EmailProvider, 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, TemplateData, TemplateDefinition, cancelNotification, cancelNotificationsByReference, cancelScheduledNotification, countNotifications, createErrorSlackNotifier, createNotificationRecord, createScheduledNotification, findNotificationByJobId, findNotifications, findScheduledNotifications, getNotificationStats, getTemplate, getTemplateNames, hasTemplate, markNotificationFailed, markNotificationPending, markNotificationSent, notificationJobRouter, notifications, registerBuiltinTemplates, registerEmailProvider, registerFilter, registerSMSProvider, registerSlackProvider, registerTemplate, renderTemplate, scheduleEmail, scheduleSMS, sendEmail, sendEmailBulk, sendSMS, sendSMSBulk, sendScheduledEmailJob, sendScheduledSmsJob, sendSlack, sendSlackBulk, updateNotificationJobId };