@softure-ai/mailing 0.0.0-stage → 0.1.5

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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -2
  3. package/dist/address.d.ts +27 -0
  4. package/dist/address.d.ts.map +1 -0
  5. package/dist/address.js +76 -0
  6. package/dist/address.js.map +1 -0
  7. package/dist/cli/bin.d.ts +3 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +5 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/command.d.ts +9 -0
  12. package/dist/cli/command.d.ts.map +1 -0
  13. package/dist/cli/command.js +36 -0
  14. package/dist/cli/command.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +4 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/run.d.ts +31 -0
  20. package/dist/cli/run.d.ts.map +1 -0
  21. package/dist/cli/run.js +285 -0
  22. package/dist/cli/run.js.map +1 -0
  23. package/dist/contract.d.ts +102 -0
  24. package/dist/contract.d.ts.map +1 -0
  25. package/dist/contract.js +3 -0
  26. package/dist/contract.js.map +1 -0
  27. package/dist/index.d.ts +47 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +59 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/messages/en.d.ts +26 -0
  32. package/dist/messages/en.d.ts.map +1 -0
  33. package/dist/messages/en.js +26 -0
  34. package/dist/messages/en.js.map +1 -0
  35. package/dist/messages/index.d.ts +59 -0
  36. package/dist/messages/index.d.ts.map +1 -0
  37. package/dist/messages/index.js +11 -0
  38. package/dist/messages/index.js.map +1 -0
  39. package/dist/messages/pl.d.ts +3 -0
  40. package/dist/messages/pl.d.ts.map +1 -0
  41. package/dist/messages/pl.js +27 -0
  42. package/dist/messages/pl.js.map +1 -0
  43. package/dist/next/actions.d.ts +2 -0
  44. package/dist/next/actions.d.ts.map +1 -0
  45. package/dist/next/actions.js +35 -0
  46. package/dist/next/actions.js.map +1 -0
  47. package/dist/next/context.d.ts +4 -0
  48. package/dist/next/context.d.ts.map +1 -0
  49. package/dist/next/context.js +14 -0
  50. package/dist/next/context.js.map +1 -0
  51. package/dist/next/deliver-once.d.ts +4 -0
  52. package/dist/next/deliver-once.d.ts.map +1 -0
  53. package/dist/next/deliver-once.js +7 -0
  54. package/dist/next/deliver-once.js.map +1 -0
  55. package/dist/next/index.d.ts +8 -0
  56. package/dist/next/index.d.ts.map +1 -0
  57. package/dist/next/index.js +11 -0
  58. package/dist/next/index.js.map +1 -0
  59. package/dist/next/messages.d.ts +5 -0
  60. package/dist/next/messages.d.ts.map +1 -0
  61. package/dist/next/messages.js +7 -0
  62. package/dist/next/messages.js.map +1 -0
  63. package/dist/next/pages.d.ts +7 -0
  64. package/dist/next/pages.d.ts.map +1 -0
  65. package/dist/next/pages.js +40 -0
  66. package/dist/next/pages.js.map +1 -0
  67. package/dist/next/params.d.ts +4 -0
  68. package/dist/next/params.d.ts.map +1 -0
  69. package/dist/next/params.js +4 -0
  70. package/dist/next/params.js.map +1 -0
  71. package/dist/next/route.d.ts +14 -0
  72. package/dist/next/route.d.ts.map +1 -0
  73. package/dist/next/route.js +44 -0
  74. package/dist/next/route.js.map +1 -0
  75. package/dist/next/send-mail.d.ts +4 -0
  76. package/dist/next/send-mail.d.ts.map +1 -0
  77. package/dist/next/send-mail.js +16 -0
  78. package/dist/next/send-mail.js.map +1 -0
  79. package/dist/options.d.ts +13 -0
  80. package/dist/options.d.ts.map +1 -0
  81. package/dist/options.js +29 -0
  82. package/dist/options.js.map +1 -0
  83. package/dist/providers/resend.d.ts +17 -0
  84. package/dist/providers/resend.d.ts.map +1 -0
  85. package/dist/providers/resend.js +64 -0
  86. package/dist/providers/resend.js.map +1 -0
  87. package/dist/schema.d.ts +350 -0
  88. package/dist/schema.d.ts.map +1 -0
  89. package/dist/schema.js +30 -0
  90. package/dist/schema.js.map +1 -0
  91. package/dist/server/campaign-file.d.ts +28 -0
  92. package/dist/server/campaign-file.d.ts.map +1 -0
  93. package/dist/server/campaign-file.js +87 -0
  94. package/dist/server/campaign-file.js.map +1 -0
  95. package/dist/server/campaigns.d.ts +58 -0
  96. package/dist/server/campaigns.d.ts.map +1 -0
  97. package/dist/server/campaigns.js +118 -0
  98. package/dist/server/campaigns.js.map +1 -0
  99. package/dist/server/deliveries.d.ts +56 -0
  100. package/dist/server/deliveries.d.ts.map +1 -0
  101. package/dist/server/deliveries.js +100 -0
  102. package/dist/server/deliveries.js.map +1 -0
  103. package/dist/server/dns.d.ts +33 -0
  104. package/dist/server/dns.d.ts.map +1 -0
  105. package/dist/server/dns.js +113 -0
  106. package/dist/server/dns.js.map +1 -0
  107. package/dist/server/health.d.ts +3 -0
  108. package/dist/server/health.d.ts.map +1 -0
  109. package/dist/server/health.js +13 -0
  110. package/dist/server/health.js.map +1 -0
  111. package/dist/server/index.d.ts +12 -0
  112. package/dist/server/index.d.ts.map +1 -0
  113. package/dist/server/index.js +14 -0
  114. package/dist/server/index.js.map +1 -0
  115. package/dist/server/list-mail.d.ts +20 -0
  116. package/dist/server/list-mail.d.ts.map +1 -0
  117. package/dist/server/list-mail.js +31 -0
  118. package/dist/server/list-mail.js.map +1 -0
  119. package/dist/server/options.d.ts +14 -0
  120. package/dist/server/options.d.ts.map +1 -0
  121. package/dist/server/options.js +22 -0
  122. package/dist/server/options.js.map +1 -0
  123. package/dist/server/send-mail.d.ts +19 -0
  124. package/dist/server/send-mail.d.ts.map +1 -0
  125. package/dist/server/send-mail.js +129 -0
  126. package/dist/server/send-mail.js.map +1 -0
  127. package/dist/server/suppressions.d.ts +27 -0
  128. package/dist/server/suppressions.d.ts.map +1 -0
  129. package/dist/server/suppressions.js +64 -0
  130. package/dist/server/suppressions.js.map +1 -0
  131. package/dist/server/unsubscribe-link.d.ts +51 -0
  132. package/dist/server/unsubscribe-link.d.ts.map +1 -0
  133. package/dist/server/unsubscribe-link.js +78 -0
  134. package/dist/server/unsubscribe-link.js.map +1 -0
  135. package/dist/server/validate-mail.d.ts +23 -0
  136. package/dist/server/validate-mail.d.ts.map +1 -0
  137. package/dist/server/validate-mail.js +52 -0
  138. package/dist/server/validate-mail.js.map +1 -0
  139. package/dist/testing/fake-provider.d.ts +24 -0
  140. package/dist/testing/fake-provider.d.ts.map +1 -0
  141. package/dist/testing/fake-provider.js +43 -0
  142. package/dist/testing/fake-provider.js.map +1 -0
  143. package/dist/testing/index.d.ts +3 -0
  144. package/dist/testing/index.d.ts.map +1 -0
  145. package/dist/testing/index.js +5 -0
  146. package/dist/testing/index.js.map +1 -0
  147. package/dist/testing/outbox.d.ts +6 -0
  148. package/dist/testing/outbox.d.ts.map +1 -0
  149. package/dist/testing/outbox.js +32 -0
  150. package/dist/testing/outbox.js.map +1 -0
  151. package/migrations/0001_create_suppressions.sql +13 -0
  152. package/migrations/0002_create_campaigns_and_deliveries.sql +41 -0
  153. package/module.json +32 -0
  154. package/package.json +70 -4
  155. package/src/address.ts +89 -0
  156. package/src/cli/bin.ts +5 -0
  157. package/src/cli/command.ts +44 -0
  158. package/src/cli/index.ts +3 -0
  159. package/src/cli/run.ts +315 -0
  160. package/src/contract.ts +108 -0
  161. package/src/index.ts +85 -0
  162. package/src/messages/en.ts +25 -0
  163. package/src/messages/index.ts +15 -0
  164. package/src/messages/pl.ts +28 -0
  165. package/src/next/actions.ts +35 -0
  166. package/src/next/context.ts +15 -0
  167. package/src/next/deliver-once.ts +10 -0
  168. package/src/next/index.ts +10 -0
  169. package/src/next/messages.ts +9 -0
  170. package/src/next/next-modules.d.ts +12 -0
  171. package/src/next/pages.tsx +69 -0
  172. package/src/next/params.ts +6 -0
  173. package/src/next/route.ts +45 -0
  174. package/src/next/send-mail.ts +15 -0
  175. package/src/options.ts +34 -0
  176. package/src/providers/resend.ts +86 -0
  177. package/src/schema.ts +40 -0
  178. package/src/server/campaign-file.ts +101 -0
  179. package/src/server/campaigns.ts +175 -0
  180. package/src/server/deliveries.ts +155 -0
  181. package/src/server/dns.ts +153 -0
  182. package/src/server/health.ts +14 -0
  183. package/src/server/index.ts +56 -0
  184. package/src/server/list-mail.ts +42 -0
  185. package/src/server/options.ts +32 -0
  186. package/src/server/send-mail.ts +152 -0
  187. package/src/server/suppressions.ts +77 -0
  188. package/src/server/unsubscribe-link.ts +109 -0
  189. package/src/server/validate-mail.ts +68 -0
  190. package/src/testing/fake-provider.ts +66 -0
  191. package/src/testing/index.ts +4 -0
  192. package/src/testing/outbox.ts +32 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/providers/resend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,350 @@
1
+ export declare const mailingSchema: import("drizzle-orm/pg-core").PgSchema<"mailing">;
2
+ export declare const suppressions: import("drizzle-orm/pg-core").PgTableWithColumns<{
3
+ name: "suppressions";
4
+ schema: "mailing";
5
+ columns: {
6
+ recipientKey: import("drizzle-orm/pg-core").PgColumn<{
7
+ name: "recipient_key";
8
+ tableName: "suppressions";
9
+ dataType: "string";
10
+ columnType: "PgText";
11
+ data: string;
12
+ driverParam: string;
13
+ notNull: true;
14
+ hasDefault: false;
15
+ isPrimaryKey: true;
16
+ isAutoincrement: false;
17
+ hasRuntimeDefault: false;
18
+ enumValues: [string, ...string[]];
19
+ baseColumn: never;
20
+ identity: undefined;
21
+ generated: undefined;
22
+ }, {}, {}>;
23
+ source: import("drizzle-orm/pg-core").PgColumn<{
24
+ name: "source";
25
+ tableName: "suppressions";
26
+ dataType: "string";
27
+ columnType: "PgText";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: [string, ...string[]];
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
41
+ name: "created_at";
42
+ tableName: "suppressions";
43
+ dataType: "date";
44
+ columnType: "PgTimestamp";
45
+ data: Date;
46
+ driverParam: string;
47
+ notNull: true;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: undefined;
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {}>;
57
+ };
58
+ dialect: "pg";
59
+ }>;
60
+ export declare const campaigns: import("drizzle-orm/pg-core").PgTableWithColumns<{
61
+ name: "campaigns";
62
+ schema: "mailing";
63
+ columns: {
64
+ id: import("drizzle-orm/pg-core").PgColumn<{
65
+ name: "id";
66
+ tableName: "campaigns";
67
+ dataType: "string";
68
+ columnType: "PgText";
69
+ data: string;
70
+ driverParam: string;
71
+ notNull: true;
72
+ hasDefault: false;
73
+ isPrimaryKey: true;
74
+ isAutoincrement: false;
75
+ hasRuntimeDefault: false;
76
+ enumValues: [string, ...string[]];
77
+ baseColumn: never;
78
+ identity: undefined;
79
+ generated: undefined;
80
+ }, {}, {}>;
81
+ kind: import("drizzle-orm/pg-core").PgColumn<{
82
+ name: "kind";
83
+ tableName: "campaigns";
84
+ dataType: "string";
85
+ columnType: "PgText";
86
+ data: string;
87
+ driverParam: string;
88
+ notNull: true;
89
+ hasDefault: false;
90
+ isPrimaryKey: false;
91
+ isAutoincrement: false;
92
+ hasRuntimeDefault: false;
93
+ enumValues: [string, ...string[]];
94
+ baseColumn: never;
95
+ identity: undefined;
96
+ generated: undefined;
97
+ }, {}, {}>;
98
+ subject: import("drizzle-orm/pg-core").PgColumn<{
99
+ name: "subject";
100
+ tableName: "campaigns";
101
+ dataType: "string";
102
+ columnType: "PgText";
103
+ data: string;
104
+ driverParam: string;
105
+ notNull: true;
106
+ hasDefault: false;
107
+ isPrimaryKey: false;
108
+ isAutoincrement: false;
109
+ hasRuntimeDefault: false;
110
+ enumValues: [string, ...string[]];
111
+ baseColumn: never;
112
+ identity: undefined;
113
+ generated: undefined;
114
+ }, {}, {}>;
115
+ contentHash: import("drizzle-orm/pg-core").PgColumn<{
116
+ name: "content_hash";
117
+ tableName: "campaigns";
118
+ dataType: "string";
119
+ columnType: "PgText";
120
+ data: string;
121
+ driverParam: string;
122
+ notNull: true;
123
+ hasDefault: false;
124
+ isPrimaryKey: false;
125
+ isAutoincrement: false;
126
+ hasRuntimeDefault: false;
127
+ enumValues: [string, ...string[]];
128
+ baseColumn: never;
129
+ identity: undefined;
130
+ generated: undefined;
131
+ }, {}, {}>;
132
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
133
+ name: "created_at";
134
+ tableName: "campaigns";
135
+ dataType: "date";
136
+ columnType: "PgTimestamp";
137
+ data: Date;
138
+ driverParam: string;
139
+ notNull: true;
140
+ hasDefault: false;
141
+ isPrimaryKey: false;
142
+ isAutoincrement: false;
143
+ hasRuntimeDefault: false;
144
+ enumValues: undefined;
145
+ baseColumn: never;
146
+ identity: undefined;
147
+ generated: undefined;
148
+ }, {}, {}>;
149
+ };
150
+ dialect: "pg";
151
+ }>;
152
+ export declare const deliveries: import("drizzle-orm/pg-core").PgTableWithColumns<{
153
+ name: "deliveries";
154
+ schema: "mailing";
155
+ columns: {
156
+ scope: import("drizzle-orm/pg-core").PgColumn<{
157
+ name: "scope";
158
+ tableName: "deliveries";
159
+ dataType: "string";
160
+ columnType: "PgText";
161
+ data: string;
162
+ driverParam: string;
163
+ notNull: true;
164
+ hasDefault: false;
165
+ isPrimaryKey: false;
166
+ isAutoincrement: false;
167
+ hasRuntimeDefault: false;
168
+ enumValues: [string, ...string[]];
169
+ baseColumn: never;
170
+ identity: undefined;
171
+ generated: undefined;
172
+ }, {}, {}>;
173
+ recipientKey: import("drizzle-orm/pg-core").PgColumn<{
174
+ name: "recipient_key";
175
+ tableName: "deliveries";
176
+ dataType: "string";
177
+ columnType: "PgText";
178
+ data: string;
179
+ driverParam: string;
180
+ notNull: true;
181
+ hasDefault: false;
182
+ isPrimaryKey: false;
183
+ isAutoincrement: false;
184
+ hasRuntimeDefault: false;
185
+ enumValues: [string, ...string[]];
186
+ baseColumn: never;
187
+ identity: undefined;
188
+ generated: undefined;
189
+ }, {}, {}>;
190
+ kind: import("drizzle-orm/pg-core").PgColumn<{
191
+ name: "kind";
192
+ tableName: "deliveries";
193
+ dataType: "string";
194
+ columnType: "PgText";
195
+ data: string;
196
+ driverParam: string;
197
+ notNull: true;
198
+ hasDefault: false;
199
+ isPrimaryKey: false;
200
+ isAutoincrement: false;
201
+ hasRuntimeDefault: false;
202
+ enumValues: [string, ...string[]];
203
+ baseColumn: never;
204
+ identity: undefined;
205
+ generated: undefined;
206
+ }, {}, {}>;
207
+ campaignId: import("drizzle-orm/pg-core").PgColumn<{
208
+ name: "campaign_id";
209
+ tableName: "deliveries";
210
+ dataType: "string";
211
+ columnType: "PgText";
212
+ data: string;
213
+ driverParam: string;
214
+ notNull: false;
215
+ hasDefault: false;
216
+ isPrimaryKey: false;
217
+ isAutoincrement: false;
218
+ hasRuntimeDefault: false;
219
+ enumValues: [string, ...string[]];
220
+ baseColumn: never;
221
+ identity: undefined;
222
+ generated: undefined;
223
+ }, {}, {}>;
224
+ status: import("drizzle-orm/pg-core").PgColumn<{
225
+ name: "status";
226
+ tableName: "deliveries";
227
+ dataType: "string";
228
+ columnType: "PgText";
229
+ data: DeliveryStatus;
230
+ driverParam: string;
231
+ notNull: true;
232
+ hasDefault: false;
233
+ isPrimaryKey: false;
234
+ isAutoincrement: false;
235
+ hasRuntimeDefault: false;
236
+ enumValues: [string, ...string[]];
237
+ baseColumn: never;
238
+ identity: undefined;
239
+ generated: undefined;
240
+ }, {}, {
241
+ $type: DeliveryStatus;
242
+ }>;
243
+ attempts: import("drizzle-orm/pg-core").PgColumn<{
244
+ name: "attempts";
245
+ tableName: "deliveries";
246
+ dataType: "number";
247
+ columnType: "PgInteger";
248
+ data: number;
249
+ driverParam: string | number;
250
+ notNull: true;
251
+ hasDefault: false;
252
+ isPrimaryKey: false;
253
+ isAutoincrement: false;
254
+ hasRuntimeDefault: false;
255
+ enumValues: undefined;
256
+ baseColumn: never;
257
+ identity: undefined;
258
+ generated: undefined;
259
+ }, {}, {}>;
260
+ claimedAt: import("drizzle-orm/pg-core").PgColumn<{
261
+ name: "claimed_at";
262
+ tableName: "deliveries";
263
+ dataType: "date";
264
+ columnType: "PgTimestamp";
265
+ data: Date;
266
+ driverParam: string;
267
+ notNull: true;
268
+ hasDefault: false;
269
+ isPrimaryKey: false;
270
+ isAutoincrement: false;
271
+ hasRuntimeDefault: false;
272
+ enumValues: undefined;
273
+ baseColumn: never;
274
+ identity: undefined;
275
+ generated: undefined;
276
+ }, {}, {}>;
277
+ finishedAt: import("drizzle-orm/pg-core").PgColumn<{
278
+ name: "finished_at";
279
+ tableName: "deliveries";
280
+ dataType: "date";
281
+ columnType: "PgTimestamp";
282
+ data: Date;
283
+ driverParam: string;
284
+ notNull: false;
285
+ hasDefault: false;
286
+ isPrimaryKey: false;
287
+ isAutoincrement: false;
288
+ hasRuntimeDefault: false;
289
+ enumValues: undefined;
290
+ baseColumn: never;
291
+ identity: undefined;
292
+ generated: undefined;
293
+ }, {}, {}>;
294
+ providerMessageId: import("drizzle-orm/pg-core").PgColumn<{
295
+ name: "provider_message_id";
296
+ tableName: "deliveries";
297
+ dataType: "string";
298
+ columnType: "PgText";
299
+ data: string;
300
+ driverParam: string;
301
+ notNull: false;
302
+ hasDefault: false;
303
+ isPrimaryKey: false;
304
+ isAutoincrement: false;
305
+ hasRuntimeDefault: false;
306
+ enumValues: [string, ...string[]];
307
+ baseColumn: never;
308
+ identity: undefined;
309
+ generated: undefined;
310
+ }, {}, {}>;
311
+ reason: import("drizzle-orm/pg-core").PgColumn<{
312
+ name: "reason";
313
+ tableName: "deliveries";
314
+ dataType: "string";
315
+ columnType: "PgText";
316
+ data: string;
317
+ driverParam: string;
318
+ notNull: false;
319
+ hasDefault: false;
320
+ isPrimaryKey: false;
321
+ isAutoincrement: false;
322
+ hasRuntimeDefault: false;
323
+ enumValues: [string, ...string[]];
324
+ baseColumn: never;
325
+ identity: undefined;
326
+ generated: undefined;
327
+ }, {}, {}>;
328
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
329
+ name: "created_at";
330
+ tableName: "deliveries";
331
+ dataType: "date";
332
+ columnType: "PgTimestamp";
333
+ data: Date;
334
+ driverParam: string;
335
+ notNull: true;
336
+ hasDefault: false;
337
+ isPrimaryKey: false;
338
+ isAutoincrement: false;
339
+ hasRuntimeDefault: false;
340
+ enumValues: undefined;
341
+ baseColumn: never;
342
+ identity: undefined;
343
+ generated: undefined;
344
+ }, {}, {}>;
345
+ };
346
+ dialect: "pg";
347
+ }>;
348
+ /** A ledger row's state: `pending` (released for a retry), `claimed`, or a final `sent` / `rejected`. */
349
+ export type DeliveryStatus = "pending" | "claimed" | "sent" | "rejected";
350
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,aAAa,mDAAsB,CAAC;AAEjD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAIvB,CAAC;AAEH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMpB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBtB,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,30 @@
1
+ // Drizzle view of the module's tables (migrations/). The migrations are the source of truth; this
2
+ // file only types the queries.
3
+ import { integer, pgSchema, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
4
+ export const mailingSchema = pgSchema("mailing");
5
+ export const suppressions = mailingSchema.table("suppressions", {
6
+ recipientKey: text("recipient_key").primaryKey(),
7
+ source: text("source").notNull(),
8
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
9
+ });
10
+ export const campaigns = mailingSchema.table("campaigns", {
11
+ id: text("id").primaryKey(),
12
+ kind: text("kind").notNull(),
13
+ subject: text("subject").notNull(),
14
+ contentHash: text("content_hash").notNull(),
15
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
16
+ });
17
+ export const deliveries = mailingSchema.table("deliveries", {
18
+ scope: text("scope").notNull(),
19
+ recipientKey: text("recipient_key").notNull(),
20
+ kind: text("kind").notNull(),
21
+ campaignId: text("campaign_id"),
22
+ status: text("status").$type().notNull(),
23
+ attempts: integer("attempts").notNull(),
24
+ claimedAt: timestamp("claimed_at", { withTimezone: true }).notNull(),
25
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
26
+ providerMessageId: text("provider_message_id"),
27
+ reason: text("reason"),
28
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull(),
29
+ }, (table) => [primaryKey({ columns: [table.scope, table.recipientKey] })]);
30
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,+BAA+B;AAC/B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErF,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAEjD,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC,KAAK,CAAC,cAAc,EAAE;IAC9D,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,UAAU,EAAE;IAChD,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE;IAChC,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;CACrE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,WAAW,EAAE;IACxD,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IAClC,WAAW,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,EAAE;IAC3C,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;CACrE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAC3C,YAAY,EACZ;IACE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC7C,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC;IAC/B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAkB,CAAC,OAAO,EAAE;IACxD,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACvC,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACpE,UAAU,EAAE,SAAS,CAAC,aAAa,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;IAC5D,iBAAiB,EAAE,IAAI,CAAC,qBAAqB,CAAC;IAC9C,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC,YAAY,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;CACrE,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CACxE,CAAC"}
@@ -0,0 +1,28 @@
1
+ /** A campaign's content, as `sendCampaign` sends it. */
2
+ export interface CampaignContent {
3
+ /** Kebab-case, at most 64 characters, e.g. `2026-10-launch`. Never reuse an id for other content. */
4
+ readonly id: string;
5
+ /** The list it belongs to, e.g. `newsletter`; never `transactional`. */
6
+ readonly kind: string;
7
+ readonly subject: string;
8
+ readonly text: string;
9
+ readonly html: string | null;
10
+ }
11
+ /** A parsed content file: the HTML body is still a path relative to the file. */
12
+ export interface CampaignFile extends Omit<CampaignContent, "html"> {
13
+ readonly htmlPath: string | null;
14
+ }
15
+ export type CampaignFileResult = {
16
+ readonly ok: true;
17
+ readonly value: CampaignFile;
18
+ } | {
19
+ readonly ok: false;
20
+ readonly problems: readonly string[];
21
+ };
22
+ /** Parses a content file, listing every problem at once. */
23
+ export declare function parseCampaignFile(source: string): CampaignFileResult;
24
+ /** What makes `content` unsendable as a campaign; empty when it is fine. */
25
+ export declare function getCampaignProblems(content: CampaignContent): string[];
26
+ /** The addresses of a recipients file: one per line; blank lines and `#` comments are skipped. */
27
+ export declare function parseRecipientList(source: string): string[];
28
+ //# sourceMappingURL=campaign-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-file.d.ts","sourceRoot":"","sources":["../../src/server/campaign-file.ts"],"names":[],"mappings":"AAgBA,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,qGAAqG;IACrG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAOpJ,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAmCpE;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,EAAE,CActE;AAED,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3D"}
@@ -0,0 +1,87 @@
1
+ // The campaign content file of `softure-mail campaign`: a frontmatter block and a plain-text body.
2
+ //
3
+ // ---
4
+ // id: 2026-10-launch
5
+ // kind: newsletter
6
+ // subject: Something new in Plan
7
+ // html: launch.html
8
+ // ---
9
+ // Hello,
10
+ // ...
11
+ //
12
+ // `html` is optional and names a file next to the content file, rendered however the operator
13
+ // likes. There is no markdown: the text body is sent as written.
14
+ import { isMailKind, MAX_SUBJECT_LENGTH } from "../address.js";
15
+ import { TRANSACTIONAL_KIND } from "../contract.js";
16
+ const FENCE = "---";
17
+ const KEYS = new Set(["id", "kind", "subject", "html"]);
18
+ const CAMPAIGN_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
19
+ const MAX_CAMPAIGN_ID_LENGTH = 64;
20
+ /** Parses a content file, listing every problem at once. */
21
+ export function parseCampaignFile(source) {
22
+ const lines = source.replace(/^\uFEFF/, "").split(/\r?\n/);
23
+ if (lines[0]?.trim() !== FENCE)
24
+ return { ok: false, problems: ["the file must start with a --- line opening the frontmatter"] };
25
+ const end = lines.findIndex((line, index) => index > 0 && line.trim() === FENCE);
26
+ if (end === -1)
27
+ return { ok: false, problems: ["the frontmatter has no closing --- line"] };
28
+ const problems = [];
29
+ const fields = new Map();
30
+ lines.slice(1, end).forEach((line, index) => {
31
+ if (line.trim() === "" || line.trimStart().startsWith("#"))
32
+ return;
33
+ const match = /^([a-z]+):(.*)$/.exec(line);
34
+ const key = match?.[1];
35
+ const value = match?.[2]?.trim();
36
+ if (key === undefined || value === undefined) {
37
+ problems.push(`frontmatter line ${String(index + 2)} must read "key: value"`);
38
+ }
39
+ else if (!KEYS.has(key)) {
40
+ problems.push(`unknown frontmatter key "${key}"; use ${[...KEYS].join(", ")}`);
41
+ }
42
+ else if (fields.has(key)) {
43
+ problems.push(`frontmatter key "${key}" appears twice`);
44
+ }
45
+ else {
46
+ fields.set(key, value);
47
+ }
48
+ });
49
+ const text = lines.slice(end + 1).join("\n").trim();
50
+ const file = {
51
+ id: fields.get("id") ?? "",
52
+ kind: fields.get("kind") ?? "",
53
+ subject: fields.get("subject") ?? "",
54
+ text,
55
+ htmlPath: fields.get("html") ?? null,
56
+ };
57
+ problems.push(...getCampaignProblems({ ...file, html: null }));
58
+ if (file.htmlPath === "")
59
+ problems.push("html: give a file name or leave the key out");
60
+ return problems.length === 0 ? { ok: true, value: file } : { ok: false, problems };
61
+ }
62
+ /** What makes `content` unsendable as a campaign; empty when it is fine. */
63
+ export function getCampaignProblems(content) {
64
+ const problems = [];
65
+ if (content.id.length > MAX_CAMPAIGN_ID_LENGTH || !CAMPAIGN_ID.test(content.id)) {
66
+ problems.push("id: kebab-case, at most 64 characters, e.g. 2026-10-launch");
67
+ }
68
+ if (!isMailKind(content.kind) || content.kind === TRANSACTIONAL_KIND) {
69
+ problems.push("kind: a kebab-case list name such as newsletter; campaigns are never transactional");
70
+ }
71
+ if (content.subject.trim() === "" || content.subject.length > MAX_SUBJECT_LENGTH || /[\r\n]/.test(content.subject)) {
72
+ problems.push(`subject: one line, 1 to ${String(MAX_SUBJECT_LENGTH)} characters`);
73
+ }
74
+ if (content.text.trim() === "")
75
+ problems.push("the text body under the frontmatter is empty");
76
+ if (content.html?.trim() === "")
77
+ problems.push("the HTML body is empty");
78
+ return problems;
79
+ }
80
+ /** The addresses of a recipients file: one per line; blank lines and `#` comments are skipped. */
81
+ export function parseRecipientList(source) {
82
+ return source
83
+ .split(/\r?\n/)
84
+ .map((line) => line.trim())
85
+ .filter((line) => line !== "" && !line.startsWith("#"));
86
+ }
87
+ //# sourceMappingURL=campaign-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaign-file.js","sourceRoot":"","sources":["../../src/server/campaign-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { type Result } from "@softure-ai/core";
2
+ import type { MailingErrorCode } from "../contract.js";
3
+ import { type CampaignContent } from "./campaign-file.js";
4
+ import { type DeliverOptions, type DeliveryContext, type DeliveryOutcome } from "./deliveries.js";
5
+ /** The campaign id is taken by other content. Give the new content a new id. */
6
+ export type CampaignErrorCode = "mailing.campaign_changed";
7
+ export interface CampaignSummary {
8
+ /** Distinct recipients in the list (addresses that differ only in case or spaces count once). */
9
+ readonly recipients: number;
10
+ readonly sent: number;
11
+ /** Rejected in this run, by reason. Suppressed recipients are here, under `mailing.suppressed`. */
12
+ readonly rejected: Readonly<Record<MailingErrorCode, number>>;
13
+ /** Closed by an earlier run; nothing sent. */
14
+ readonly done: number;
15
+ /** Claimed by another run that is still sending. */
16
+ readonly inFlight: number;
17
+ /** The provider was unavailable; the next run tries them again. */
18
+ readonly retryLater: number;
19
+ }
20
+ export interface SendCampaignOptions extends DeliverOptions {
21
+ /** Pause after every mail that reached the provider, for its rate limit. Default 0. */
22
+ readonly pauseMs?: number;
23
+ /** Called after every recipient, e.g. for progress output. Never receives the address. */
24
+ readonly onDelivery?: (outcome: DeliveryOutcome) => void;
25
+ readonly sleep?: (ms: number) => Promise<void>;
26
+ }
27
+ /** sha256 (hex) of what a recipient reads: kind, subject, text and HTML. */
28
+ export declare function getCampaignContentHash(content: CampaignContent): string;
29
+ /**
30
+ * Records the campaign, or confirms that the stored one has the same content. Throws on a
31
+ * database failure and for content `getCampaignProblems` refuses.
32
+ */
33
+ export declare function registerCampaign(ctx: DeliveryContext, content: CampaignContent): Promise<Result<undefined, CampaignErrorCode>>;
34
+ /**
35
+ * Sends `campaign` to every recipient that has no outcome yet, one at a time. Recipients who
36
+ * unsubscribed are rejected (`mailing.suppressed`) and never retried. Throws on a database failure.
37
+ */
38
+ export declare function sendCampaign(ctx: DeliveryContext, input: {
39
+ readonly campaign: CampaignContent;
40
+ readonly recipients: Iterable<string> | AsyncIterable<string>;
41
+ }, options?: SendCampaignOptions): Promise<Result<CampaignSummary, CampaignErrorCode>>;
42
+ export interface CampaignPlan {
43
+ readonly recipients: number;
44
+ /** Already closed by an earlier run. */
45
+ readonly done: number;
46
+ /** Not closed yet, but unsubscribed: they will be rejected without a send. */
47
+ readonly suppressed: number;
48
+ /** Would be sent now. */
49
+ readonly toSend: number;
50
+ /** The campaign id is stored with other content: a real run would be refused. */
51
+ readonly contentChanged: boolean;
52
+ }
53
+ /** What `sendCampaign` would do, without writing or sending anything. Throws on a database failure. */
54
+ export declare function planCampaign(ctx: DeliveryContext, input: {
55
+ readonly campaign: CampaignContent;
56
+ readonly recipients: Iterable<string> | AsyncIterable<string>;
57
+ }): Promise<CampaignPlan>;
58
+ //# sourceMappingURL=campaigns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"campaigns.d.ts","sourceRoot":"","sources":["../../src/server/campaigns.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAe,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAO/G,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mGAAmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAEvE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC,CASpI;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,EAC5G,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC,CA0BrD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,uGAAuG;AACvG,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GAC3G,OAAO,CAAC,YAAY,CAAC,CAyBvB"}