@spfn/notification 0.1.0-beta.13 → 0.1.0-beta.15
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 +127 -0
- package/dist/config/index.d.ts +80 -1
- package/dist/config/index.js +38 -0
- package/dist/config/index.js.map +1 -1
- package/dist/{index-BAe1ZBYQ.d.ts → index-DHgXI5Eq.d.ts} +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/server.d.ts +279 -4
- package/dist/server.js +456 -72
- package/dist/server.js.map +1 -1
- package/migrations/0001_romantic_starjammers.sql +15 -0
- package/migrations/meta/0001_snapshot.json +393 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Multi-channel notification system for SPFN applications.
|
|
|
9
9
|
- **Template system**: Variable substitution with filters
|
|
10
10
|
- **Scheduled delivery**: Schedule notifications for later via pg-boss
|
|
11
11
|
- **History tracking**: Optional notification history with database storage
|
|
12
|
+
- **Email tracking**: Open pixel & click redirect tracking with engagement analytics
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -70,6 +71,11 @@ SPFN_NOTIFICATION_EMAIL_FROM=noreply@example.com
|
|
|
70
71
|
# SMS
|
|
71
72
|
SPFN_NOTIFICATION_SMS_PROVIDER=aws-sns
|
|
72
73
|
|
|
74
|
+
# Tracking
|
|
75
|
+
SPFN_NOTIFICATION_TRACKING_ENABLED=true
|
|
76
|
+
SPFN_NOTIFICATION_TRACKING_SECRET=your-hmac-secret-key
|
|
77
|
+
SPFN_NOTIFICATION_TRACKING_BASE_URL=https://api.example.com
|
|
78
|
+
|
|
73
79
|
# AWS Credentials
|
|
74
80
|
AWS_REGION=ap-northeast-2
|
|
75
81
|
AWS_ACCESS_KEY_ID=xxx
|
|
@@ -95,6 +101,11 @@ configureNotification({
|
|
|
95
101
|
appName: 'MyApp',
|
|
96
102
|
},
|
|
97
103
|
enableHistory: true, // Enable notification history tracking
|
|
104
|
+
tracking: {
|
|
105
|
+
enabled: true, // Enable email tracking
|
|
106
|
+
secret: 'your-hmac-secret-key', // HMAC signing key
|
|
107
|
+
baseUrl: 'https://api.example.com', // Tracking endpoint URL
|
|
108
|
+
},
|
|
98
109
|
});
|
|
99
110
|
```
|
|
100
111
|
|
|
@@ -348,6 +359,114 @@ const scheduled = await findScheduledNotifications({
|
|
|
348
359
|
});
|
|
349
360
|
```
|
|
350
361
|
|
|
362
|
+
## Email Tracking
|
|
363
|
+
|
|
364
|
+
Track email opens and link clicks to measure engagement.
|
|
365
|
+
|
|
366
|
+
### How It Works
|
|
367
|
+
|
|
368
|
+
1. **Open tracking**: A 1x1 transparent GIF is inserted before `</body>`. When the email client loads the image, an open event is recorded.
|
|
369
|
+
2. **Click tracking**: `<a href>` links are wrapped with redirect URLs. When clicked, a click event is recorded and the user is redirected to the original URL.
|
|
370
|
+
3. Links with `mailto:`, `tel:`, `sms:`, `javascript:`, and `#` protocols are automatically skipped.
|
|
371
|
+
|
|
372
|
+
### Setup
|
|
373
|
+
|
|
374
|
+
Tracking requires:
|
|
375
|
+
- `enableHistory: true` (tracking events reference the history table via FK)
|
|
376
|
+
- `tracking.secret` configured (HMAC token signing)
|
|
377
|
+
- `tracking.baseUrl` configured (where tracking endpoints are accessible)
|
|
378
|
+
- `trackingRouter` registered in your app router
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { configureNotification, trackingRouter } from '@spfn/notification/server';
|
|
382
|
+
import { defineRouter } from '@spfn/core/route';
|
|
383
|
+
|
|
384
|
+
configureNotification({
|
|
385
|
+
enableHistory: true,
|
|
386
|
+
tracking: {
|
|
387
|
+
enabled: true,
|
|
388
|
+
secret: 'your-hmac-secret-key',
|
|
389
|
+
baseUrl: 'https://api.example.com',
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Register tracking router (provides /_noti/t/o/:token and /_noti/t/c/:token)
|
|
394
|
+
const appRouter = defineRouter({ ... })
|
|
395
|
+
.packages([trackingRouter]);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Per-Email Control
|
|
399
|
+
|
|
400
|
+
Override the global tracking setting for individual emails:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// Force tracking on for this email (even if globally disabled)
|
|
404
|
+
await sendEmail({
|
|
405
|
+
to: 'user@example.com',
|
|
406
|
+
template: 'campaign',
|
|
407
|
+
data: { ... },
|
|
408
|
+
tracking: true,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Force tracking off for this email (even if globally enabled)
|
|
412
|
+
await sendEmail({
|
|
413
|
+
to: 'admin@example.com',
|
|
414
|
+
subject: 'Internal Report',
|
|
415
|
+
html: '<p>...</p>',
|
|
416
|
+
tracking: false,
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Priority
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
sendEmail({ tracking: true/false }) ← 1st: per-call override
|
|
424
|
+
configureNotification({ tracking: { enabled } }) ← 2nd: code config
|
|
425
|
+
SPFN_NOTIFICATION_TRACKING_ENABLED=true ← 3rd: environment variable
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Tracking Endpoints
|
|
429
|
+
|
|
430
|
+
These endpoints skip authentication (accessed by email clients):
|
|
431
|
+
|
|
432
|
+
| Endpoint | Response | Action |
|
|
433
|
+
|----------|----------|--------|
|
|
434
|
+
| `GET /_noti/t/o/:token` | 200 + 1x1 GIF | Records open event |
|
|
435
|
+
| `GET /_noti/t/c/:token?url=...` | 302 redirect | Records click event, redirects to original URL |
|
|
436
|
+
|
|
437
|
+
- Invalid tokens still return pixel/redirect (UX protection)
|
|
438
|
+
- `Cache-Control: no-store` for re-open tracking
|
|
439
|
+
- DB writes are fire-and-forget (response speed first)
|
|
440
|
+
|
|
441
|
+
### Analytics
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import {
|
|
445
|
+
getTrackingStats,
|
|
446
|
+
getEngagementStats,
|
|
447
|
+
getClickDetails,
|
|
448
|
+
} from '@spfn/notification/server';
|
|
449
|
+
|
|
450
|
+
// Stats for a specific notification
|
|
451
|
+
const stats = await getTrackingStats(notificationId);
|
|
452
|
+
// { totalOpens: 15, uniqueOpens: 8, totalClicks: 5, uniqueClicks: 3 }
|
|
453
|
+
|
|
454
|
+
// Overall engagement stats
|
|
455
|
+
const engagement = await getEngagementStats({ channel: 'email' });
|
|
456
|
+
// { sent: 1000, opened: 450, clicked: 120, openRate: 45.00, clickRate: 12.00 }
|
|
457
|
+
|
|
458
|
+
// Click details per link
|
|
459
|
+
const clicks = await getClickDetails(notificationId);
|
|
460
|
+
// [{ linkUrl: 'https://...', linkIndex: 0, totalClicks: 5, uniqueClicks: 3 }]
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Unique counts are based on distinct IP addresses.
|
|
464
|
+
|
|
465
|
+
### Limitations
|
|
466
|
+
|
|
467
|
+
- **Open tracking is approximate**: Email clients like Apple Mail Privacy Protection may pre-fetch images, inflating open counts.
|
|
468
|
+
- Tracking auto-disables when `enableHistory: false` (FK dependency) or `tracking.secret` is not set.
|
|
469
|
+
|
|
351
470
|
## API Reference
|
|
352
471
|
|
|
353
472
|
### Exports
|
|
@@ -399,6 +518,14 @@ export {
|
|
|
399
518
|
findNotifications,
|
|
400
519
|
getNotificationStats,
|
|
401
520
|
|
|
521
|
+
// Tracking
|
|
522
|
+
trackingRouter,
|
|
523
|
+
processTrackingHtml,
|
|
524
|
+
getTrackingStats,
|
|
525
|
+
getEngagementStats,
|
|
526
|
+
getClickDetails,
|
|
527
|
+
isTrackingEnabled,
|
|
528
|
+
|
|
402
529
|
// Jobs
|
|
403
530
|
notificationJobRouter,
|
|
404
531
|
};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -42,6 +42,34 @@ declare const notificationEnvSchema: {
|
|
|
42
42
|
} & {
|
|
43
43
|
key: "SPFN_NOTIFICATION_SLACK_WEBHOOK_URL";
|
|
44
44
|
};
|
|
45
|
+
SPFN_NOTIFICATION_TRACKING_ENABLED: {
|
|
46
|
+
description: string;
|
|
47
|
+
default: string;
|
|
48
|
+
required: boolean;
|
|
49
|
+
examples: string[];
|
|
50
|
+
type: "string";
|
|
51
|
+
validator: (value: string) => string;
|
|
52
|
+
} & {
|
|
53
|
+
key: "SPFN_NOTIFICATION_TRACKING_ENABLED";
|
|
54
|
+
};
|
|
55
|
+
SPFN_NOTIFICATION_TRACKING_SECRET: {
|
|
56
|
+
description: string;
|
|
57
|
+
required: boolean;
|
|
58
|
+
sensitive: boolean;
|
|
59
|
+
type: "string";
|
|
60
|
+
validator: (value: string) => string;
|
|
61
|
+
} & {
|
|
62
|
+
key: "SPFN_NOTIFICATION_TRACKING_SECRET";
|
|
63
|
+
};
|
|
64
|
+
SPFN_NOTIFICATION_TRACKING_BASE_URL: {
|
|
65
|
+
description: string;
|
|
66
|
+
required: boolean;
|
|
67
|
+
examples: string[];
|
|
68
|
+
type: "string";
|
|
69
|
+
validator: (value: string) => string;
|
|
70
|
+
} & {
|
|
71
|
+
key: "SPFN_NOTIFICATION_TRACKING_BASE_URL";
|
|
72
|
+
};
|
|
45
73
|
AWS_REGION: {
|
|
46
74
|
description: string;
|
|
47
75
|
default: string;
|
|
@@ -111,6 +139,34 @@ declare const env: _spfn_core_env.InferEnvType<{
|
|
|
111
139
|
} & {
|
|
112
140
|
key: "SPFN_NOTIFICATION_SLACK_WEBHOOK_URL";
|
|
113
141
|
};
|
|
142
|
+
SPFN_NOTIFICATION_TRACKING_ENABLED: {
|
|
143
|
+
description: string;
|
|
144
|
+
default: string;
|
|
145
|
+
required: boolean;
|
|
146
|
+
examples: string[];
|
|
147
|
+
type: "string";
|
|
148
|
+
validator: (value: string) => string;
|
|
149
|
+
} & {
|
|
150
|
+
key: "SPFN_NOTIFICATION_TRACKING_ENABLED";
|
|
151
|
+
};
|
|
152
|
+
SPFN_NOTIFICATION_TRACKING_SECRET: {
|
|
153
|
+
description: string;
|
|
154
|
+
required: boolean;
|
|
155
|
+
sensitive: boolean;
|
|
156
|
+
type: "string";
|
|
157
|
+
validator: (value: string) => string;
|
|
158
|
+
} & {
|
|
159
|
+
key: "SPFN_NOTIFICATION_TRACKING_SECRET";
|
|
160
|
+
};
|
|
161
|
+
SPFN_NOTIFICATION_TRACKING_BASE_URL: {
|
|
162
|
+
description: string;
|
|
163
|
+
required: boolean;
|
|
164
|
+
examples: string[];
|
|
165
|
+
type: "string";
|
|
166
|
+
validator: (value: string) => string;
|
|
167
|
+
} & {
|
|
168
|
+
key: "SPFN_NOTIFICATION_TRACKING_BASE_URL";
|
|
169
|
+
};
|
|
114
170
|
AWS_REGION: {
|
|
115
171
|
description: string;
|
|
116
172
|
default: string;
|
|
@@ -164,6 +220,17 @@ interface NotificationConfig {
|
|
|
164
220
|
* @default false
|
|
165
221
|
*/
|
|
166
222
|
enableHistory?: boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Email engagement tracking configuration
|
|
225
|
+
*/
|
|
226
|
+
tracking?: {
|
|
227
|
+
/** Enable tracking (default: false) */
|
|
228
|
+
enabled?: boolean;
|
|
229
|
+
/** HMAC secret key for token signing */
|
|
230
|
+
secret?: string;
|
|
231
|
+
/** Base URL for tracking endpoints */
|
|
232
|
+
baseUrl?: string;
|
|
233
|
+
};
|
|
167
234
|
}
|
|
168
235
|
/**
|
|
169
236
|
* Configure notification settings
|
|
@@ -193,5 +260,17 @@ declare function getAppName(): string;
|
|
|
193
260
|
* Check if history tracking is enabled
|
|
194
261
|
*/
|
|
195
262
|
declare function isHistoryEnabled(): boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Check if tracking is enabled (config → env → false)
|
|
265
|
+
*/
|
|
266
|
+
declare function isTrackingEnabled(): boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Get tracking HMAC secret
|
|
269
|
+
*/
|
|
270
|
+
declare function getTrackingSecret(): string | undefined;
|
|
271
|
+
/**
|
|
272
|
+
* Get tracking base URL
|
|
273
|
+
*/
|
|
274
|
+
declare function getTrackingBaseUrl(): string | undefined;
|
|
196
275
|
|
|
197
|
-
export { type NotificationConfig, configureNotification, env, getAppName, getEmailFrom, getEmailReplyTo, getNotificationConfig, getSmsDefaultCountryCode, isHistoryEnabled, notificationEnvSchema };
|
|
276
|
+
export { type NotificationConfig, configureNotification, env, getAppName, getEmailFrom, getEmailReplyTo, getNotificationConfig, getSmsDefaultCountryCode, getTrackingBaseUrl, getTrackingSecret, isHistoryEnabled, isTrackingEnabled, notificationEnvSchema };
|
package/dist/config/index.js
CHANGED
|
@@ -37,6 +37,29 @@ var notificationEnvSchema = defineEnvSchema({
|
|
|
37
37
|
examples: ["https://hooks.slack.com/services/xxx/xxx/xxx"]
|
|
38
38
|
})
|
|
39
39
|
},
|
|
40
|
+
// Tracking
|
|
41
|
+
SPFN_NOTIFICATION_TRACKING_ENABLED: {
|
|
42
|
+
...envString({
|
|
43
|
+
description: "Enable email engagement tracking (open/click)",
|
|
44
|
+
default: "false",
|
|
45
|
+
required: false,
|
|
46
|
+
examples: ["true", "false"]
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
SPFN_NOTIFICATION_TRACKING_SECRET: {
|
|
50
|
+
...envString({
|
|
51
|
+
description: "HMAC secret key for tracking token signing",
|
|
52
|
+
required: false,
|
|
53
|
+
sensitive: true
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
SPFN_NOTIFICATION_TRACKING_BASE_URL: {
|
|
57
|
+
...envString({
|
|
58
|
+
description: "Base URL for tracking endpoints",
|
|
59
|
+
required: false,
|
|
60
|
+
examples: ["https://api.example.com"]
|
|
61
|
+
})
|
|
62
|
+
},
|
|
40
63
|
// AWS (shared with other AWS services)
|
|
41
64
|
AWS_REGION: {
|
|
42
65
|
...envString({
|
|
@@ -87,6 +110,18 @@ function getAppName() {
|
|
|
87
110
|
function isHistoryEnabled() {
|
|
88
111
|
return globalConfig.enableHistory ?? false;
|
|
89
112
|
}
|
|
113
|
+
function isTrackingEnabled() {
|
|
114
|
+
if (globalConfig.tracking?.enabled != null) {
|
|
115
|
+
return globalConfig.tracking.enabled;
|
|
116
|
+
}
|
|
117
|
+
return env.SPFN_NOTIFICATION_TRACKING_ENABLED === "true";
|
|
118
|
+
}
|
|
119
|
+
function getTrackingSecret() {
|
|
120
|
+
return globalConfig.tracking?.secret ?? env.SPFN_NOTIFICATION_TRACKING_SECRET;
|
|
121
|
+
}
|
|
122
|
+
function getTrackingBaseUrl() {
|
|
123
|
+
return globalConfig.tracking?.baseUrl ?? env.SPFN_NOTIFICATION_TRACKING_BASE_URL;
|
|
124
|
+
}
|
|
90
125
|
export {
|
|
91
126
|
configureNotification,
|
|
92
127
|
env,
|
|
@@ -95,7 +130,10 @@ export {
|
|
|
95
130
|
getEmailReplyTo,
|
|
96
131
|
getNotificationConfig,
|
|
97
132
|
getSmsDefaultCountryCode,
|
|
133
|
+
getTrackingBaseUrl,
|
|
134
|
+
getTrackingSecret,
|
|
98
135
|
isHistoryEnabled,
|
|
136
|
+
isTrackingEnabled,
|
|
99
137
|
notificationEnvSchema
|
|
100
138
|
};
|
|
101
139
|
//# sourceMappingURL=index.js.map
|
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/index.ts","../../src/config/schema.ts"],"sourcesContent":["/**\n * @spfn/notification - Configuration\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { notificationEnvSchema } from './schema';\n\nexport { notificationEnvSchema };\n\n/**\n * Environment registry\n */\nconst registry = createEnvRegistry(notificationEnvSchema);\nexport const env = registry.validate();\n\n/**\n * Notification configuration\n */\nexport interface NotificationConfig\n{\n email?: {\n provider?: 'aws-ses' | 'sendgrid' | 'smtp';\n from?: string;\n replyTo?: string;\n };\n sms?: {\n provider?: 'aws-sns' | 'twilio';\n defaultCountryCode?: string;\n };\n slack?: {\n webhookUrl?: string;\n };\n defaults?: {\n appName?: string;\n };\n /**\n * Enable notification history tracking (requires database)\n * @default false\n */\n enableHistory?: boolean;\n}\n\nlet globalConfig: NotificationConfig = {};\n\n/**\n * Configure notification settings\n */\nexport function configureNotification(config: NotificationConfig): void\n{\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current notification configuration\n */\nexport function getNotificationConfig(): NotificationConfig\n{\n return { ...globalConfig };\n}\n\n/**\n * Get email from address\n */\nexport function getEmailFrom(): string\n{\n return globalConfig.email?.from || env.SPFN_NOTIFICATION_EMAIL_FROM || 'noreply@example.com';\n}\n\n/**\n * Get email reply-to address\n */\nexport function getEmailReplyTo(): string | undefined\n{\n return globalConfig.email?.replyTo;\n}\n\n/**\n * Get SMS default country code\n */\nexport function getSmsDefaultCountryCode(): string\n{\n return globalConfig.sms?.defaultCountryCode || '+82';\n}\n\n/**\n * Get app name for templates\n */\nexport function getAppName(): string\n{\n return globalConfig.defaults?.appName || 'SPFN';\n}\n\n/**\n * Check if history tracking is enabled\n */\nexport function isHistoryEnabled(): boolean\n{\n return globalConfig.enableHistory ?? false;\n}\n","/**\n * @spfn/notification - Environment Schema\n */\n\nimport { defineEnvSchema, envString } from '@spfn/core/env';\n\nexport const notificationEnvSchema = defineEnvSchema({\n // Email\n SPFN_NOTIFICATION_EMAIL_PROVIDER: {\n ...envString({\n description: 'Email provider (aws-ses, sendgrid, smtp)',\n default: 'aws-ses',\n required: false,\n examples: ['aws-ses', 'sendgrid', 'smtp'],\n }),\n },\n\n SPFN_NOTIFICATION_EMAIL_FROM: {\n ...envString({\n description: 'Default sender email address',\n required: false,\n examples: ['noreply@example.com'],\n }),\n },\n\n // SMS\n SPFN_NOTIFICATION_SMS_PROVIDER: {\n ...envString({\n description: 'SMS provider (aws-sns, twilio)',\n default: 'aws-sns',\n required: false,\n examples: ['aws-sns', 'twilio'],\n }),\n },\n\n // Slack\n SPFN_NOTIFICATION_SLACK_WEBHOOK_URL: {\n ...envString({\n description: 'Slack webhook URL',\n required: false,\n examples: ['https://hooks.slack.com/services/xxx/xxx/xxx'],\n }),\n },\n\n // AWS (shared with other AWS services)\n AWS_REGION: {\n ...envString({\n description: 'AWS region',\n default: 'ap-northeast-2',\n required: false,\n examples: ['ap-northeast-2', 'us-east-1'],\n }),\n },\n\n AWS_ACCESS_KEY_ID: {\n ...envString({\n description: 'AWS access key ID',\n required: false,\n sensitive: true,\n }),\n },\n\n AWS_SECRET_ACCESS_KEY: {\n ...envString({\n description: 'AWS secret access key',\n required: false,\n sensitive: true,\n }),\n },\n});\n"],"mappings":";AAIA,SAAS,yBAAyB;;;ACAlC,SAAS,iBAAiB,iBAAiB;AAEpC,IAAM,wBAAwB,gBAAgB;AAAA;AAAA,EAEjD,kCAAkC;AAAA,IAC9B,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,CAAC,WAAW,YAAY,MAAM;AAAA,IAC5C,CAAC;AAAA,EACL;AAAA,EAEA,8BAA8B;AAAA,IAC1B,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,UAAU,CAAC,qBAAqB;AAAA,IACpC,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,gCAAgC;AAAA,IAC5B,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,CAAC,WAAW,QAAQ;AAAA,IAClC,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,qCAAqC;AAAA,IACjC,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,UAAU,CAAC,8CAA8C;AAAA,IAC7D,CAAC;AAAA,EACL;AAAA;AAAA,EAGA,YAAY;AAAA,IACR,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,SAAS;AAAA,MACT,UAAU;AAAA,MACV,UAAU,CAAC,kBAAkB,WAAW;AAAA,IAC5C,CAAC;AAAA,EACL;AAAA,EAEA,mBAAmB;AAAA,IACf,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,WAAW;AAAA,IACf,CAAC;AAAA,EACL;AAAA,EAEA,uBAAuB;AAAA,IACnB,GAAG,UAAU;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,WAAW;AAAA,IACf,CAAC;AAAA,EACL;AACJ,CAAC;;;
|
|
1
|
+
{"version":3,"sources":["../../src/config/index.ts","../../src/config/schema.ts"],"sourcesContent":["/**\n * @spfn/notification - Configuration\n */\n\nimport { createEnvRegistry } from '@spfn/core/env';\nimport { notificationEnvSchema } from './schema';\n\nexport { notificationEnvSchema };\n\n/**\n * Environment registry\n */\nconst registry = createEnvRegistry(notificationEnvSchema);\nexport const env = registry.validate();\n\n/**\n * Notification configuration\n */\nexport interface NotificationConfig\n{\n email?: {\n provider?: 'aws-ses' | 'sendgrid' | 'smtp';\n from?: string;\n replyTo?: string;\n };\n sms?: {\n provider?: 'aws-sns' | 'twilio';\n defaultCountryCode?: string;\n };\n slack?: {\n webhookUrl?: string;\n };\n defaults?: {\n appName?: string;\n };\n /**\n * Enable notification history tracking (requires database)\n * @default false\n */\n enableHistory?: boolean;\n /**\n * Email engagement tracking configuration\n */\n tracking?: {\n /** Enable tracking (default: false) */\n enabled?: boolean;\n /** HMAC secret key for token signing */\n secret?: string;\n /** Base URL for tracking endpoints */\n baseUrl?: string;\n };\n}\n\nlet globalConfig: NotificationConfig = {};\n\n/**\n * Configure notification settings\n */\nexport function configureNotification(config: NotificationConfig): void\n{\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current notification configuration\n */\nexport function getNotificationConfig(): NotificationConfig\n{\n return { ...globalConfig };\n}\n\n/**\n * Get email from address\n */\nexport function getEmailFrom(): string\n{\n return globalConfig.email?.from || env.SPFN_NOTIFICATION_EMAIL_FROM || 'noreply@example.com';\n}\n\n/**\n * Get email reply-to address\n */\nexport function getEmailReplyTo(): string | undefined\n{\n return globalConfig.email?.replyTo;\n}\n\n/**\n * Get SMS default country code\n */\nexport function getSmsDefaultCountryCode(): string\n{\n return globalConfig.sms?.defaultCountryCode || '+82';\n}\n\n/**\n * Get app name for templates\n */\nexport function getAppName(): string\n{\n return globalConfig.defaults?.appName || 'SPFN';\n}\n\n/**\n * Check if history tracking is enabled\n */\nexport function isHistoryEnabled(): boolean\n{\n return globalConfig.enableHistory ?? false;\n}\n\n/**\n * Check if tracking is enabled (config → env → false)\n */\nexport function isTrackingEnabled(): boolean\n{\n if (globalConfig.tracking?.enabled != null)\n {\n return globalConfig.tracking.enabled;\n }\n return env.SPFN_NOTIFICATION_TRACKING_ENABLED === 'true';\n}\n\n/**\n * Get tracking HMAC secret\n */\nexport function getTrackingSecret(): string | undefined\n{\n return globalConfig.tracking?.secret ?? env.SPFN_NOTIFICATION_TRACKING_SECRET;\n}\n\n/**\n * Get tracking base URL\n */\nexport function getTrackingBaseUrl(): string | undefined\n{\n return globalConfig.tracking?.baseUrl ?? env.SPFN_NOTIFICATION_TRACKING_BASE_URL;\n}\n","/**\n * @spfn/notification - Environment Schema\n */\n\nimport { defineEnvSchema, envString } from '@spfn/core/env';\n\nexport const notificationEnvSchema = defineEnvSchema({\n // Email\n SPFN_NOTIFICATION_EMAIL_PROVIDER: {\n ...envString({\n description: 'Email provider (aws-ses, sendgrid, smtp)',\n default: 'aws-ses',\n required: false,\n examples: ['aws-ses', 'sendgrid', 'smtp'],\n }),\n },\n\n SPFN_NOTIFICATION_EMAIL_FROM: {\n ...envString({\n description: 'Default sender email address',\n required: false,\n examples: ['noreply@example.com'],\n }),\n },\n\n // SMS\n SPFN_NOTIFICATION_SMS_PROVIDER: {\n ...envString({\n description: 'SMS provider (aws-sns, twilio)',\n default: 'aws-sns',\n required: false,\n examples: ['aws-sns', 'twilio'],\n }),\n },\n\n // Slack\n SPFN_NOTIFICATION_SLACK_WEBHOOK_URL: {\n ...envString({\n description: 'Slack webhook URL',\n required: false,\n examples: ['https://hooks.slack.com/services/xxx/xxx/xxx'],\n }),\n },\n\n // Tracking\n SPFN_NOTIFICATION_TRACKING_ENABLED: {\n ...envString({\n description: 'Enable email engagement tracking (open/click)',\n default: 'false',\n required: false,\n examples: ['true', 'false'],\n }),\n },\n\n SPFN_NOTIFICATION_TRACKING_SECRET: {\n ...envString({\n description: 'HMAC secret key for tracking token signing',\n required: false,\n sensitive: true,\n }),\n },\n\n SPFN_NOTIFICATION_TRACKING_BASE_URL: {\n ...envString({\n description: 'Base URL for tracking endpoints',\n required: false,\n examples: ['https://api.example.com'],\n }),\n },\n\n // AWS (shared with other AWS services)\n AWS_REGION: {\n ...envString({\n description: 'AWS region',\n default: 'ap-northeast-2',\n required: false,\n examples: ['ap-northeast-2', 'us-east-1'],\n }),\n },\n\n AWS_ACCESS_KEY_ID: {\n ...envString({\n description: 'AWS access key ID',\n required: false,\n sensitive: true,\n }),\n },\n\n AWS_SECRET_ACCESS_KEY: {\n ...envString({\n description: 'AWS secret access key',\n required: false,\n sensitive: true,\n }),\n },\n});\n"],"mappings":"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","names":[]}
|
|
@@ -61,6 +61,11 @@ interface SendEmailParams {
|
|
|
61
61
|
* Reply-to address
|
|
62
62
|
*/
|
|
63
63
|
replyTo?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Enable/disable engagement tracking for this email.
|
|
66
|
+
* When undefined, falls back to global tracking config.
|
|
67
|
+
*/
|
|
68
|
+
tracking?: boolean;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
66
71
|
* Email provider interface
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
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-
|
|
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-DHgXI5Eq.js';
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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, d as SendSlackParams, e as SlackProvider, T as TemplateDefinition, f as TemplateData, N as NotificationChannel$1, R as RenderedTemplate } from './index-
|
|
3
|
-
export { g as EmailTemplateContent, i as SlackTemplateContent, h as SmsTemplateContent } from './index-
|
|
1
|
+
export { NotificationConfig, configureNotification, getAppName, getEmailFrom, getEmailReplyTo, getNotificationConfig, getSmsDefaultCountryCode, getTrackingBaseUrl, getTrackingSecret, isTrackingEnabled } from './config/index.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-DHgXI5Eq.js';
|
|
3
|
+
export { g as EmailTemplateContent, i as SlackTemplateContent, h as SmsTemplateContent } from './index-DHgXI5Eq.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
|
+
import * as _spfn_core_route from '@spfn/core/route';
|
|
7
|
+
import * as _sinclair_typebox from '@sinclair/typebox';
|
|
6
8
|
import '@spfn/core/env';
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -510,6 +512,184 @@ declare const notifications: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
510
512
|
type Notification = typeof notifications.$inferSelect;
|
|
511
513
|
type NewNotification = typeof notifications.$inferInsert;
|
|
512
514
|
|
|
515
|
+
/**
|
|
516
|
+
* @spfn/notification - Tracking Events Entity
|
|
517
|
+
*
|
|
518
|
+
* Stores email engagement tracking events (opens, clicks)
|
|
519
|
+
*/
|
|
520
|
+
/**
|
|
521
|
+
* Tracking event types
|
|
522
|
+
*/
|
|
523
|
+
declare const TRACKING_EVENT_TYPES: readonly ["open", "click"];
|
|
524
|
+
type TrackingEventType = typeof TRACKING_EVENT_TYPES[number];
|
|
525
|
+
/**
|
|
526
|
+
* Tracking events table - stores email engagement events
|
|
527
|
+
*/
|
|
528
|
+
declare const trackingEvents: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
529
|
+
name: "tracking_events";
|
|
530
|
+
schema: string;
|
|
531
|
+
columns: {
|
|
532
|
+
createdAt: drizzle_orm_pg_core.PgColumn<{
|
|
533
|
+
name: "created_at";
|
|
534
|
+
tableName: "tracking_events";
|
|
535
|
+
dataType: "date";
|
|
536
|
+
columnType: "PgTimestamp";
|
|
537
|
+
data: Date;
|
|
538
|
+
driverParam: string;
|
|
539
|
+
notNull: true;
|
|
540
|
+
hasDefault: true;
|
|
541
|
+
isPrimaryKey: false;
|
|
542
|
+
isAutoincrement: false;
|
|
543
|
+
hasRuntimeDefault: false;
|
|
544
|
+
enumValues: undefined;
|
|
545
|
+
baseColumn: never;
|
|
546
|
+
identity: undefined;
|
|
547
|
+
generated: undefined;
|
|
548
|
+
}, {}, {}>;
|
|
549
|
+
updatedAt: drizzle_orm_pg_core.PgColumn<{
|
|
550
|
+
name: "updated_at";
|
|
551
|
+
tableName: "tracking_events";
|
|
552
|
+
dataType: "date";
|
|
553
|
+
columnType: "PgTimestamp";
|
|
554
|
+
data: Date;
|
|
555
|
+
driverParam: string;
|
|
556
|
+
notNull: true;
|
|
557
|
+
hasDefault: true;
|
|
558
|
+
isPrimaryKey: false;
|
|
559
|
+
isAutoincrement: false;
|
|
560
|
+
hasRuntimeDefault: false;
|
|
561
|
+
enumValues: undefined;
|
|
562
|
+
baseColumn: never;
|
|
563
|
+
identity: undefined;
|
|
564
|
+
generated: undefined;
|
|
565
|
+
}, {}, {}>;
|
|
566
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
567
|
+
name: "id";
|
|
568
|
+
tableName: "tracking_events";
|
|
569
|
+
dataType: "number";
|
|
570
|
+
columnType: "PgBigSerial53";
|
|
571
|
+
data: number;
|
|
572
|
+
driverParam: number;
|
|
573
|
+
notNull: true;
|
|
574
|
+
hasDefault: true;
|
|
575
|
+
isPrimaryKey: true;
|
|
576
|
+
isAutoincrement: false;
|
|
577
|
+
hasRuntimeDefault: false;
|
|
578
|
+
enumValues: undefined;
|
|
579
|
+
baseColumn: never;
|
|
580
|
+
identity: undefined;
|
|
581
|
+
generated: undefined;
|
|
582
|
+
}, {}, {}>;
|
|
583
|
+
notificationId: drizzle_orm_pg_core.PgColumn<{
|
|
584
|
+
name: "notification_id";
|
|
585
|
+
tableName: "tracking_events";
|
|
586
|
+
dataType: "number";
|
|
587
|
+
columnType: "PgInteger";
|
|
588
|
+
data: number;
|
|
589
|
+
driverParam: string | number;
|
|
590
|
+
notNull: true;
|
|
591
|
+
hasDefault: false;
|
|
592
|
+
isPrimaryKey: false;
|
|
593
|
+
isAutoincrement: false;
|
|
594
|
+
hasRuntimeDefault: false;
|
|
595
|
+
enumValues: undefined;
|
|
596
|
+
baseColumn: never;
|
|
597
|
+
identity: undefined;
|
|
598
|
+
generated: undefined;
|
|
599
|
+
}, {}, {}>;
|
|
600
|
+
type: drizzle_orm_pg_core.PgColumn<{
|
|
601
|
+
name: "type";
|
|
602
|
+
tableName: "tracking_events";
|
|
603
|
+
dataType: "string";
|
|
604
|
+
columnType: "PgText";
|
|
605
|
+
data: "open" | "click";
|
|
606
|
+
driverParam: string;
|
|
607
|
+
notNull: true;
|
|
608
|
+
hasDefault: false;
|
|
609
|
+
isPrimaryKey: false;
|
|
610
|
+
isAutoincrement: false;
|
|
611
|
+
hasRuntimeDefault: false;
|
|
612
|
+
enumValues: ["open", "click"];
|
|
613
|
+
baseColumn: never;
|
|
614
|
+
identity: undefined;
|
|
615
|
+
generated: undefined;
|
|
616
|
+
}, {}, {}>;
|
|
617
|
+
linkUrl: drizzle_orm_pg_core.PgColumn<{
|
|
618
|
+
name: "link_url";
|
|
619
|
+
tableName: "tracking_events";
|
|
620
|
+
dataType: "string";
|
|
621
|
+
columnType: "PgText";
|
|
622
|
+
data: string;
|
|
623
|
+
driverParam: string;
|
|
624
|
+
notNull: false;
|
|
625
|
+
hasDefault: false;
|
|
626
|
+
isPrimaryKey: false;
|
|
627
|
+
isAutoincrement: false;
|
|
628
|
+
hasRuntimeDefault: false;
|
|
629
|
+
enumValues: [string, ...string[]];
|
|
630
|
+
baseColumn: never;
|
|
631
|
+
identity: undefined;
|
|
632
|
+
generated: undefined;
|
|
633
|
+
}, {}, {}>;
|
|
634
|
+
linkIndex: drizzle_orm_pg_core.PgColumn<{
|
|
635
|
+
name: "link_index";
|
|
636
|
+
tableName: "tracking_events";
|
|
637
|
+
dataType: "number";
|
|
638
|
+
columnType: "PgInteger";
|
|
639
|
+
data: number;
|
|
640
|
+
driverParam: string | number;
|
|
641
|
+
notNull: false;
|
|
642
|
+
hasDefault: false;
|
|
643
|
+
isPrimaryKey: false;
|
|
644
|
+
isAutoincrement: false;
|
|
645
|
+
hasRuntimeDefault: false;
|
|
646
|
+
enumValues: undefined;
|
|
647
|
+
baseColumn: never;
|
|
648
|
+
identity: undefined;
|
|
649
|
+
generated: undefined;
|
|
650
|
+
}, {}, {}>;
|
|
651
|
+
ipAddress: drizzle_orm_pg_core.PgColumn<{
|
|
652
|
+
name: "ip_address";
|
|
653
|
+
tableName: "tracking_events";
|
|
654
|
+
dataType: "string";
|
|
655
|
+
columnType: "PgText";
|
|
656
|
+
data: string;
|
|
657
|
+
driverParam: string;
|
|
658
|
+
notNull: false;
|
|
659
|
+
hasDefault: false;
|
|
660
|
+
isPrimaryKey: false;
|
|
661
|
+
isAutoincrement: false;
|
|
662
|
+
hasRuntimeDefault: false;
|
|
663
|
+
enumValues: [string, ...string[]];
|
|
664
|
+
baseColumn: never;
|
|
665
|
+
identity: undefined;
|
|
666
|
+
generated: undefined;
|
|
667
|
+
}, {}, {}>;
|
|
668
|
+
userAgent: drizzle_orm_pg_core.PgColumn<{
|
|
669
|
+
name: "user_agent";
|
|
670
|
+
tableName: "tracking_events";
|
|
671
|
+
dataType: "string";
|
|
672
|
+
columnType: "PgText";
|
|
673
|
+
data: string;
|
|
674
|
+
driverParam: string;
|
|
675
|
+
notNull: false;
|
|
676
|
+
hasDefault: false;
|
|
677
|
+
isPrimaryKey: false;
|
|
678
|
+
isAutoincrement: false;
|
|
679
|
+
hasRuntimeDefault: false;
|
|
680
|
+
enumValues: [string, ...string[]];
|
|
681
|
+
baseColumn: never;
|
|
682
|
+
identity: undefined;
|
|
683
|
+
generated: undefined;
|
|
684
|
+
}, {}, {}>;
|
|
685
|
+
};
|
|
686
|
+
dialect: "pg";
|
|
687
|
+
}>;
|
|
688
|
+
/**
|
|
689
|
+
* Type inference
|
|
690
|
+
*/
|
|
691
|
+
type TrackingEvent = typeof trackingEvents.$inferSelect;
|
|
692
|
+
|
|
513
693
|
/**
|
|
514
694
|
* @spfn/notification - Notification Service
|
|
515
695
|
*
|
|
@@ -777,4 +957,99 @@ interface ErrorSlackOptions {
|
|
|
777
957
|
*/
|
|
778
958
|
declare function createErrorSlackNotifier(options?: ErrorSlackOptions): (err: Error, ctx: ErrorContext) => Promise<void>;
|
|
779
959
|
|
|
780
|
-
|
|
960
|
+
/**
|
|
961
|
+
* Tracking router
|
|
962
|
+
*/
|
|
963
|
+
declare const trackingRouter: _spfn_core_route.Router<{
|
|
964
|
+
trackOpen: _spfn_core_route.RouteDef<{
|
|
965
|
+
params: _sinclair_typebox.TObject<{
|
|
966
|
+
token: _sinclair_typebox.TString;
|
|
967
|
+
}>;
|
|
968
|
+
}, {}, Response>;
|
|
969
|
+
trackClick: _spfn_core_route.RouteDef<{
|
|
970
|
+
params: _sinclair_typebox.TObject<{
|
|
971
|
+
token: _sinclair_typebox.TString;
|
|
972
|
+
}>;
|
|
973
|
+
query: _sinclair_typebox.TObject<{
|
|
974
|
+
url: _sinclair_typebox.TString;
|
|
975
|
+
}>;
|
|
976
|
+
}, {}, Response>;
|
|
977
|
+
}>;
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* @spfn/notification - Tracking HTML Processor
|
|
981
|
+
*
|
|
982
|
+
* Injects open tracking pixel and wraps links for click tracking.
|
|
983
|
+
*/
|
|
984
|
+
interface ProcessTrackingOptions {
|
|
985
|
+
notificationId: number;
|
|
986
|
+
baseUrl: string;
|
|
987
|
+
}
|
|
988
|
+
interface TrackedLink {
|
|
989
|
+
index: number;
|
|
990
|
+
url: string;
|
|
991
|
+
}
|
|
992
|
+
interface ProcessTrackingResult {
|
|
993
|
+
html: string;
|
|
994
|
+
trackedLinks: TrackedLink[];
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Process HTML to inject tracking pixel and wrap links
|
|
998
|
+
*
|
|
999
|
+
* 1. Wraps <a href="..."> links with click tracking redirect URLs
|
|
1000
|
+
* 2. Inserts a 1x1 transparent GIF tracking pixel before </body>
|
|
1001
|
+
*/
|
|
1002
|
+
declare function processTrackingHtml(html: string, options: ProcessTrackingOptions): ProcessTrackingResult;
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* @spfn/notification - Tracking Service
|
|
1006
|
+
*
|
|
1007
|
+
* Records engagement events and provides analytics queries.
|
|
1008
|
+
*/
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* Tracking stats for a single notification
|
|
1012
|
+
*/
|
|
1013
|
+
interface TrackingStats {
|
|
1014
|
+
totalOpens: number;
|
|
1015
|
+
uniqueOpens: number;
|
|
1016
|
+
totalClicks: number;
|
|
1017
|
+
uniqueClicks: number;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Get tracking stats for a specific notification
|
|
1021
|
+
*/
|
|
1022
|
+
declare function getTrackingStats(notificationId: number): Promise<TrackingStats>;
|
|
1023
|
+
/**
|
|
1024
|
+
* Engagement stats across notifications
|
|
1025
|
+
*/
|
|
1026
|
+
interface EngagementStats {
|
|
1027
|
+
sent: number;
|
|
1028
|
+
opened: number;
|
|
1029
|
+
clicked: number;
|
|
1030
|
+
openRate: number;
|
|
1031
|
+
clickRate: number;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Get engagement stats with optional filters
|
|
1035
|
+
*/
|
|
1036
|
+
declare function getEngagementStats(options?: {
|
|
1037
|
+
channel?: NotificationChannel;
|
|
1038
|
+
from?: Date;
|
|
1039
|
+
to?: Date;
|
|
1040
|
+
}): Promise<EngagementStats>;
|
|
1041
|
+
/**
|
|
1042
|
+
* Click detail for a specific link
|
|
1043
|
+
*/
|
|
1044
|
+
interface ClickDetail {
|
|
1045
|
+
linkUrl: string;
|
|
1046
|
+
linkIndex: number;
|
|
1047
|
+
totalClicks: number;
|
|
1048
|
+
uniqueClicks: number;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Get click details for a specific notification
|
|
1052
|
+
*/
|
|
1053
|
+
declare function getClickDetails(notificationId: number): Promise<ClickDetail[]>;
|
|
1054
|
+
|
|
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 };
|