@kernhq/module-mail 0.1.0

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 (83) hide show
  1. package/LICENSE +662 -0
  2. package/dist/contract.d.ts +285 -0
  3. package/dist/contract.d.ts.map +1 -0
  4. package/dist/contract.js +115 -0
  5. package/dist/contract.js.map +1 -0
  6. package/dist/server/index.d.ts +29 -0
  7. package/dist/server/index.d.ts.map +1 -0
  8. package/dist/server/index.js +189 -0
  9. package/dist/server/index.js.map +1 -0
  10. package/dist/server/providers/index.d.ts +13 -0
  11. package/dist/server/providers/index.d.ts.map +1 -0
  12. package/dist/server/providers/index.js +36 -0
  13. package/dist/server/providers/index.js.map +1 -0
  14. package/dist/server/providers/mailgun.d.ts +9 -0
  15. package/dist/server/providers/mailgun.d.ts.map +1 -0
  16. package/dist/server/providers/mailgun.js +42 -0
  17. package/dist/server/providers/mailgun.js.map +1 -0
  18. package/dist/server/providers/postmark.d.ts +7 -0
  19. package/dist/server/providers/postmark.d.ts.map +1 -0
  20. package/dist/server/providers/postmark.js +38 -0
  21. package/dist/server/providers/postmark.js.map +1 -0
  22. package/dist/server/providers/resend.d.ts +7 -0
  23. package/dist/server/providers/resend.d.ts.map +1 -0
  24. package/dist/server/providers/resend.js +34 -0
  25. package/dist/server/providers/resend.js.map +1 -0
  26. package/dist/server/providers/ses.d.ts +9 -0
  27. package/dist/server/providers/ses.d.ts.map +1 -0
  28. package/dist/server/providers/ses.js +59 -0
  29. package/dist/server/providers/ses.js.map +1 -0
  30. package/dist/server/providers/smtp.d.ts +13 -0
  31. package/dist/server/providers/smtp.d.ts.map +1 -0
  32. package/dist/server/providers/smtp.js +36 -0
  33. package/dist/server/providers/smtp.js.map +1 -0
  34. package/dist/server/providers/types.d.ts +38 -0
  35. package/dist/server/providers/types.d.ts.map +1 -0
  36. package/dist/server/providers/types.js +2 -0
  37. package/dist/server/providers/types.js.map +1 -0
  38. package/dist/server/schema.d.ts +469 -0
  39. package/dist/server/schema.d.ts.map +1 -0
  40. package/dist/server/schema.js +40 -0
  41. package/dist/server/schema.js.map +1 -0
  42. package/dist/server/send.d.ts +32 -0
  43. package/dist/server/send.d.ts.map +1 -0
  44. package/dist/server/send.js +119 -0
  45. package/dist/server/send.js.map +1 -0
  46. package/dist/server/settings.d.ts +9 -0
  47. package/dist/server/settings.d.ts.map +1 -0
  48. package/dist/server/settings.js +35 -0
  49. package/dist/server/settings.js.map +1 -0
  50. package/dist/server/suppressions.d.ts +17 -0
  51. package/dist/server/suppressions.d.ts.map +1 -0
  52. package/dist/server/suppressions.js +46 -0
  53. package/dist/server/suppressions.js.map +1 -0
  54. package/dist/server/templates.d.ts +19 -0
  55. package/dist/server/templates.d.ts.map +1 -0
  56. package/dist/server/templates.js +55 -0
  57. package/dist/server/templates.js.map +1 -0
  58. package/migrations/0000_init.sql +39 -0
  59. package/migrations/0001_notes.sql +6 -0
  60. package/migrations/meta/0000_snapshot.json +288 -0
  61. package/migrations/meta/_journal.json +20 -0
  62. package/package.json +80 -0
  63. package/src/client/MailSettings.svelte +214 -0
  64. package/src/client/index.ts +29 -0
  65. package/templates/_layout.mjml +34 -0
  66. package/templates/invitation.mjml +16 -0
  67. package/templates/invitation.subject.txt +1 -0
  68. package/templates/invitation.txt +8 -0
  69. package/templates/magic-link.mjml +16 -0
  70. package/templates/magic-link.subject.txt +1 -0
  71. package/templates/magic-link.txt +6 -0
  72. package/templates/notification-digest.mjml +24 -0
  73. package/templates/notification-digest.subject.txt +1 -0
  74. package/templates/notification-digest.txt +8 -0
  75. package/templates/reset-password.mjml +16 -0
  76. package/templates/reset-password.subject.txt +1 -0
  77. package/templates/reset-password.txt +7 -0
  78. package/templates/test.mjml +12 -0
  79. package/templates/test.subject.txt +1 -0
  80. package/templates/test.txt +5 -0
  81. package/templates/verify-email.mjml +17 -0
  82. package/templates/verify-email.subject.txt +1 -0
  83. package/templates/verify-email.txt +7 -0
@@ -0,0 +1,469 @@
1
+ export declare const schema: import("drizzle-orm/pg-core").PgSchema<string>;
2
+ /** One row per outbound message. `workspace_id` is null for instance-level mail. */
3
+ export declare const deliveries: import("drizzle-orm/pg-core").PgTableWithColumns<{
4
+ name: "deliveries";
5
+ schema: string;
6
+ columns: {
7
+ id: import("drizzle-orm/pg-core").PgColumn<{
8
+ name: "id";
9
+ tableName: "deliveries";
10
+ dataType: "string";
11
+ columnType: "PgUUID";
12
+ data: string;
13
+ driverParam: string;
14
+ notNull: true;
15
+ hasDefault: true;
16
+ isPrimaryKey: true;
17
+ isAutoincrement: false;
18
+ hasRuntimeDefault: false;
19
+ enumValues: undefined;
20
+ baseColumn: never;
21
+ identity: undefined;
22
+ generated: undefined;
23
+ }, {}, {}>;
24
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
25
+ name: "workspace_id";
26
+ tableName: "deliveries";
27
+ dataType: "string";
28
+ columnType: "PgUUID";
29
+ data: string;
30
+ driverParam: string;
31
+ notNull: false;
32
+ hasDefault: false;
33
+ isPrimaryKey: false;
34
+ isAutoincrement: false;
35
+ hasRuntimeDefault: false;
36
+ enumValues: undefined;
37
+ baseColumn: never;
38
+ identity: undefined;
39
+ generated: undefined;
40
+ }, {}, {}>;
41
+ to: import("drizzle-orm/pg-core").PgColumn<{
42
+ name: "to";
43
+ tableName: "deliveries";
44
+ dataType: "array";
45
+ columnType: "PgArray";
46
+ data: string[];
47
+ driverParam: string | string[];
48
+ notNull: true;
49
+ hasDefault: false;
50
+ isPrimaryKey: false;
51
+ isAutoincrement: false;
52
+ hasRuntimeDefault: false;
53
+ enumValues: [string, ...string[]];
54
+ baseColumn: import("drizzle-orm").Column<{
55
+ name: "to";
56
+ tableName: "deliveries";
57
+ dataType: "string";
58
+ columnType: "PgText";
59
+ data: string;
60
+ driverParam: string;
61
+ notNull: false;
62
+ hasDefault: false;
63
+ isPrimaryKey: false;
64
+ isAutoincrement: false;
65
+ hasRuntimeDefault: false;
66
+ enumValues: [string, ...string[]];
67
+ baseColumn: never;
68
+ identity: undefined;
69
+ generated: undefined;
70
+ }, {}, {}>;
71
+ identity: undefined;
72
+ generated: undefined;
73
+ }, {}, {
74
+ baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
75
+ name: "to";
76
+ dataType: "string";
77
+ columnType: "PgText";
78
+ data: string;
79
+ enumValues: [string, ...string[]];
80
+ driverParam: string;
81
+ }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
82
+ size: undefined;
83
+ }>;
84
+ subject: import("drizzle-orm/pg-core").PgColumn<{
85
+ name: "subject";
86
+ tableName: "deliveries";
87
+ dataType: "string";
88
+ columnType: "PgText";
89
+ data: string;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: false;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: [string, ...string[]];
97
+ baseColumn: never;
98
+ identity: undefined;
99
+ generated: undefined;
100
+ }, {}, {}>;
101
+ provider: import("drizzle-orm/pg-core").PgColumn<{
102
+ name: "provider";
103
+ tableName: "deliveries";
104
+ dataType: "string";
105
+ columnType: "PgText";
106
+ data: string;
107
+ driverParam: string;
108
+ notNull: true;
109
+ hasDefault: false;
110
+ isPrimaryKey: false;
111
+ isAutoincrement: false;
112
+ hasRuntimeDefault: false;
113
+ enumValues: [string, ...string[]];
114
+ baseColumn: never;
115
+ identity: undefined;
116
+ generated: undefined;
117
+ }, {}, {}>;
118
+ template: import("drizzle-orm/pg-core").PgColumn<{
119
+ name: "template";
120
+ tableName: "deliveries";
121
+ dataType: "string";
122
+ columnType: "PgText";
123
+ data: string;
124
+ driverParam: string;
125
+ notNull: false;
126
+ hasDefault: false;
127
+ isPrimaryKey: false;
128
+ isAutoincrement: false;
129
+ hasRuntimeDefault: false;
130
+ enumValues: [string, ...string[]];
131
+ baseColumn: never;
132
+ identity: undefined;
133
+ generated: undefined;
134
+ }, {}, {}>;
135
+ status: import("drizzle-orm/pg-core").PgColumn<{
136
+ name: "status";
137
+ tableName: "deliveries";
138
+ dataType: "string";
139
+ columnType: "PgText";
140
+ data: string;
141
+ driverParam: string;
142
+ notNull: true;
143
+ hasDefault: true;
144
+ isPrimaryKey: false;
145
+ isAutoincrement: false;
146
+ hasRuntimeDefault: false;
147
+ enumValues: [string, ...string[]];
148
+ baseColumn: never;
149
+ identity: undefined;
150
+ generated: undefined;
151
+ }, {}, {}>;
152
+ providerMessageId: import("drizzle-orm/pg-core").PgColumn<{
153
+ name: "provider_message_id";
154
+ tableName: "deliveries";
155
+ dataType: "string";
156
+ columnType: "PgText";
157
+ data: string;
158
+ driverParam: string;
159
+ notNull: false;
160
+ hasDefault: false;
161
+ isPrimaryKey: false;
162
+ isAutoincrement: false;
163
+ hasRuntimeDefault: false;
164
+ enumValues: [string, ...string[]];
165
+ baseColumn: never;
166
+ identity: undefined;
167
+ generated: undefined;
168
+ }, {}, {}>;
169
+ error: import("drizzle-orm/pg-core").PgColumn<{
170
+ name: "error";
171
+ tableName: "deliveries";
172
+ dataType: "string";
173
+ columnType: "PgText";
174
+ data: string;
175
+ driverParam: string;
176
+ notNull: false;
177
+ hasDefault: false;
178
+ isPrimaryKey: false;
179
+ isAutoincrement: false;
180
+ hasRuntimeDefault: false;
181
+ enumValues: [string, ...string[]];
182
+ baseColumn: never;
183
+ identity: undefined;
184
+ generated: undefined;
185
+ }, {}, {}>;
186
+ tags: import("drizzle-orm/pg-core").PgColumn<{
187
+ name: "tags";
188
+ tableName: "deliveries";
189
+ dataType: "array";
190
+ columnType: "PgArray";
191
+ data: string[];
192
+ driverParam: string | string[];
193
+ notNull: true;
194
+ hasDefault: true;
195
+ isPrimaryKey: false;
196
+ isAutoincrement: false;
197
+ hasRuntimeDefault: false;
198
+ enumValues: [string, ...string[]];
199
+ baseColumn: import("drizzle-orm").Column<{
200
+ name: "tags";
201
+ tableName: "deliveries";
202
+ dataType: "string";
203
+ columnType: "PgText";
204
+ data: string;
205
+ driverParam: string;
206
+ notNull: false;
207
+ hasDefault: false;
208
+ isPrimaryKey: false;
209
+ isAutoincrement: false;
210
+ hasRuntimeDefault: false;
211
+ enumValues: [string, ...string[]];
212
+ baseColumn: never;
213
+ identity: undefined;
214
+ generated: undefined;
215
+ }, {}, {}>;
216
+ identity: undefined;
217
+ generated: undefined;
218
+ }, {}, {
219
+ baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
220
+ name: "tags";
221
+ dataType: "string";
222
+ columnType: "PgText";
223
+ data: string;
224
+ enumValues: [string, ...string[]];
225
+ driverParam: string;
226
+ }, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
227
+ size: undefined;
228
+ }>;
229
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
230
+ name: "created_at";
231
+ tableName: "deliveries";
232
+ dataType: "date";
233
+ columnType: "PgTimestamp";
234
+ data: Date;
235
+ driverParam: string;
236
+ notNull: true;
237
+ hasDefault: true;
238
+ isPrimaryKey: false;
239
+ isAutoincrement: false;
240
+ hasRuntimeDefault: false;
241
+ enumValues: undefined;
242
+ baseColumn: never;
243
+ identity: undefined;
244
+ generated: undefined;
245
+ }, {}, {}>;
246
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
247
+ name: "updated_at";
248
+ tableName: "deliveries";
249
+ dataType: "date";
250
+ columnType: "PgTimestamp";
251
+ data: Date;
252
+ driverParam: string;
253
+ notNull: true;
254
+ hasDefault: true;
255
+ isPrimaryKey: false;
256
+ isAutoincrement: false;
257
+ hasRuntimeDefault: false;
258
+ enumValues: undefined;
259
+ baseColumn: never;
260
+ identity: undefined;
261
+ generated: undefined;
262
+ }, {}, {}>;
263
+ };
264
+ dialect: "pg";
265
+ }>;
266
+ /** Addresses we must not send to. `workspace_id` null = instance-wide suppression. */
267
+ export declare const suppressions: import("drizzle-orm/pg-core").PgTableWithColumns<{
268
+ name: "suppressions";
269
+ schema: string;
270
+ columns: {
271
+ id: import("drizzle-orm/pg-core").PgColumn<{
272
+ name: "id";
273
+ tableName: "suppressions";
274
+ dataType: "string";
275
+ columnType: "PgUUID";
276
+ data: string;
277
+ driverParam: string;
278
+ notNull: true;
279
+ hasDefault: true;
280
+ isPrimaryKey: true;
281
+ isAutoincrement: false;
282
+ hasRuntimeDefault: false;
283
+ enumValues: undefined;
284
+ baseColumn: never;
285
+ identity: undefined;
286
+ generated: undefined;
287
+ }, {}, {}>;
288
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
289
+ name: "workspace_id";
290
+ tableName: "suppressions";
291
+ dataType: "string";
292
+ columnType: "PgUUID";
293
+ data: string;
294
+ driverParam: string;
295
+ notNull: false;
296
+ hasDefault: false;
297
+ isPrimaryKey: false;
298
+ isAutoincrement: false;
299
+ hasRuntimeDefault: false;
300
+ enumValues: undefined;
301
+ baseColumn: never;
302
+ identity: undefined;
303
+ generated: undefined;
304
+ }, {}, {}>;
305
+ email: import("drizzle-orm/pg-core").PgColumn<{
306
+ name: "email";
307
+ tableName: "suppressions";
308
+ dataType: "string";
309
+ columnType: "PgText";
310
+ data: string;
311
+ driverParam: string;
312
+ notNull: true;
313
+ hasDefault: false;
314
+ isPrimaryKey: false;
315
+ isAutoincrement: false;
316
+ hasRuntimeDefault: false;
317
+ enumValues: [string, ...string[]];
318
+ baseColumn: never;
319
+ identity: undefined;
320
+ generated: undefined;
321
+ }, {}, {}>;
322
+ reason: import("drizzle-orm/pg-core").PgColumn<{
323
+ name: "reason";
324
+ tableName: "suppressions";
325
+ dataType: "string";
326
+ columnType: "PgText";
327
+ data: string;
328
+ driverParam: string;
329
+ notNull: true;
330
+ hasDefault: false;
331
+ isPrimaryKey: false;
332
+ isAutoincrement: false;
333
+ hasRuntimeDefault: false;
334
+ enumValues: [string, ...string[]];
335
+ baseColumn: never;
336
+ identity: undefined;
337
+ generated: undefined;
338
+ }, {}, {}>;
339
+ source: import("drizzle-orm/pg-core").PgColumn<{
340
+ name: "source";
341
+ tableName: "suppressions";
342
+ dataType: "string";
343
+ columnType: "PgText";
344
+ data: string;
345
+ driverParam: string;
346
+ notNull: false;
347
+ hasDefault: false;
348
+ isPrimaryKey: false;
349
+ isAutoincrement: false;
350
+ hasRuntimeDefault: false;
351
+ enumValues: [string, ...string[]];
352
+ baseColumn: never;
353
+ identity: undefined;
354
+ generated: undefined;
355
+ }, {}, {}>;
356
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
357
+ name: "created_at";
358
+ tableName: "suppressions";
359
+ dataType: "date";
360
+ columnType: "PgTimestamp";
361
+ data: Date;
362
+ driverParam: string;
363
+ notNull: true;
364
+ hasDefault: true;
365
+ isPrimaryKey: false;
366
+ isAutoincrement: false;
367
+ hasRuntimeDefault: false;
368
+ enumValues: undefined;
369
+ baseColumn: never;
370
+ identity: undefined;
371
+ generated: undefined;
372
+ }, {}, {}>;
373
+ };
374
+ dialect: "pg";
375
+ }>;
376
+ /** Placeholder for intake addresses (`intake+token@…`) routed to tracker/recruit/CRM. */
377
+ export declare const inboundRoutes: import("drizzle-orm/pg-core").PgTableWithColumns<{
378
+ name: "inbound_routes";
379
+ schema: string;
380
+ columns: {
381
+ id: import("drizzle-orm/pg-core").PgColumn<{
382
+ name: "id";
383
+ tableName: "inbound_routes";
384
+ dataType: "string";
385
+ columnType: "PgUUID";
386
+ data: string;
387
+ driverParam: string;
388
+ notNull: true;
389
+ hasDefault: true;
390
+ isPrimaryKey: true;
391
+ isAutoincrement: false;
392
+ hasRuntimeDefault: false;
393
+ enumValues: undefined;
394
+ baseColumn: never;
395
+ identity: undefined;
396
+ generated: undefined;
397
+ }, {}, {}>;
398
+ workspaceId: import("drizzle-orm/pg-core").PgColumn<{
399
+ name: "workspace_id";
400
+ tableName: "inbound_routes";
401
+ dataType: "string";
402
+ columnType: "PgUUID";
403
+ data: string;
404
+ driverParam: string;
405
+ notNull: true;
406
+ hasDefault: false;
407
+ isPrimaryKey: false;
408
+ isAutoincrement: false;
409
+ hasRuntimeDefault: false;
410
+ enumValues: undefined;
411
+ baseColumn: never;
412
+ identity: undefined;
413
+ generated: undefined;
414
+ }, {}, {}>;
415
+ token: import("drizzle-orm/pg-core").PgColumn<{
416
+ name: "token";
417
+ tableName: "inbound_routes";
418
+ dataType: "string";
419
+ columnType: "PgText";
420
+ data: string;
421
+ driverParam: string;
422
+ notNull: true;
423
+ hasDefault: false;
424
+ isPrimaryKey: false;
425
+ isAutoincrement: false;
426
+ hasRuntimeDefault: false;
427
+ enumValues: [string, ...string[]];
428
+ baseColumn: never;
429
+ identity: undefined;
430
+ generated: undefined;
431
+ }, {}, {}>;
432
+ target: import("drizzle-orm/pg-core").PgColumn<{
433
+ name: "target";
434
+ tableName: "inbound_routes";
435
+ dataType: "string";
436
+ columnType: "PgText";
437
+ data: string;
438
+ driverParam: string;
439
+ notNull: true;
440
+ hasDefault: false;
441
+ isPrimaryKey: false;
442
+ isAutoincrement: false;
443
+ hasRuntimeDefault: false;
444
+ enumValues: [string, ...string[]];
445
+ baseColumn: never;
446
+ identity: undefined;
447
+ generated: undefined;
448
+ }, {}, {}>;
449
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
450
+ name: "created_at";
451
+ tableName: "inbound_routes";
452
+ dataType: "date";
453
+ columnType: "PgTimestamp";
454
+ data: Date;
455
+ driverParam: string;
456
+ notNull: true;
457
+ hasDefault: true;
458
+ isPrimaryKey: false;
459
+ isAutoincrement: false;
460
+ hasRuntimeDefault: false;
461
+ enumValues: undefined;
462
+ baseColumn: never;
463
+ identity: undefined;
464
+ generated: undefined;
465
+ }, {}, {}>;
466
+ };
467
+ dialect: "pg";
468
+ }>;
469
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM,gDAAuB,CAAA;AAE1C,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBtB,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWxB,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWzB,CAAA"}
@@ -0,0 +1,40 @@
1
+ import { moduleSchema, sql } from '@kernhq/kernel';
2
+ import { index, text, timestamp, uuid } from 'drizzle-orm/pg-core';
3
+ export const schema = moduleSchema('mail');
4
+ /** One row per outbound message. `workspace_id` is null for instance-level mail. */
5
+ export const deliveries = schema.table('deliveries', {
6
+ id: uuid('id').primaryKey().default(sql `uuidv7()`),
7
+ workspaceId: uuid('workspace_id'),
8
+ to: text('to').array().notNull(),
9
+ subject: text('subject').notNull(),
10
+ provider: text('provider').notNull(),
11
+ template: text('template'),
12
+ status: text('status').notNull().default('queued'),
13
+ providerMessageId: text('provider_message_id'),
14
+ error: text('error'),
15
+ tags: text('tags').array().notNull().default(sql `'{}'::text[]`),
16
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
17
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
18
+ }, (t) => [
19
+ index('deliveries_ws_idx').on(t.workspaceId, t.createdAt),
20
+ index('deliveries_pmid_idx').on(t.providerMessageId),
21
+ ]);
22
+ /** Addresses we must not send to. `workspace_id` null = instance-wide suppression. */
23
+ export const suppressions = schema.table('suppressions', {
24
+ id: uuid('id').primaryKey().default(sql `uuidv7()`),
25
+ workspaceId: uuid('workspace_id'),
26
+ email: text('email').notNull(),
27
+ reason: text('reason').notNull(),
28
+ source: text('source'),
29
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
30
+ }, (t) => [index('suppressions_email_idx').on(t.email, t.workspaceId)]);
31
+ /** Placeholder for intake addresses (`intake+token@…`) routed to tracker/recruit/CRM. */
32
+ export const inboundRoutes = schema.table('inbound_routes', {
33
+ id: uuid('id').primaryKey().default(sql `uuidv7()`),
34
+ workspaceId: uuid('workspace_id').notNull(),
35
+ token: text('token').notNull().unique(),
36
+ /** object ref string, e.g. `tracker:project:<id>` */
37
+ target: text('target').notNull(),
38
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
39
+ }, (t) => [index('inbound_routes_ws_idx').on(t.workspaceId)]);
40
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/server/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { core, EventDef } from '@kernhq/contracts';
2
+ import type { Kernel } from '@kernhq/kernel';
3
+ import { type MailDeliveryStatus, type SendMailInput } from '../contract.js';
4
+ import type { MailProvider, OutgoingMessage } from './providers/types.js';
5
+ export declare const instanceName: () => string;
6
+ /** Workspace integration config (`core.settings.getIntegration` kind `mail`) or null → platform SMTP. */
7
+ export declare function resolveConfig(kernel: Kernel, workspaceId: string | undefined): Promise<core.MailProviderConfig | null>;
8
+ export declare function resolveProvider(kernel: Kernel, workspaceId: string | undefined): Promise<MailProvider>;
9
+ /** Render template (when set), fetch attachments, apply suppressions → message ready to send. */
10
+ export declare function buildMessage(kernel: Kernel, input: SendMailInput, from: string): Promise<OutgoingMessage>;
11
+ export declare function updateDelivery(kernel: Kernel, deliveryId: string, patch: {
12
+ status?: MailDeliveryStatus;
13
+ providerMessageId?: string | null;
14
+ error?: string | null;
15
+ }): Promise<void>;
16
+ export declare function emitDeliveryEvent(kernel: Kernel, def: EventDef, row: {
17
+ id: string;
18
+ workspaceId: string | null;
19
+ to: string[];
20
+ }, extra?: {
21
+ providerMessageId?: string | null;
22
+ error?: string | null;
23
+ }): Promise<void>;
24
+ /**
25
+ * `mail.send` job: resolve provider, drop suppressed recipients, send, record the outcome.
26
+ * Throws on provider errors so pg-boss retries with backoff; the delivery row tracks the last error.
27
+ */
28
+ export declare function processSend(kernel: Kernel, job: {
29
+ deliveryId: string;
30
+ message: SendMailInput;
31
+ }): Promise<void>;
32
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/server/send.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAG5C,OAAO,EAAE,KAAK,kBAAkB,EAAc,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAExF,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAKzE,eAAO,MAAM,YAAY,cAAiD,CAAA;AAE1E,yGAAyG;AACzG,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAGzC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED,iGAAiG;AACjG,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CA4C1B;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE;IAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC/F,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,EAAE,CAAA;CAAE,EAC7D,KAAK,GAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GACvE,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CA4Cf"}
@@ -0,0 +1,119 @@
1
+ import { KernError } from '@kernhq/kernel';
2
+ import { eq } from 'drizzle-orm';
3
+ import { mailEvents } from '../contract.js';
4
+ import { providerFor } from './providers/index.js';
5
+ import { deliveries } from './schema.js';
6
+ import { filterSuppressed, loadSuppressed } from './suppressions.js';
7
+ import { renderTemplate } from './templates.js';
8
+ export const instanceName = () => process.env.KERN_INSTANCE_NAME ?? 'Kern';
9
+ /** Workspace integration config (`core.settings.getIntegration` kind `mail`) or null → platform SMTP. */
10
+ export async function resolveConfig(kernel, workspaceId) {
11
+ if (!workspaceId)
12
+ return null;
13
+ return kernel.settings.integration(workspaceId, 'mail');
14
+ }
15
+ export async function resolveProvider(kernel, workspaceId) {
16
+ return providerFor(await resolveConfig(kernel, workspaceId));
17
+ }
18
+ /** Render template (when set), fetch attachments, apply suppressions → message ready to send. */
19
+ export async function buildMessage(kernel, input, from) {
20
+ let { subject, text, html } = { subject: input.subject, text: input.text, html: input.html };
21
+ if (input.template) {
22
+ const rendered = await renderTemplate(input.template.name, input.template.data, {
23
+ instanceName: instanceName(),
24
+ });
25
+ html = rendered.html;
26
+ text = text ?? rendered.text;
27
+ if (!input.subject.trim())
28
+ subject = rendered.subject;
29
+ }
30
+ if (!text && !html)
31
+ throw new KernError('BAD_REQUEST', 'One of text, html or template is required');
32
+ const attachments = await Promise.all((input.attachments ?? []).map(async (a) => {
33
+ if (a.base64)
34
+ return { filename: a.filename, contentType: a.contentType, content: Buffer.from(a.base64, 'base64') };
35
+ const file = await kernel.call('core.files.get', {
36
+ workspaceId: input.workspaceId,
37
+ id: a.fileId,
38
+ });
39
+ const obj = await kernel.storage.get(file.key);
40
+ const chunks = [];
41
+ for await (const chunk of obj.body)
42
+ chunks.push(Buffer.from(chunk));
43
+ return {
44
+ filename: a.filename,
45
+ contentType: a.contentType ?? file.mimeType,
46
+ content: Buffer.concat(chunks),
47
+ };
48
+ }));
49
+ return {
50
+ from,
51
+ to: input.to,
52
+ cc: input.cc,
53
+ bcc: input.bcc,
54
+ replyTo: input.replyTo,
55
+ subject,
56
+ text,
57
+ html,
58
+ headers: input.headers,
59
+ attachments: attachments.length ? attachments : undefined,
60
+ tags: input.tags,
61
+ };
62
+ }
63
+ export async function updateDelivery(kernel, deliveryId, patch) {
64
+ await kernel.database.withWorkspace(null, (tx) => tx
65
+ .update(deliveries)
66
+ .set({ ...patch, updatedAt: new Date() })
67
+ .where(eq(deliveries.id, deliveryId)));
68
+ }
69
+ export async function emitDeliveryEvent(kernel, def, row, extra = {}) {
70
+ await kernel.emit(def, {
71
+ deliveryId: row.id,
72
+ workspaceId: row.workspaceId,
73
+ to: row.to,
74
+ providerMessageId: extra.providerMessageId ?? null,
75
+ error: extra.error ?? null,
76
+ }, { workspaceId: row.workspaceId });
77
+ }
78
+ /**
79
+ * `mail.send` job: resolve provider, drop suppressed recipients, send, record the outcome.
80
+ * Throws on provider errors so pg-boss retries with backoff; the delivery row tracks the last error.
81
+ */
82
+ export async function processSend(kernel, job) {
83
+ const { deliveryId, message } = job;
84
+ const workspaceId = message.workspaceId ?? null;
85
+ const suppressed = await loadSuppressed(kernel, workspaceId, [
86
+ ...message.to,
87
+ ...(message.cc ?? []),
88
+ ...(message.bcc ?? []),
89
+ ]);
90
+ const to = filterSuppressed(message.to, suppressed).deliverable;
91
+ if (to.length === 0) {
92
+ await updateDelivery(kernel, deliveryId, { status: 'failed', error: 'all recipients suppressed' });
93
+ await emitDeliveryEvent(kernel, mailEvents.deliveryFailed, { id: deliveryId, workspaceId, to: message.to }, { error: 'all recipients suppressed' });
94
+ return;
95
+ }
96
+ const provider = await resolveProvider(kernel, message.workspaceId);
97
+ try {
98
+ const outgoing = await buildMessage(kernel, { ...message, to }, provider.from);
99
+ outgoing.cc = message.cc ? filterSuppressed(message.cc, suppressed).deliverable : undefined;
100
+ outgoing.bcc = message.bcc ? filterSuppressed(message.bcc, suppressed).deliverable : undefined;
101
+ const result = await provider.send(outgoing);
102
+ await updateDelivery(kernel, deliveryId, {
103
+ status: 'sent',
104
+ providerMessageId: result.messageId,
105
+ error: null,
106
+ });
107
+ await emitDeliveryEvent(kernel, mailEvents.deliverySent, { id: deliveryId, workspaceId, to }, { providerMessageId: result.messageId });
108
+ }
109
+ catch (err) {
110
+ const error = err instanceof Error ? err.message : String(err);
111
+ await updateDelivery(kernel, deliveryId, { status: 'failed', error });
112
+ await emitDeliveryEvent(kernel, mailEvents.deliveryFailed, { id: deliveryId, workspaceId, to }, { error });
113
+ throw err;
114
+ }
115
+ finally {
116
+ provider.close?.();
117
+ }
118
+ }
119
+ //# sourceMappingURL=send.js.map