@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,285 @@
1
+ import { z } from 'zod';
2
+ export declare const MODULE_ID = "mail";
3
+ /** Value returned in place of a secret field when reading settings; sending it back keeps the stored value. */
4
+ export declare const SECRET_PLACEHOLDER = "__kern_secret__";
5
+ export declare const MailProviderKind: z.ZodEnum<{
6
+ platform: "platform";
7
+ smtp: "smtp";
8
+ mailgun: "mailgun";
9
+ ses: "ses";
10
+ postmark: "postmark";
11
+ resend: "resend";
12
+ }>;
13
+ export type MailProviderKind = z.infer<typeof MailProviderKind>;
14
+ export declare const MailAttachment: z.ZodObject<{
15
+ filename: z.ZodString;
16
+ contentType: z.ZodDefault<z.ZodString>;
17
+ fileId: z.ZodOptional<z.ZodUUID>;
18
+ base64: z.ZodOptional<z.ZodString>;
19
+ }, z.core.$strip>;
20
+ export type MailAttachment = z.infer<typeof MailAttachment>;
21
+ export declare const SendMailInput: z.ZodObject<{
22
+ workspaceId: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
23
+ to: z.ZodArray<z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>>;
24
+ cc: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>>>;
25
+ bcc: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>>>;
26
+ subject: z.ZodString;
27
+ text: z.ZodOptional<z.ZodString>;
28
+ html: z.ZodOptional<z.ZodString>;
29
+ template: z.ZodOptional<z.ZodObject<{
30
+ name: z.ZodString;
31
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
32
+ }, z.core.$strip>>;
33
+ replyTo: z.ZodOptional<z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>>;
34
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
35
+ attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
36
+ filename: z.ZodString;
37
+ contentType: z.ZodDefault<z.ZodString>;
38
+ fileId: z.ZodOptional<z.ZodUUID>;
39
+ base64: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strip>>>;
41
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ }, z.core.$strip>;
43
+ export type SendMailInput = z.infer<typeof SendMailInput>;
44
+ export declare const MailDeliveryStatus: z.ZodEnum<{
45
+ queued: "queued";
46
+ sent: "sent";
47
+ failed: "failed";
48
+ bounced: "bounced";
49
+ }>;
50
+ export type MailDeliveryStatus = z.infer<typeof MailDeliveryStatus>;
51
+ export declare const MailDelivery: z.ZodObject<{
52
+ id: z.ZodUUID;
53
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
54
+ to: z.ZodArray<z.ZodString>;
55
+ subject: z.ZodString;
56
+ provider: z.ZodEnum<{
57
+ platform: "platform";
58
+ smtp: "smtp";
59
+ mailgun: "mailgun";
60
+ ses: "ses";
61
+ postmark: "postmark";
62
+ resend: "resend";
63
+ }>;
64
+ template: z.ZodNullable<z.ZodString>;
65
+ status: z.ZodEnum<{
66
+ queued: "queued";
67
+ sent: "sent";
68
+ failed: "failed";
69
+ bounced: "bounced";
70
+ }>;
71
+ providerMessageId: z.ZodNullable<z.ZodString>;
72
+ error: z.ZodNullable<z.ZodString>;
73
+ tags: z.ZodArray<z.ZodString>;
74
+ createdAt: z.ZodISODateTime;
75
+ updatedAt: z.ZodISODateTime;
76
+ }, z.core.$strip>;
77
+ export type MailDelivery = z.infer<typeof MailDelivery>;
78
+ export declare const MailSuppression: z.ZodObject<{
79
+ id: z.ZodUUID;
80
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
81
+ email: z.ZodString;
82
+ reason: z.ZodEnum<{
83
+ bounce: "bounce";
84
+ complaint: "complaint";
85
+ manual: "manual";
86
+ }>;
87
+ source: z.ZodNullable<z.ZodString>;
88
+ createdAt: z.ZodISODateTime;
89
+ }, z.core.$strip>;
90
+ export type MailSuppression = z.infer<typeof MailSuppression>;
91
+ /**
92
+ * Workspace mail settings as exposed over the API. Secret fields are replaced with
93
+ * SECRET_PLACEHOLDER on read; sending the placeholder back keeps the stored secret.
94
+ * The stored shape is `core.MailProviderConfig` (contracts).
95
+ */
96
+ export declare const mailContract: {
97
+ settings: {
98
+ get: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
99
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
100
+ }, z.core.$strip>, z.ZodObject<{
101
+ config: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
102
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
103
+ BAD_REQUEST: {
104
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
105
+ };
106
+ UNAUTHORIZED: {};
107
+ FORBIDDEN: {
108
+ data: z.ZodOptional<z.ZodObject<{
109
+ permission: z.ZodOptional<z.ZodString>;
110
+ }, z.core.$strip>>;
111
+ };
112
+ NOT_FOUND: {};
113
+ CONFLICT: {
114
+ data: z.ZodOptional<z.ZodObject<{
115
+ reason: z.ZodOptional<z.ZodString>;
116
+ }, z.core.$strip>>;
117
+ };
118
+ RATE_LIMITED: {};
119
+ MODULE_DISABLED: {
120
+ data: z.ZodObject<{
121
+ module: z.ZodString;
122
+ }, z.core.$strip>;
123
+ };
124
+ }>, Record<never, never>>;
125
+ set: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
126
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
127
+ config: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
128
+ }, z.core.$strip>, z.ZodObject<{
129
+ ok: z.ZodBoolean;
130
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
131
+ BAD_REQUEST: {
132
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
133
+ };
134
+ UNAUTHORIZED: {};
135
+ FORBIDDEN: {
136
+ data: z.ZodOptional<z.ZodObject<{
137
+ permission: z.ZodOptional<z.ZodString>;
138
+ }, z.core.$strip>>;
139
+ };
140
+ NOT_FOUND: {};
141
+ CONFLICT: {
142
+ data: z.ZodOptional<z.ZodObject<{
143
+ reason: z.ZodOptional<z.ZodString>;
144
+ }, z.core.$strip>>;
145
+ };
146
+ RATE_LIMITED: {};
147
+ MODULE_DISABLED: {
148
+ data: z.ZodObject<{
149
+ module: z.ZodString;
150
+ }, z.core.$strip>;
151
+ };
152
+ }>, Record<never, never>>;
153
+ test: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
154
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
155
+ to: z.ZodPipe<z.ZodEmail, z.ZodTransform<string, string>>;
156
+ }, z.core.$strip>, z.ZodObject<{
157
+ ok: z.ZodBoolean;
158
+ error: z.ZodNullable<z.ZodString>;
159
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
160
+ BAD_REQUEST: {
161
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
162
+ };
163
+ UNAUTHORIZED: {};
164
+ FORBIDDEN: {
165
+ data: z.ZodOptional<z.ZodObject<{
166
+ permission: z.ZodOptional<z.ZodString>;
167
+ }, z.core.$strip>>;
168
+ };
169
+ NOT_FOUND: {};
170
+ CONFLICT: {
171
+ data: z.ZodOptional<z.ZodObject<{
172
+ reason: z.ZodOptional<z.ZodString>;
173
+ }, z.core.$strip>>;
174
+ };
175
+ RATE_LIMITED: {};
176
+ MODULE_DISABLED: {
177
+ data: z.ZodObject<{
178
+ module: z.ZodString;
179
+ }, z.core.$strip>;
180
+ };
181
+ }>, Record<never, never>>;
182
+ };
183
+ deliveries: {
184
+ list: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
185
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
186
+ cursor: z.ZodOptional<z.ZodString>;
187
+ limit: z.ZodDefault<z.ZodNumber>;
188
+ status: z.ZodOptional<z.ZodEnum<{
189
+ queued: "queued";
190
+ sent: "sent";
191
+ failed: "failed";
192
+ bounced: "bounced";
193
+ }>>;
194
+ }, z.core.$strip>, z.ZodObject<{
195
+ items: z.ZodArray<z.ZodObject<{
196
+ id: z.ZodUUID;
197
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
198
+ to: z.ZodArray<z.ZodString>;
199
+ subject: z.ZodString;
200
+ provider: z.ZodEnum<{
201
+ platform: "platform";
202
+ smtp: "smtp";
203
+ mailgun: "mailgun";
204
+ ses: "ses";
205
+ postmark: "postmark";
206
+ resend: "resend";
207
+ }>;
208
+ template: z.ZodNullable<z.ZodString>;
209
+ status: z.ZodEnum<{
210
+ queued: "queued";
211
+ sent: "sent";
212
+ failed: "failed";
213
+ bounced: "bounced";
214
+ }>;
215
+ providerMessageId: z.ZodNullable<z.ZodString>;
216
+ error: z.ZodNullable<z.ZodString>;
217
+ tags: z.ZodArray<z.ZodString>;
218
+ createdAt: z.ZodISODateTime;
219
+ updatedAt: z.ZodISODateTime;
220
+ }, z.core.$strip>>;
221
+ nextCursor: z.ZodNullable<z.ZodString>;
222
+ total: z.ZodOptional<z.ZodNumber>;
223
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
224
+ BAD_REQUEST: {
225
+ data: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
226
+ };
227
+ UNAUTHORIZED: {};
228
+ FORBIDDEN: {
229
+ data: z.ZodOptional<z.ZodObject<{
230
+ permission: z.ZodOptional<z.ZodString>;
231
+ }, z.core.$strip>>;
232
+ };
233
+ NOT_FOUND: {};
234
+ CONFLICT: {
235
+ data: z.ZodOptional<z.ZodObject<{
236
+ reason: z.ZodOptional<z.ZodString>;
237
+ }, z.core.$strip>>;
238
+ };
239
+ RATE_LIMITED: {};
240
+ MODULE_DISABLED: {
241
+ data: z.ZodObject<{
242
+ module: z.ZodString;
243
+ }, z.core.$strip>;
244
+ };
245
+ }>, Record<never, never>>;
246
+ };
247
+ };
248
+ export type MailContract = typeof mailContract;
249
+ export declare const mailEvents: {
250
+ deliverySent: import("@kernhq/contracts").EventDef<"mail.delivery.sent", z.ZodObject<{
251
+ deliveryId: z.ZodUUID;
252
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
253
+ to: z.ZodArray<z.ZodString>;
254
+ providerMessageId: z.ZodNullable<z.ZodString>;
255
+ error: z.ZodNullable<z.ZodString>;
256
+ }, z.core.$strip>>;
257
+ deliveryFailed: import("@kernhq/contracts").EventDef<"mail.delivery.failed", z.ZodObject<{
258
+ deliveryId: z.ZodUUID;
259
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
260
+ to: z.ZodArray<z.ZodString>;
261
+ providerMessageId: z.ZodNullable<z.ZodString>;
262
+ error: z.ZodNullable<z.ZodString>;
263
+ }, z.core.$strip>>;
264
+ deliveryBounced: import("@kernhq/contracts").EventDef<"mail.delivery.bounced", z.ZodObject<{
265
+ deliveryId: z.ZodUUID;
266
+ workspaceId: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">>;
267
+ to: z.ZodArray<z.ZodString>;
268
+ providerMessageId: z.ZodNullable<z.ZodString>;
269
+ error: z.ZodNullable<z.ZodString>;
270
+ }, z.core.$strip>>;
271
+ };
272
+ export declare const mailPermissions: readonly [{
273
+ readonly key: "mail.settings.manage";
274
+ readonly label: "Manage mail settings";
275
+ readonly scope: "workspace";
276
+ readonly defaultRoles: ["owner", "admin"];
277
+ readonly dangerous: true;
278
+ }, {
279
+ readonly key: "mail.deliveries.view";
280
+ readonly label: "View mail deliveries";
281
+ readonly scope: "workspace";
282
+ readonly defaultRoles: ["owner", "admin"];
283
+ readonly dangerous: false;
284
+ }];
285
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,SAAS,SAAS,CAAA;AAE/B,+GAA+G;AAC/G,eAAO,MAAM,kBAAkB,oBAAoB,CAAA;AAEnD,eAAO,MAAM,gBAAgB;;;;;;;EAAuE,CAAA;AACpG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAE/D,eAAO,MAAM,cAAc;;;;;iBAWvB,CAAA;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;iBAgBxB,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEzD,eAAO,MAAM,kBAAkB;;;;;EAAkD,CAAA;AACjF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAEnE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;iBAavB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD,eAAO,MAAM,eAAe;;;;;;;;;;;iBAO1B,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAI7D;;;;GAIG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqBxB,CAAA;AACD,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAA;AAS9C,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;CAItB,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;EAe1B,CAAA"}
@@ -0,0 +1,115 @@
1
+ import { baseContract, defineEvent, definePermissions, Email, PageInput, page, Timestamp, WorkspaceId, } from '@kernhq/contracts';
2
+ import { z } from 'zod';
3
+ export const MODULE_ID = 'mail';
4
+ /** Value returned in place of a secret field when reading settings; sending it back keeps the stored value. */
5
+ export const SECRET_PLACEHOLDER = '__kern_secret__';
6
+ export const MailProviderKind = z.enum(['platform', 'smtp', 'mailgun', 'ses', 'postmark', 'resend']);
7
+ export const MailAttachment = z
8
+ .object({
9
+ filename: z.string().min(1).max(255),
10
+ contentType: z.string().default('application/octet-stream'),
11
+ /** id of a file stored in core files (fetched at send time) */
12
+ fileId: z.uuid().optional(),
13
+ /** inline content, base64-encoded (small attachments only) */
14
+ base64: z.string().optional(),
15
+ })
16
+ .refine((a) => Boolean(a.fileId) !== Boolean(a.base64), {
17
+ message: 'exactly one of fileId or base64 is required',
18
+ });
19
+ export const SendMailInput = z.object({
20
+ /** omitted for instance-level mail (signup verification…): the platform provider is used */
21
+ workspaceId: WorkspaceId.optional(),
22
+ to: z.array(Email).min(1).max(50),
23
+ cc: z.array(Email).max(50).optional(),
24
+ bcc: z.array(Email).max(50).optional(),
25
+ subject: z.string().min(1).max(500),
26
+ text: z.string().optional(),
27
+ html: z.string().optional(),
28
+ /** render a named MJML template with `data` instead of passing text/html */
29
+ template: z.object({ name: z.string(), data: z.record(z.string(), z.unknown()).default({}) }).optional(),
30
+ replyTo: Email.optional(),
31
+ headers: z.record(z.string(), z.string()).optional(),
32
+ attachments: z.array(MailAttachment).max(20).optional(),
33
+ /** free-form tags stored on the delivery (e.g. `digest`, `invite`) */
34
+ tags: z.array(z.string().max(64)).max(10).optional(),
35
+ });
36
+ export const MailDeliveryStatus = z.enum(['queued', 'sent', 'failed', 'bounced']);
37
+ export const MailDelivery = z.object({
38
+ id: z.uuid(),
39
+ workspaceId: WorkspaceId.nullable(),
40
+ to: z.array(z.string()),
41
+ subject: z.string(),
42
+ provider: MailProviderKind,
43
+ template: z.string().nullable(),
44
+ status: MailDeliveryStatus,
45
+ providerMessageId: z.string().nullable(),
46
+ error: z.string().nullable(),
47
+ tags: z.array(z.string()),
48
+ createdAt: Timestamp,
49
+ updatedAt: Timestamp,
50
+ });
51
+ export const MailSuppression = z.object({
52
+ id: z.uuid(),
53
+ workspaceId: WorkspaceId.nullable(),
54
+ email: z.string(),
55
+ reason: z.enum(['bounce', 'complaint', 'manual']),
56
+ source: z.string().nullable(),
57
+ createdAt: Timestamp,
58
+ });
59
+ const ws = z.object({ workspaceId: WorkspaceId });
60
+ /**
61
+ * Workspace mail settings as exposed over the API. Secret fields are replaced with
62
+ * SECRET_PLACEHOLDER on read; sending the placeholder back keeps the stored secret.
63
+ * The stored shape is `core.MailProviderConfig` (contracts).
64
+ */
65
+ export const mailContract = {
66
+ settings: {
67
+ get: baseContract
68
+ .route({ method: 'GET', path: '/settings', tags: ['mail'] })
69
+ .input(ws)
70
+ .output(z.object({ config: z.record(z.string(), z.unknown()).nullable() })),
71
+ set: baseContract
72
+ .route({ method: 'PUT', path: '/settings', tags: ['mail'] })
73
+ .input(ws.extend({ config: z.record(z.string(), z.unknown()).nullable() }))
74
+ .output(z.object({ ok: z.boolean() })),
75
+ test: baseContract
76
+ .route({ method: 'POST', path: '/settings/test', tags: ['mail'] })
77
+ .input(ws.extend({ to: Email }))
78
+ .output(z.object({ ok: z.boolean(), error: z.string().nullable() })),
79
+ },
80
+ deliveries: {
81
+ list: baseContract
82
+ .route({ method: 'GET', path: '/deliveries', tags: ['mail'] })
83
+ .input(ws.extend(PageInput.shape).extend({ status: MailDeliveryStatus.optional() }))
84
+ .output(page(MailDelivery)),
85
+ },
86
+ };
87
+ const deliveryEvent = z.object({
88
+ deliveryId: z.uuid(),
89
+ workspaceId: WorkspaceId.nullable(),
90
+ to: z.array(z.string()),
91
+ providerMessageId: z.string().nullable(),
92
+ error: z.string().nullable(),
93
+ });
94
+ export const mailEvents = {
95
+ deliverySent: defineEvent('mail.delivery.sent', deliveryEvent),
96
+ deliveryFailed: defineEvent('mail.delivery.failed', deliveryEvent),
97
+ deliveryBounced: defineEvent('mail.delivery.bounced', deliveryEvent),
98
+ };
99
+ export const mailPermissions = definePermissions([
100
+ {
101
+ key: 'mail.settings.manage',
102
+ label: 'Manage mail settings',
103
+ scope: 'workspace',
104
+ defaultRoles: ['owner', 'admin'],
105
+ dangerous: true,
106
+ },
107
+ {
108
+ key: 'mail.deliveries.view',
109
+ label: 'View mail deliveries',
110
+ scope: 'workspace',
111
+ defaultRoles: ['owner', 'admin'],
112
+ dangerous: false,
113
+ },
114
+ ]);
115
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,KAAK,EACL,SAAS,EACT,IAAI,EACJ,SAAS,EACT,WAAW,GACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAA;AAE/B,+GAA+G;AAC/G,MAAM,CAAC,MAAM,kBAAkB,GAAG,iBAAiB,CAAA;AAEnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAA;AAGpG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,CAAC;IACN,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACpC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,0BAA0B,CAAC;IAC3D,+DAA+D;IAC/D,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC3B,8DAA8D;IAC9D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC;KACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE;IACtD,OAAO,EAAE,6CAA6C;CACvD,CAAC,CAAA;AAGJ,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,4FAA4F;IAC5F,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE;IACnC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACjC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACrC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACxG,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE;IACzB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpD,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvD,sEAAsE;IACtE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;CACrD,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAGjF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;IACZ,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE;IACnC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,gBAAgB;IAC1B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,kBAAkB;IAC1B,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;CACrB,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;IACZ,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,SAAS,EAAE,SAAS;CACrB,CAAC,CAAA;AAGF,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAA;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,QAAQ,EAAE;QACR,GAAG,EAAE,YAAY;aACd,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;aAC3D,KAAK,CAAC,EAAE,CAAC;aACT,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC7E,GAAG,EAAE,YAAY;aACd,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;aAC3D,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;aAC1E,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,EAAE,YAAY;aACf,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;aACjE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;KACvE;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;aACf,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;aAC7D,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,kBAAkB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;aACnF,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;KAC9B;CACF,CAAA;AAGD,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE;IACpB,WAAW,EAAE,WAAW,CAAC,QAAQ,EAAE;IACnC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAA;AACF,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY,EAAE,WAAW,CAAC,oBAAoB,EAAE,aAAa,CAAC;IAC9D,cAAc,EAAE,WAAW,CAAC,sBAAsB,EAAE,aAAa,CAAC;IAClE,eAAe,EAAE,WAAW,CAAC,uBAAuB,EAAE,aAAa,CAAC;CACrE,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;IAC/C;QACE,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAChC,SAAS,EAAE,IAAI;KAChB;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,KAAK,EAAE,WAAW;QAClB,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAChC,SAAS,EAAE,KAAK;KACjB;CACF,CAAC,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { type Kernel } from '@kernhq/kernel';
2
+ import { z } from 'zod';
3
+ import { MODULE_ID, mailContract, mailEvents, mailPermissions, SECRET_PLACEHOLDER, SendMailInput } from '../contract.js';
4
+ export * from './providers/index.js';
5
+ export * from './schema.js';
6
+ export * from './send.js';
7
+ export * from './settings.js';
8
+ export * from './suppressions.js';
9
+ export * from './templates.js';
10
+ /**
11
+ * Queues an email. Every caller — this module's own API, other modules through `kernel.call('mail.send')`
12
+ * and the core service's account emails — goes through here, so delivery, retries, suppression and the
13
+ * audit trail behave identically no matter who sent the message.
14
+ */
15
+ export declare function queueSend(kernel: Kernel, input: SendMailInput): Promise<{
16
+ deliveryId: string;
17
+ status: string;
18
+ }>;
19
+ /**
20
+ * The mail module: outbound email for the whole platform. Providers are configured per workspace
21
+ * (SMTP, Mailgun, SES, Postmark, Resend) and fall back to the instance's own SMTP settings, so a
22
+ * self-hosted install works with nothing but `SMTP_URL`.
23
+ */
24
+ export declare const mailModule: import("@kernhq/kernel").ServerModule<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
25
+ export { MODULE_ID, mailContract, mailEvents, mailPermissions, SECRET_PLACEHOLDER };
26
+ export default mailModule;
27
+ /** Guard used by the service when a provider webhook arrives for an unknown delivery. */
28
+ export declare function requireDelivery<T>(row: T | undefined | null): T;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,MAAM,EAIZ,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAEL,SAAS,EACT,YAAY,EACZ,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,gBAAgB,CAAA;AAOvB,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,mBAAmB,CAAA;AACjC,cAAc,gBAAgB,CAAA;AAK9B;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBjD;AA2ED;;;;GAIG;AACH,eAAO,MAAM,UAAU,gHA4DrB,CAAA;AAEF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAA;AACnF,eAAe,UAAU,CAAA;AAEzB,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,CAAC,CAG/D"}
@@ -0,0 +1,189 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { defineModule, defineServerModule, KernError, requires, uuidv7, workspaceScoped, } from '@kernhq/kernel';
4
+ import { implement } from '@orpc/server';
5
+ import { and, desc, eq, lt } from 'drizzle-orm';
6
+ import { z } from 'zod';
7
+ import { MODULE_ID, mailContract, mailEvents, mailPermissions, SECRET_PLACEHOLDER, SendMailInput, } from '../contract.js';
8
+ import { deliveries, schema } from './schema.js';
9
+ import { buildMessage, instanceName, processSend, resolveConfig } from './send.js';
10
+ import { maskConfig, unmaskConfig } from './settings.js';
11
+ import { addSuppression } from './suppressions.js';
12
+ import { renderTemplate } from './templates.js';
13
+ export * from './providers/index.js';
14
+ export * from './schema.js';
15
+ export * from './send.js';
16
+ export * from './settings.js';
17
+ export * from './suppressions.js';
18
+ export * from './templates.js';
19
+ const SEND_JOB = 'send';
20
+ const iso = (d) => d.toISOString();
21
+ /**
22
+ * Queues an email. Every caller — this module's own API, other modules through `kernel.call('mail.send')`
23
+ * and the core service's account emails — goes through here, so delivery, retries, suppression and the
24
+ * audit trail behave identically no matter who sent the message.
25
+ */
26
+ export async function queueSend(kernel, input) {
27
+ const message = SendMailInput.parse(input);
28
+ const config = await resolveConfig(kernel, message.workspaceId);
29
+ const built = await buildMessage(kernel, message, fromAddress(config));
30
+ const deliveryId = uuidv7();
31
+ await kernel.database.db.insert(deliveries).values({
32
+ id: deliveryId,
33
+ workspaceId: message.workspaceId ?? null,
34
+ to: message.to,
35
+ subject: built.subject,
36
+ provider: config?.provider ?? 'platform',
37
+ template: message.template?.name ?? null,
38
+ status: 'queued',
39
+ tags: message.tags ?? [],
40
+ });
41
+ await kernel.jobs.send(`${MODULE_ID}.${SEND_JOB}`, { deliveryId, message });
42
+ return { deliveryId, status: 'queued' };
43
+ }
44
+ /** The envelope sender for a workspace's configured provider, or the instance default. */
45
+ function fromAddress(config) {
46
+ if (config && 'from' in config && typeof config.from === 'string')
47
+ return config.from;
48
+ return process.env.MAIL_FROM ?? `${instanceName()} <no-reply@localhost>`;
49
+ }
50
+ const os = implement(mailContract).$context();
51
+ function mailRouter(kernel) {
52
+ const scoped = os.use(workspaceScoped(MODULE_ID));
53
+ return os.router({
54
+ settings: {
55
+ get: scoped.settings.get.use(requires('mail.settings.manage')).handler(async ({ input }) => {
56
+ const stored = await kernel.settings.integration(input.workspaceId, 'mail');
57
+ return { config: stored ? maskConfig(stored) : null };
58
+ }),
59
+ set: scoped.settings.set.use(requires('mail.settings.manage')).handler(async ({ input }) => {
60
+ if (input.config === null) {
61
+ await kernel.settings.setIntegration(input.workspaceId, 'mail', null);
62
+ return { ok: true };
63
+ }
64
+ const previous = await kernel.settings.integration(input.workspaceId, 'mail');
65
+ // secrets come back from the client as placeholders; keep whatever is already stored
66
+ const merged = unmaskConfig(input.config, previous ?? null);
67
+ await kernel.settings.setIntegration(input.workspaceId, 'mail', merged);
68
+ return { ok: true };
69
+ }),
70
+ test: scoped.settings.test.use(requires('mail.settings.manage')).handler(async ({ input }) => {
71
+ try {
72
+ await queueSend(kernel, {
73
+ workspaceId: input.workspaceId,
74
+ to: [input.to],
75
+ subject: `${instanceName()} test message`,
76
+ template: { name: 'test', data: { instanceName: instanceName() } },
77
+ });
78
+ return { ok: true, error: null };
79
+ }
80
+ catch (err) {
81
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
82
+ }
83
+ }),
84
+ },
85
+ deliveries: {
86
+ list: scoped.deliveries.list.use(requires('mail.deliveries.view')).handler(async ({ input }) => {
87
+ const where = [eq(deliveries.workspaceId, input.workspaceId)];
88
+ if (input.status)
89
+ where.push(eq(deliveries.status, input.status));
90
+ if (input.cursor)
91
+ where.push(lt(deliveries.id, input.cursor));
92
+ const rows = await kernel.database.db
93
+ .select()
94
+ .from(deliveries)
95
+ .where(and(...where))
96
+ .orderBy(desc(deliveries.id))
97
+ .limit(input.limit + 1);
98
+ const items = rows.slice(0, input.limit).map((r) => ({
99
+ id: r.id,
100
+ workspaceId: r.workspaceId,
101
+ to: r.to,
102
+ subject: r.subject,
103
+ provider: r.provider,
104
+ template: r.template,
105
+ status: r.status,
106
+ providerMessageId: r.providerMessageId,
107
+ error: r.error,
108
+ tags: r.tags,
109
+ createdAt: iso(r.createdAt),
110
+ updatedAt: iso(r.updatedAt),
111
+ }));
112
+ const nextCursor = rows.length > input.limit ? (items.at(-1)?.id ?? null) : null;
113
+ return { items, nextCursor };
114
+ }),
115
+ },
116
+ });
117
+ }
118
+ /**
119
+ * The mail module: outbound email for the whole platform. Providers are configured per workspace
120
+ * (SMTP, Mailgun, SES, Postmark, Resend) and fall back to the instance's own SMTP settings, so a
121
+ * self-hosted install works with nothing but `SMTP_URL`.
122
+ */
123
+ export const mailModule = defineServerModule({
124
+ definition: defineModule({
125
+ id: MODULE_ID,
126
+ name: 'Mail',
127
+ version: '0.1.0',
128
+ description: 'Outbound email: per-workspace providers, templates, delivery log and suppressions',
129
+ icon: 'mail',
130
+ core: false,
131
+ defaultHost: 'mail',
132
+ permissions: mailPermissions,
133
+ events: mailEvents,
134
+ }),
135
+ schema,
136
+ migrationsFolder: join(dirname(fileURLToPath(import.meta.url)), '../../migrations'),
137
+ router: mailRouter,
138
+ jobs: [
139
+ {
140
+ name: SEND_JOB,
141
+ schema: z.object({ deliveryId: z.uuid(), message: SendMailInput }),
142
+ options: { retryLimit: 5, retryDelay: 30, retryBackoff: true },
143
+ handler: async (input, { kernel }) => processSend(kernel, input),
144
+ },
145
+ ],
146
+ procedures: {
147
+ /** Send an email. Used by core for account mail and by any module that needs to notify people. */
148
+ send: {
149
+ input: SendMailInput,
150
+ output: z.object({ deliveryId: z.string(), status: z.string() }),
151
+ handler: (input, { kernel }) => queueSend(kernel, input),
152
+ },
153
+ /** Render a template without sending it — used for previews and tests. */
154
+ render: {
155
+ input: z.object({ name: z.string(), data: z.record(z.string(), z.unknown()).default({}) }),
156
+ output: z.object({ subject: z.string(), html: z.string(), text: z.string() }),
157
+ handler: (input) => renderTemplate(input.name, input.data, { instanceName: instanceName() }),
158
+ },
159
+ /** Record a bounce or complaint so later sends skip the address. */
160
+ suppress: {
161
+ input: z.object({
162
+ workspaceId: z.string().nullable().default(null),
163
+ email: z.string(),
164
+ reason: z.string(),
165
+ }),
166
+ handler: async (input, { kernel }) => {
167
+ await addSuppression(kernel, {
168
+ workspaceId: input.workspaceId,
169
+ email: input.email,
170
+ reason: input.reason,
171
+ });
172
+ return { ok: true };
173
+ },
174
+ },
175
+ },
176
+ /** Provider webhooks are mounted by the mail service (see `repos/mail/src/webhooks.ts`). */
177
+ onBoot: (kernel) => {
178
+ kernel.log.info({ module: MODULE_ID }, 'mail module ready');
179
+ },
180
+ });
181
+ export { MODULE_ID, mailContract, mailEvents, mailPermissions, SECRET_PLACEHOLDER };
182
+ export default mailModule;
183
+ /** Guard used by the service when a provider webhook arrives for an unknown delivery. */
184
+ export function requireDelivery(row) {
185
+ if (!row)
186
+ throw KernError.notFound('Delivery');
187
+ return row;
188
+ }
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}