@fonderie/courier 1.0.0 → 1.0.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.
@@ -0,0 +1,55 @@
1
+ <!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
2
+
3
+ # @fonderie/courier — outcomes
4
+
5
+ What this package does to a running app: tables its migrations create,
6
+ rows it seeds, routes it registers. Generated from the migration SQL and
7
+ route tables in source — trust this file instead of reading `dist/` or
8
+ downloading tarballs.
9
+
10
+ ## Database tables (after all migrations)
11
+
12
+ ### `fonderie_courier_templates`
13
+
14
+ ```sql
15
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid()
16
+ type TEXT NOT NULL
17
+ locale TEXT
18
+ subject TEXT
19
+ html TEXT
20
+ text TEXT NOT NULL
21
+ active BOOLEAN NOT NULL DEFAULT true
22
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
23
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
24
+ -- UNIQUE (type, locale)
25
+ ```
26
+
27
+ ### `fonderie_message_log`
28
+
29
+ ```sql
30
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid()
31
+ message_type TEXT NOT NULL
32
+ channel TEXT NOT NULL
33
+ recipient TEXT NOT NULL
34
+ locale TEXT
35
+ status TEXT NOT NULL DEFAULT 'pending'
36
+ error TEXT
37
+ attempts INT NOT NULL DEFAULT 0
38
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
39
+ sent_at TIMESTAMPTZ
40
+ provider TEXT
41
+ provider_message_id TEXT
42
+ opened_at TIMESTAMPTZ
43
+ clicked_at TIMESTAMPTZ
44
+ bounced_at TIMESTAMPTZ
45
+ bounce_reason TEXT
46
+ -- INDEX idx_fml_created (created_at DESC)
47
+ ```
48
+
49
+ Raw SQL ships in `node_modules/@fonderie/courier/dist/migrations/sql/` — read it there if you must; never download tarballs.
50
+
51
+ ## Migration statements not replayed (verify in raw SQL)
52
+
53
+ - `ELSE`
54
+ - `END IF`
55
+ - `END $$`
@@ -0,0 +1,136 @@
1
+ <!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
2
+
3
+ # @fonderie/courier — signatures
4
+
5
+ ## @fonderie/courier
6
+
7
+ Subpath exports: `@fonderie/courier/types`, `@fonderie/courier/migrations`
8
+
9
+ ```ts
10
+ new CourierModule(config: ICourierConfig, store?: IStoreAdapter | undefined, bus?: EventBus | undefined): CourierModule
11
+ .name: "@fonderie/courier"
12
+ .deps: string[]
13
+ .dispatcher: Dispatcher
14
+ .install(app: IFonderieApp): void
15
+
16
+ function handleSendGridDelivery(req: Request, store: IStoreAdapter, webhookSecret?: string | undefined): Promise<Response>
17
+
18
+ function handleMailgunDelivery(req: Request, store: IStoreAdapter, signingKey?: string | undefined): Promise<Response>
19
+
20
+ function handleMailtrapDelivery(req: Request, store: IStoreAdapter): Promise<Response>
21
+
22
+ new Dispatcher(config: ICourierConfig, resolver: ITemplateResolver, store?: IStoreAdapter | undefined): Dispatcher
23
+ .registerChannel(channel: ICourierChannel): Dispatcher
24
+ .dispatch(message: ICourierMessage): Promise<void>
25
+
26
+ new SmsChannel(config: ISmsChannelConfig): SmsChannel
27
+ .name: "sms"
28
+ .send(message: ICourierMessage, template: IRenderedTemplate): Promise<void>
29
+
30
+ new PushChannel(config: IPushChannelConfig): PushChannel
31
+ .name: "push"
32
+ .send(message: ICourierMessage, template: IRenderedTemplate): Promise<void>
33
+
34
+ new EmailChannel(config: IEmailChannelConfig): EmailChannel
35
+ .name: "email"
36
+ .send(message: ICourierMessage, template: IRenderedTemplate): Promise<void>
37
+
38
+ new DBTemplateResolver(store: IStoreAdapter): DBTemplateResolver
39
+ .resolve(type: string, data: Record<string, unknown>, locale?: string | undefined): Promise<IRenderedTemplate>
40
+
41
+ new FSTemplateResolver(directory: string): FSTemplateResolver
42
+ .resolve(type: string, data: Record<string, unknown>, locale?: string | undefined): Promise<IRenderedTemplate>
43
+
44
+ interface IMessageLog {
45
+ id: string;
46
+ messageType: string;
47
+ channel: string;
48
+ recipient: string;
49
+ locale: string | null;
50
+ status: MessageLogStatus;
51
+ error: string | null;
52
+ attempts: number;
53
+ provider: string | null;
54
+ providerMessageId: string | null;
55
+ openedAt: string | null;
56
+ clickedAt: string | null;
57
+ bouncedAt: string | null;
58
+ bounceReason: string | null;
59
+ createdAt: string;
60
+ sentAt: string | null;
61
+ }
62
+
63
+ type MessageLogStatus = 'pending' | 'sent' | 'failed' | 'delivered' | 'opened' | 'clicked' | 'bounced' | 'spam';
64
+
65
+ interface ICourierMessage {
66
+ type: string;
67
+ locale?: string;
68
+ recipient: {
69
+ email: string | null;
70
+ phone: string | null;
71
+ deviceToken: string | null;
72
+ };
73
+ data: Record<string, unknown>;
74
+ }
75
+
76
+ interface ICourierChannel {
77
+ name: string;
78
+ send(message: ICourierMessage, template: IRenderedTemplate): Promise<void>;
79
+ }
80
+
81
+ interface IRenderedTemplate {
82
+ subject?: string;
83
+ html?: string;
84
+ text: string;
85
+ }
86
+
87
+ interface ITemplateResolver {
88
+ resolve(type: string, data: Record<string, unknown>, locale?: string): Promise<IRenderedTemplate>;
89
+ }
90
+
91
+ const Channel: { readonly EMAIL: "email"; readonly SMS: "sms"; readonly PUSH: "push"; }
92
+
93
+ interface ICourierConfig {
94
+ channels: Record<string, Array<'email' | 'sms' | 'push'>>;
95
+ sms?: ISmsChannelConfig;
96
+ push?: IPushChannelConfig;
97
+ email?: IEmailChannelConfig;
98
+ templates?: {
99
+ source: 'db' | 'fs';
100
+ directory?: string;
101
+ };
102
+ delivery?: {
103
+ signingKeys?: {
104
+ sendgrid?: string;
105
+ mailgun?: string;
106
+ };
107
+ };
108
+ }
109
+
110
+ interface IEmailChannelConfig {
111
+ provider: 'resend' | 'ses' | 'smtp';
112
+ from: string;
113
+ apiKey?: string;
114
+ smtp?: {
115
+ host: string;
116
+ port: number;
117
+ secure: boolean;
118
+ user: string;
119
+ pass: string;
120
+ };
121
+ }
122
+
123
+ interface ISmsChannelConfig {
124
+ provider: 'twilio' | 'vonage';
125
+ from: string;
126
+ accountSid?: string;
127
+ authToken?: string;
128
+ apiKey?: string;
129
+ apiSecret?: string;
130
+ }
131
+
132
+ interface IPushChannelConfig {
133
+ provider: 'fcm';
134
+ serviceAccount: Record<string, unknown>;
135
+ }
136
+ ```
@@ -0,0 +1,3 @@
1
+ declare const getMigrationsPath: () => string;
2
+
3
+ export { getMigrationsPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/courier",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Transactional messaging for SaaS — multi-channel delivery (email, SMS, push) with FS or DB templates, per-message-type channel routing, and a persistent message log.",
5
5
  "keywords": [
6
6
  "fonderie-js",
@@ -39,7 +39,7 @@
39
39
  "module": "./dist/index.js",
40
40
  "types": "./dist/index.d.ts",
41
41
  "scripts": {
42
- "build": "tsup && node -e \"require('node:fs').cpSync('src/migrations/sql', 'dist/migrations/sql', {recursive:true})\"",
42
+ "build": "tsup && tsup --config tsup.migrations.ts",
43
43
  "dev": "tsup --watch",
44
44
  "typecheck": "tsc --noEmit",
45
45
  "test": "tsx --test src/__tests__/*.test.ts",
@@ -51,9 +51,9 @@
51
51
  "nodemailer": "^8.0.7"
52
52
  },
53
53
  "peerDependencies": {
54
- "@fonderie/core": "^0.1.0",
55
- "@fonderie/store": "^0.1.0",
56
- "@fonderie/events": "^1.0.0"
54
+ "@fonderie/core": "^0.1.1",
55
+ "@fonderie/store": "^0.1.1",
56
+ "@fonderie/events": "^1.0.1"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "@fonderie/store": {
@@ -78,16 +78,17 @@
78
78
  },
79
79
  "files": [
80
80
  "dist",
81
+ "brain",
81
82
  "LICENSE",
82
83
  "README.md"
83
84
  ],
84
85
  "repository": {
85
86
  "type": "git",
86
- "url": "git+https://github.com/fonderie-js/sdk.git",
87
+ "url": "git+https://github.com/fonderiejs/sdk.git",
87
88
  "directory": "packages/courier"
88
89
  },
89
- "homepage": "https://github.com/fonderie-js/sdk/tree/main/packages/courier#readme",
90
+ "homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/courier#readme",
90
91
  "bugs": {
91
- "url": "https://github.com/fonderie-js/sdk/issues"
92
+ "url": "https://github.com/fonderiejs/sdk/issues"
92
93
  }
93
94
  }
@@ -1,34 +0,0 @@
1
- -- fonderie_courier_templates: per-type, per-locale message templates
2
- CREATE TABLE IF NOT EXISTS fonderie_courier_templates (
3
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
- type TEXT NOT NULL,
5
- locale TEXT, -- NULL = default / catch-all locale
6
- subject TEXT,
7
- html TEXT,
8
- text TEXT NOT NULL,
9
- active BOOLEAN NOT NULL DEFAULT true,
10
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
11
- updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
- UNIQUE (type, locale)
13
- );
14
-
15
- CREATE INDEX IF NOT EXISTS idx_fct_type
16
- ON fonderie_courier_templates (type) WHERE active = true;
17
-
18
- -- fonderie_message_log: audit trail of every dispatch attempt
19
- CREATE TABLE IF NOT EXISTS fonderie_message_log (
20
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
21
- message_type TEXT NOT NULL,
22
- channel TEXT NOT NULL,
23
- recipient TEXT NOT NULL,
24
- locale TEXT,
25
- status TEXT NOT NULL DEFAULT 'pending',
26
- error TEXT,
27
- attempts INT NOT NULL DEFAULT 0,
28
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
29
- sent_at TIMESTAMPTZ
30
- );
31
-
32
- CREATE INDEX IF NOT EXISTS idx_fml_type ON fonderie_message_log (message_type);
33
- CREATE INDEX IF NOT EXISTS idx_fml_status ON fonderie_message_log (status);
34
- CREATE INDEX IF NOT EXISTS idx_fml_created ON fonderie_message_log (created_at DESC);
@@ -1,49 +0,0 @@
1
- -- Seed default email templates
2
- -- Uses UPDATE + conditional INSERT to safely handle NULL locale (ON CONFLICT
3
- -- does not fire for NULL columns in standard Postgres unique indexes).
4
-
5
- -- email-verification — variables: pin, firstName
6
- DO $$
7
- BEGIN
8
- IF EXISTS (
9
- SELECT 1 FROM fonderie_courier_templates
10
- WHERE type = 'email-verification' AND locale IS NULL
11
- ) THEN
12
- UPDATE fonderie_courier_templates
13
- SET
14
- subject = 'Your verification code',
15
- html = '<p>Hi {{firstName}},</p>
16
- <p>Your email verification code is:</p>
17
- <h2 style="letter-spacing:0.2em;">{{pin}}</h2>
18
- <p>Enter this code to complete your registration. It expires in 24 hours.</p>
19
- <p>If you did not create an account, you can safely ignore this email.</p>',
20
- text = 'Hi {{firstName}},
21
-
22
- Your email verification code is: {{pin}}
23
-
24
- Enter this code to complete your registration. It expires in 24 hours.
25
-
26
- If you did not create an account, you can safely ignore this email.',
27
- updated_at = now()
28
- WHERE type = 'email-verification' AND locale IS NULL;
29
- ELSE
30
- INSERT INTO fonderie_courier_templates (type, locale, subject, html, text)
31
- VALUES (
32
- 'email-verification',
33
- NULL,
34
- 'Your verification code',
35
- '<p>Hi {{firstName}},</p>
36
- <p>Your email verification code is:</p>
37
- <h2 style="letter-spacing:0.2em;">{{pin}}</h2>
38
- <p>Enter this code to complete your registration. It expires in 24 hours.</p>
39
- <p>If you did not create an account, you can safely ignore this email.</p>',
40
- 'Hi {{firstName}},
41
-
42
- Your email verification code is: {{pin}}
43
-
44
- Enter this code to complete your registration. It expires in 24 hours.
45
-
46
- If you did not create an account, you can safely ignore this email.'
47
- );
48
- END IF;
49
- END $$;
@@ -1,12 +0,0 @@
1
- -- Extend fonderie_message_log for delivery event correlation
2
- ALTER TABLE fonderie_message_log
3
- ADD COLUMN IF NOT EXISTS provider TEXT,
4
- ADD COLUMN IF NOT EXISTS provider_message_id TEXT,
5
- ADD COLUMN IF NOT EXISTS opened_at TIMESTAMPTZ,
6
- ADD COLUMN IF NOT EXISTS clicked_at TIMESTAMPTZ,
7
- ADD COLUMN IF NOT EXISTS bounced_at TIMESTAMPTZ,
8
- ADD COLUMN IF NOT EXISTS bounce_reason TEXT;
9
-
10
- CREATE INDEX IF NOT EXISTS idx_fml_provider_msg_id
11
- ON fonderie_message_log (provider_message_id)
12
- WHERE provider_message_id IS NOT NULL;