@softure-ai/mailing 0.1.5 → 0.1.7

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 (91) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +85 -23
  3. package/dist/cli/run.d.ts +1 -1
  4. package/dist/cli/run.d.ts.map +1 -1
  5. package/dist/cli/run.js +56 -22
  6. package/dist/cli/run.js.map +1 -1
  7. package/dist/contract.d.ts +48 -8
  8. package/dist/contract.d.ts.map +1 -1
  9. package/dist/contract.js +2 -0
  10. package/dist/contract.js.map +1 -1
  11. package/dist/index.d.ts +12 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +3 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/messages/en.d.ts +2 -0
  16. package/dist/messages/en.d.ts.map +1 -1
  17. package/dist/messages/en.js +2 -0
  18. package/dist/messages/en.js.map +1 -1
  19. package/dist/messages/index.d.ts +4 -0
  20. package/dist/messages/index.d.ts.map +1 -1
  21. package/dist/messages/pl.d.ts.map +1 -1
  22. package/dist/messages/pl.js +2 -0
  23. package/dist/messages/pl.js.map +1 -1
  24. package/dist/next/actions.d.ts.map +1 -1
  25. package/dist/next/actions.js +6 -6
  26. package/dist/next/actions.js.map +1 -1
  27. package/dist/next/context.js +2 -2
  28. package/dist/next/context.js.map +1 -1
  29. package/dist/next/pages.d.ts.map +1 -1
  30. package/dist/next/pages.js +8 -6
  31. package/dist/next/pages.js.map +1 -1
  32. package/dist/next/route.d.ts +2 -1
  33. package/dist/next/route.d.ts.map +1 -1
  34. package/dist/next/route.js +6 -4
  35. package/dist/next/route.js.map +1 -1
  36. package/dist/options.d.ts +15 -1
  37. package/dist/options.d.ts.map +1 -1
  38. package/dist/options.js +32 -1
  39. package/dist/options.js.map +1 -1
  40. package/dist/providers/resend.d.ts.map +1 -1
  41. package/dist/providers/resend.js +12 -6
  42. package/dist/providers/resend.js.map +1 -1
  43. package/dist/schema.d.ts +17 -0
  44. package/dist/schema.d.ts.map +1 -1
  45. package/dist/schema.js +2 -0
  46. package/dist/schema.js.map +1 -1
  47. package/dist/server/campaigns.d.ts +30 -4
  48. package/dist/server/campaigns.d.ts.map +1 -1
  49. package/dist/server/campaigns.js +67 -12
  50. package/dist/server/campaigns.js.map +1 -1
  51. package/dist/server/deliveries.d.ts +30 -2
  52. package/dist/server/deliveries.d.ts.map +1 -1
  53. package/dist/server/deliveries.js +58 -23
  54. package/dist/server/deliveries.js.map +1 -1
  55. package/dist/server/index.d.ts +3 -3
  56. package/dist/server/index.d.ts.map +1 -1
  57. package/dist/server/index.js +2 -2
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/send-mail.d.ts +3 -1
  60. package/dist/server/send-mail.d.ts.map +1 -1
  61. package/dist/server/send-mail.js +18 -6
  62. package/dist/server/send-mail.js.map +1 -1
  63. package/dist/server/suppressions.d.ts +8 -6
  64. package/dist/server/suppressions.d.ts.map +1 -1
  65. package/dist/server/suppressions.js +23 -7
  66. package/dist/server/suppressions.js.map +1 -1
  67. package/dist/server/unsubscribe-link.d.ts +23 -0
  68. package/dist/server/unsubscribe-link.d.ts.map +1 -1
  69. package/dist/server/unsubscribe-link.js +30 -1
  70. package/dist/server/unsubscribe-link.js.map +1 -1
  71. package/migrations/0003_add_delivery_provider_status.sql +13 -0
  72. package/module.json +1 -1
  73. package/package.json +6 -2
  74. package/src/cli/run.ts +72 -27
  75. package/src/contract.ts +49 -6
  76. package/src/index.ts +8 -3
  77. package/src/messages/en.ts +2 -0
  78. package/src/messages/pl.ts +2 -0
  79. package/src/next/actions.ts +5 -6
  80. package/src/next/context.ts +2 -2
  81. package/src/next/pages.tsx +10 -7
  82. package/src/next/route.ts +6 -4
  83. package/src/options.ts +36 -2
  84. package/src/providers/resend.ts +11 -7
  85. package/src/schema.ts +2 -0
  86. package/src/server/campaigns.ts +95 -17
  87. package/src/server/deliveries.ts +89 -29
  88. package/src/server/index.ts +8 -0
  89. package/src/server/send-mail.ts +30 -6
  90. package/src/server/suppressions.ts +23 -8
  91. package/src/server/unsubscribe-link.ts +41 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ Newest first. Each version lists what changed for an app that uses `@softure-ai/mailing`. When an app has run a version in
4
+ production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
+ "Definition of done"). Versions before the first one below are described in their GitHub Releases (`mailing@x.y.z`).
6
+
7
+ ## 0.1.7
8
+
9
+ - A failed send keeps the provider's HTTP status: `SendMailResult` failures and `deliverOnce` outcomes carry
10
+ `httpStatus`, and `mailing.deliveries.provider_status` stores it (migration `0003`).
11
+ - New codes `mailing.provider_refused` (401/403: the key or account is refused) and `mailing.quota_exceeded` (429
12
+ that is not a rate limit), with en/pl copy. Before, `resend()` read 401/403 as `rejected` and 429 as `unavailable`.
13
+ Provider adapters may answer the new `refused` and `quota_exceeded` statuses.
14
+ - `deliverOnce` returns `halted` for both: nothing is closed, the claim is released with its attempt given back.
15
+ `sendCampaign` (and `softure-mail campaign`) stops at the first one and reports `halted`; the rest of the list is
16
+ left for the next run.
17
+ - `legacyUnsubscribe: { params, verify }`: unsubscribe links an app sent before adopting the module keep working on
18
+ the page, its action and the one-click route. New `readUnsubscribeLink`; `unsubscribe` takes its result (a bare
19
+ token still works).
20
+ - `filterCampaignRecipient`: campaigns skip recipients the app does not want to mail (e.g. by consent scope), without
21
+ a ledger row; summaries and plans count them as `filtered`.
22
+ - `staleClaimMs` and `uncertainClaimMs` module options. **Changed default:** a claim older than 23 hours is no longer
23
+ taken over by itself (`uncertain`), since the provider may have forgotten its idempotency key; pass
24
+ `retakeUncertain` (`--resend-uncertain`) to send it anyway.
25
+
26
+ ## 0.1.6
27
+
28
+ - Adapters and commands use the configured database handle; `@softure-ai/ui` is a peer dependency.
package/README.md CHANGED
@@ -1,8 +1,5 @@
1
1
  # @softure-ai/mailing
2
2
 
3
- **Status:** wave 2 · transport (EN-1), unsubscribe (EN-2), delivery ledger, campaigns and sender DNS
4
- check (EN-3).
5
-
6
3
  Sends one mail to one recipient through a provider adapter and answers with a typed result, never a
7
4
  throw. Ported from FIRE_TRACKER `src/lib/mail.ts` (a hand-written Resend `fetch`, plain text only),
8
5
  with the sender, reply-to, provider and timeout taken from configuration, an HTML body, and a fake
@@ -13,16 +10,19 @@ FIRE ran deployment-specific scripts.
13
10
 
14
11
  ## 1. What it provides
15
12
 
16
- - `mailing({ from, replyTo, provider, timeoutMs })` for `softure.config.ts`.
13
+ - `mailing({ from, replyTo, provider, timeoutMs, ... })` for `softure.config.ts` (section 3).
17
14
  - `sendMail(context, mail, options)` in `@softure-ai/mailing/server` and `sendMail(mail, options)` in
18
- `@softure-ai/mailing/next` (on the registered config). The result is a core `Result`:
15
+ `@softure-ai/mailing/next` (on the registered config). The result is a core `Result`; a failure the
16
+ provider answered with an HTTP status also carries it as `httpStatus`:
19
17
 
20
18
  | Result | Meaning | What the caller does |
21
19
  | --- | --- | --- |
22
20
  | `ok({ id, provider })` | the provider accepted the mail | keep `id` if it needs one |
23
21
  | `err("mailing.invalid_input")` | refused before anything left the process | fix the input; never retry |
24
- | `err("mailing.rejected")` | the provider refused it (4xx: bad address, bad key, key reused with another body) | do not retry the same request |
25
- | `err("mailing.unavailable")` | provider down (5xx), 408, 429, a busy idempotency key, network, timeout, an answer without an id, no API key; for list mail also no unsubscribe secret or an unreadable suppression list | retry later with the same `idempotencyKey` |
22
+ | `err("mailing.rejected")` | the provider refused this mail (4xx: bad address, key reused with another body) | do not retry the same request |
23
+ | `err("mailing.provider_refused")` | the provider refused the account (401/403: missing, wrong or restricted API key, account not allowed to send) | stop sending: every mail fails the same way; fix the key or account, then retry |
24
+ | `err("mailing.quota_exceeded")` | the account's sending quota is spent (429 that is not a rate limit: Resend's daily or monthly quota) | stop sending; retry when the quota renews |
25
+ | `err("mailing.unavailable")` | provider down (5xx), 408, a rate limit (429 `rate_limit_exceeded`), a busy idempotency key, network, timeout, an answer without an id, no API key; for list mail also no unsubscribe secret or an unreadable suppression list | retry later with the same `idempotencyKey` |
26
26
  | `err("mailing.suppressed")` | a list mail to a recipient who unsubscribed; nothing was sent | skip the recipient; never retry |
27
27
 
28
28
  - **List mail.** `kind: "transactional"` (the default) is mail the recipient needs whatever they
@@ -34,11 +34,13 @@ FIRE ran deployment-specific scripts.
34
34
  - The unsubscribe page (`/unsubscribe`) and the one-click route (`/api/mailing/unsubscribe`) in `/next`,
35
35
  each mounted with one line (section 4).
36
36
  - `@softure-ai/mailing/server`: `isSuppressed(ctx, address)`, `suppressRecipient(ctx, address)` (for
37
- scripts, bounce or complaint handlers), `unsubscribe(ctx, token, source)`,
37
+ scripts, bounce or complaint handlers), `unsubscribe(ctx, link, source)` (a link from
38
+ `readUnsubscribeLink(params, config)`, or a bare token),
38
39
  `liftSuppression(ctx, address)` (a new explicit consent lifts the person's own opt-out),
39
40
  `buildUnsubscribeLinks`, `getRecipientKey`, and the footer and header helpers.
40
41
  - **An `onUnsubscribed` hook**, run in the opt-out's transaction, so the consent ledger can record
41
- the withdrawal (section 10).
42
+ the withdrawal, and **`legacyUnsubscribe`**, which keeps unsubscribe links the app sent before it
43
+ adopted the module working (section 10).
42
44
  - **Sending once.** `deliverOnce(ctx, { scope, mail })` (`/server`, and `deliverOnce({ scope, mail })`
43
45
  in `/next`) sends a mail at most once per scope and recipient through the delivery ledger.
44
46
  - **Campaigns.** `sendCampaign`, `planCampaign` and the content file parser in `/server`; the
@@ -109,20 +111,29 @@ const outcome = await deliverOnce({
109
111
  | Outcome | Meaning |
110
112
  | --- | --- |
111
113
  | `{ status: "sent", id }` | sent now |
112
- | `{ status: "rejected", reason }` | refused now, for good: `mailing.suppressed`, `rejected`, `invalid_input`, or `unavailable` on the last attempt |
114
+ | `{ status: "rejected", reason, httpStatus? }` | refused now, for good: `mailing.suppressed`, `rejected`, `invalid_input`, or `unavailable` on the last attempt |
113
115
  | `{ status: "done", outcome }` | an earlier call closed it (`sent` or `rejected`); nothing sent |
114
116
  | `{ status: "in-flight" }` | another sender holds a fresh claim; nothing sent |
115
- | `{ status: "retry-later" }` | the provider was unavailable; the claim is released, call again later |
117
+ | `{ status: "uncertain" }` | a claim older than `uncertainClaimMs` is open: its send may have gone out; nothing sent (see Retries) |
118
+ | `{ status: "retry-later", httpStatus? }` | the provider was unavailable; the claim is released, call again later |
119
+ | `{ status: "halted", reason, httpStatus? }` | `mailing.provider_refused` or `mailing.quota_exceeded`: the account cannot send. The claim is released with its attempt given back; stop sending and call again once the account can send |
116
120
 
117
121
  - **Scopes** name what the mail is about and are the only registration a lifecycle mail needs:
118
122
  `<module>.<event>:<entity>` (`billing.trial-ending:sub_42`, `waitlist.welcome:<signup id>`).
119
123
  Lowercase letters, digits and `._:-`, at most 128 characters. Campaigns use `campaign:<id>`.
120
124
  - **Idempotency key.** The ledger sends with `<scope>:<recipient key>`; callers do not pass one.
121
125
  - **Retries.** `unavailable` releases the claim (`pending`); the fifth attempt (`maxAttempts`) that
122
- is still unavailable closes the row as `rejected`. A process that dies between the send and the
123
- outcome leaves a claim; after 15 minutes (`staleClaimMs`) another call takes it over and sends
124
- again with the same idempotency key, which the provider folds into the first send while it keeps
125
- the key (Resend: 24 hours). Outcomes are fenced by the attempt number, so a sender that lost its
126
+ is still unavailable closes the row as `rejected`. A halt (`provider_refused`, `quota_exceeded`)
127
+ never closes a row and never uses up an attempt, so a run with a bad key loses no recipient. The
128
+ provider's HTTP status of the last failed answer is kept in `provider_status`.
129
+ - **Interrupted sends.** A process that dies between the send and the outcome leaves a claim; after
130
+ `staleClaimMs` (15 minutes) another call takes it over and sends again with the same idempotency
131
+ key, which the provider folds into the first send while it keeps the key (Resend: 24 hours). A
132
+ claim older than `uncertainClaimMs` (23 hours) is `uncertain`: the provider may have forgotten the
133
+ key, so a retake could mail twice, and it waits for an operator. Check the provider's log, then
134
+ pass `retakeUncertain: true` (`softure-mail campaign --resend-uncertain`) to send it anyway. Both
135
+ windows are module options, and `deliverOnce(ctx, delivery, { staleClaimMs, uncertainClaimMs })`
136
+ overrides them per call. Outcomes are fenced by the attempt number, so a sender that lost its
126
137
  claim cannot overwrite the one that took over.
127
138
  - A malformed scope or kind throws (a bug); a database failure propagates.
128
139
 
@@ -149,13 +160,30 @@ softure-mail campaign launch.md --recipients recipients.txt # sends
149
160
  ```
150
161
 
151
162
  - The recipients file holds one address per line (`#` comments and blank lines skipped).
152
- - The command loads `softure.config.*` like `softure migrate` (or `--config <file>`), opens its own
153
- database connection (`database.url`) and needs `MAILING_UNSUBSCRIBE_SECRET` (campaigns are list
163
+ - The command loads `softure.config.*` like `softure migrate` (or `--config <file>`), opens the
164
+ config's `database.handle` when set, otherwise its own connection on `database.url`, and needs `MAILING_UNSUBSCRIBE_SECRET` (campaigns are list
154
165
  mail). Run `softure migrate` first.
155
166
  - Sends go one at a time with a 500 ms pause (`--pause-ms`; Resend allows 2 requests per second by
156
167
  default). Unsubscribed recipients are rejected without a send and never retried.
168
+ - **Recipient filter.** With `mailing({ filterCampaignRecipient })` every recipient is checked before
169
+ the ledger is touched, e.g. against the consent scope the app stored for the address. A refused
170
+ recipient is counted as `filtered out` and gets no row, so a later run sends to them once they
171
+ qualify:
172
+
173
+ ```ts
174
+ mailing({
175
+ from: "…",
176
+ provider: resend(),
177
+ filterCampaignRecipient: async ({ address }, ctx) => hasNewsletterConsent(ctx.db, address),
178
+ }),
179
+ ```
180
+
181
+ - **A refused key or a spent quota stops the run** at the first such answer: the command prints why
182
+ (with the HTTP status) and exits 1; that recipient and every one after it are left for the next
183
+ run. `sendCampaign` returns the same as `halted: { reason, httpStatus }`.
157
184
  - **Re-runs are safe.** Recipients with an outcome are skipped; the command exits 1 while some have
158
- none yet (`retry later`, `in flight`): run the same command again. `mailing.campaigns` pins the
185
+ none yet (`retry later`, `in flight`): run the same command again. It also exits 1 for `uncertain`
186
+ recipients (an interrupted send older than `uncertainClaimMs`) and names `--resend-uncertain`. `mailing.campaigns` pins the
159
187
  content by hash: other content under the same id is refused, so a changed campaign needs a new id.
160
188
  - From a script (a bundled container, a list built from the database), call
161
189
  `runMailCli({ config, argv })` from `@softure-ai/mailing/cli`, or `sendCampaign(ctx, { campaign,
@@ -203,6 +231,10 @@ mailing({
203
231
  | `provider` | `MailProvider` | required | `resend()`, `fakeMailProvider()` or your own adapter. |
204
232
  | `timeoutMs` | `number` | `10000` | 1000 to 60000. |
205
233
  | `onUnsubscribed` | `(event, ctx) => Promise<void>` | — | Runs on every verified unsubscribe, in its transaction (section 10). |
234
+ | `legacyUnsubscribe` | `{ params, verify }` | — | Verifies unsubscribe links the app sent before it adopted the module (section 10). |
235
+ | `filterCampaignRecipient` | `(recipient, ctx) => Promise<boolean>` | — | Decides per recipient whether a campaign goes to them (section 1, Campaigns). |
236
+ | `staleClaimMs` | `number` | `900000` (15 min) | How long a delivery claim may stay open before another sender takes it over. 1 minute to 23 hours. |
237
+ | `uncertainClaimMs` | `number` | `82800000` (23 h) | How old a claim may get before it waits for an operator (`uncertain`). More than `staleClaimMs`, at most 30 days; keep it under the provider's idempotency window. |
206
238
  | `routes` | `{ unsubscribe?, oneClick? }` | `/unsubscribe`, `/api/mailing/unsubscribe` | Where you mount the page and the route; links are built on `appOrigin` plus these paths. |
207
239
 
208
240
  `resend({ apiKey?, endpoint?, fetch? })`: without `apiKey` it reads `RESEND_API_KEY` on every send,
@@ -216,13 +248,18 @@ const provider: MailProvider = {
216
248
  async send(message, { signal }) {
217
249
  const response = await fetch(URL, { method: "POST", body: toBody(message), signal });
218
250
  if (response.ok) return { status: "sent", id: (await response.json()).MessageID };
219
- return { status: response.status >= 500 || response.status === 429 ? "unavailable" : "rejected", httpStatus: response.status };
251
+ const httpStatus = response.status;
252
+ if (httpStatus === 401 || httpStatus === 403) return { status: "refused", httpStatus };
253
+ if (httpStatus === 429) return { status: "unavailable", httpStatus }; // or "quota_exceeded" for a spent quota
254
+ return { status: httpStatus >= 500 ? "unavailable" : "rejected", httpStatus };
220
255
  },
221
256
  };
222
257
  ```
223
258
 
224
259
  A provider receives a validated `ProviderMessage` (`from`, `to`, `replyTo`, `subject`, `text`, `html`,
225
- `headers`, `idempotencyKey`, nulls for what is absent). A throw reads as `unavailable`.
260
+ `headers`, `idempotencyKey`, nulls for what is absent) and answers `sent` with an id, or `rejected`
261
+ (this mail), `refused` (the account or key), `quota_exceeded` (the account's quota) or `unavailable`
262
+ (try later), each with an optional `httpStatus`. A throw or anything else reads as `unavailable`.
226
263
 
227
264
  ## 4. Mounting
228
265
 
@@ -256,6 +293,7 @@ Schema `mailing`, applied by `softure migrate`:
256
293
  | `0001_create_suppressions.sql` | `mailing.suppressions` | `recipient_key` (primary key, 43-character base64url), `source` (`one-click`, `page`, `operator`), `created_at`. One row per address, the first opt-out kept. |
257
294
  | `0002_create_campaigns_and_deliveries.sql` | `mailing.campaigns` | `id` (kebab-case), `kind` (never `transactional`), `subject`, `content_hash` (sha256 of kind, subject and bodies), `created_at`. |
258
295
  | | `mailing.deliveries` | primary key (`scope`, `recipient_key`), `kind`, `campaign_id` (then `scope` is `campaign:<id>`), `status` (`pending`, `claimed`, `sent`, `rejected`), `attempts`, `claimed_at`, `finished_at`, `provider_message_id` (exactly when sent), `reason` (exactly when rejected). |
296
+ | `0003_add_delivery_provider_status.sql` | `mailing.deliveries` | `provider_status` (the HTTP status of the last failed or released answer, 100 to 599, else null); `attempts` may be 0 on a `pending` row (a halt gave its attempt back). |
259
297
 
260
298
  The health check (`checkMailingTables`) runs `select 1 from mailing.<table> limit 0` for the three
261
299
  tables.
@@ -284,7 +322,7 @@ styled by the app's tokens.
284
322
 
285
323
  ## 9. Copy
286
324
 
287
- `src/messages/{en,pl}.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed}`,
325
+ `src/messages/{en,pl}.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed,provider_refused,quota_exceeded}`,
288
326
  `footer.{text,htmlLead,htmlLink}` (the list-mail footer, in the app's locale) and
289
327
  `unsubscribe.*` (the page). `getMailingErrorMessage(messages, code)` returns the copy for a code;
290
328
  override any text with `mailing({ messages: { en: { footer: { text: "…" } } } })`.
@@ -307,6 +345,29 @@ import { withdrawWaitlistConsents } from "@softure-ai/waitlist/server";
307
345
  mailing({ from: "…", provider: resend(), onUnsubscribed: withdrawWaitlistConsents }),
308
346
  ```
309
347
 
348
+ **`legacyUnsubscribe: { params, verify }`** keeps unsubscribe links working that the app sent
349
+ before it adopted the module, in its own scheme (an HMAC over a sign-up id, say, on its own path).
350
+ `params` are the old link's query names (1 to 8; never `r` or `status`, which the module's links and
351
+ page own; `t` may be shared). A link without `r` that carries every one of them (non-empty, at most
352
+ 512 characters) is a legacy link: the page shows the same button with the values in hidden fields,
353
+ and the action and the one-click POST call `verify(values, ctx)`. It returns the recipient's
354
+ **address** when the link is genuine, else `null`; the module records the opt-out under that
355
+ address's key and runs `onUnsubscribed`, exactly as for its own links. `verify` must check the
356
+ link's signature itself (in constant time): whatever address it returns is unsubscribed. A throw is
357
+ a failure (the page offers a retry, the route answers 500), not an invalid link. Mount the module's
358
+ page and route at the old paths too:
359
+
360
+ ```ts
361
+ mailing({
362
+ from: "…",
363
+ provider: resend(),
364
+ legacyUnsubscribe: { params: ["u", "t"], verify: ({ u, t }, ctx) => verifyOldLink(ctx.db, u, t) },
365
+ }),
366
+
367
+ // app/old-unsubscribe/page.tsx
368
+ export { UnsubscribePage as default } from "@softure-ai/mailing/next";
369
+ ```
370
+
310
371
  **`liftSuppression(ctx, address)`** is the other direction: a module that has just recorded a new
311
372
  explicit consent (the waitlist's sign-up) calls it in that consent's transaction. It deletes the
312
373
  row only when its source is `page` or `one-click`; an `operator` row stays. It returns whether it
@@ -333,8 +394,9 @@ personal data. So the module neither exports nor deletes per user (`privacy: { e
333
394
  - `sendMail` does not retry: the caller retries `unavailable` with the same `idempotencyKey` (Resend
334
395
  keeps keys for 24 hours), or uses `deliverOnce`, which keeps the state between runs.
335
396
  - Exactly-once ends where the provider's idempotency window ends: a sender that dies after the
336
- provider accepted a mail and before the outcome was written, with the row taken over more than
337
- 24 hours later, sends that mail twice. No delivery, bounce or complaint webhooks yet.
397
+ provider accepted a mail and before the outcome was written leaves an `uncertain` claim once
398
+ `uncertainClaimMs` passes; retaking it (`retakeUncertain`) may send that mail twice. No delivery,
399
+ bounce or complaint webhooks yet.
338
400
  - Campaigns have no personalisation, scheduling or markdown: the body is sent as written.
339
401
  - Suppression is global per address: no per-list preferences. A person comes back in through a
340
402
  module's explicit consent (`liftSuppression`, e.g. a new waitlist sign-up); an operator row is
package/dist/cli/run.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface RunMailCliOptions {
14
14
  /** Relative paths resolve against it. Default: `process.cwd()`. */
15
15
  readonly cwd?: string;
16
16
  readonly output?: CliOutput;
17
- /** Opens the campaign's database connection. Default: `createDatabase(url, { max: 1 })`. */
17
+ /** Opens the campaign's database connection. Default: the config's `database.handle`, else `createDatabase(url, { max: 1 })`. */
18
18
  readonly openDatabase?: (url: string) => Promise<DatabaseHandle>;
19
19
  readonly resolveTxt?: ResolveTxt;
20
20
  readonly sleep?: (ms: number) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAWA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrE,OAAO,EAAkD,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnG,OAAO,EAAiF,KAAK,GAAG,EAAE,MAAM,+BAA+B,CAAC;AAExI,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,wGAAwG;IACxG,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,4FAA4F;IAC5F,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,UAAU,QAkBa,CAAC;AAYrC,yGAAyG;AACzG,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB5E"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAWA,OAAO,EAAe,KAAK,aAAa,EAA8B,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAA6C,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIhG,OAAO,EAAkD,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnG,OAAO,EAAiF,KAAK,GAAG,EAAE,MAAM,+BAA+B,CAAC;AAExI,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,wGAAwG;IACxG,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,iIAAiI;IACjI,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,UAAU,QAqBa,CAAC;AAmBrC,yGAAyG;AACzG,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB5E"}
package/dist/cli/run.js CHANGED
@@ -10,7 +10,7 @@ import { readFile } from "node:fs/promises";
10
10
  import { dirname, resolve } from "node:path";
11
11
  import { parseArgs } from "node:util";
12
12
  import { systemClock } from "@softure-ai/core";
13
- import { createDatabase } from "@softure-ai/db";
13
+ import { openCommandDatabase } from "@softure-ai/db";
14
14
  import { parseCampaignFile, parseRecipientList } from "../server/campaign-file.js";
15
15
  import { planCampaign, sendCampaign } from "../server/campaigns.js";
16
16
  import { checkSenderDns, getSenderDomain } from "../server/dns.js";
@@ -22,14 +22,17 @@ export const EXIT_USAGE = 2;
22
22
  /** Pause between sends: Resend's default limit is two requests per second. */
23
23
  export const DEFAULT_PAUSE_MS = 500;
24
24
  export const MAIL_USAGE = `Usage:
25
- softure-mail campaign <content-file> --recipients <file> [--dry-run] [--pause-ms <ms>]
25
+ softure-mail campaign <content-file> --recipients <file> [--dry-run] [--pause-ms <ms>] [--resend-uncertain]
26
26
  softure-mail dns [--domain <domain>] [--dkim-selector <name>]... [--spf-host <host>]...
27
27
 
28
28
  campaign Sends the campaign in <content-file> (frontmatter id, kind, subject, optional html,
29
29
  then the text body) to every address in the recipients file (one per line), at most
30
- once each. Re-running sends only to recipients without an outcome yet.
31
- --dry-run count what would be sent and change nothing
32
- --pause-ms <ms> pause after every send (default ${String(DEFAULT_PAUSE_MS)})
30
+ once each. Re-running sends only to recipients without an outcome yet. A refused
31
+ API key or a spent quota stops the run; the rest go out on the next one.
32
+ --dry-run count what would be sent and change nothing
33
+ --pause-ms <ms> pause after every send (default ${String(DEFAULT_PAUSE_MS)})
34
+ --resend-uncertain also send to recipients whose send was interrupted long ago
35
+ (it may have gone out: they may get the mail twice)
33
36
 
34
37
  dns Checks SPF, DKIM and DMARC for the sender domain (default: the domain of "from").
35
38
  --domain <domain> check this domain instead
@@ -82,7 +85,7 @@ function parseCommand(argv) {
82
85
  if (values.help === true)
83
86
  return { kind: "help" };
84
87
  if (name === "dns") {
85
- const misplaced = ["recipients", "dry-run", "pause-ms"].filter((option) => values[option] !== undefined);
88
+ const misplaced = ["recipients", "dry-run", "pause-ms", "resend-uncertain"].filter((option) => values[option] !== undefined);
86
89
  if (misplaced.length > 0)
87
90
  return `dns does not take --${misplaced.join(", --")}`;
88
91
  if (positionals.length > 0)
@@ -102,7 +105,7 @@ function parseCommand(argv) {
102
105
  const pauseMs = values["pause-ms"] === undefined ? DEFAULT_PAUSE_MS : Number(values["pause-ms"]);
103
106
  if (!Number.isInteger(pauseMs) || pauseMs < 0 || pauseMs > 60_000)
104
107
  return `--pause-ms expects whole milliseconds from 0 to 60000, got "${values["pause-ms"] ?? ""}"`;
105
- return { kind: "campaign", contentFile, recipientsFile: values.recipients, dryRun: values["dry-run"] === true, pauseMs };
108
+ return { kind: "campaign", contentFile, recipientsFile: values.recipients, dryRun: values["dry-run"] === true, pauseMs, resendUncertain: values["resend-uncertain"] === true };
106
109
  }
107
110
  function parseCommandArgs(args) {
108
111
  return parseArgs({
@@ -111,6 +114,7 @@ function parseCommandArgs(args) {
111
114
  recipients: { type: "string" },
112
115
  "dry-run": { type: "boolean" },
113
116
  "pause-ms": { type: "string" },
117
+ "resend-uncertain": { type: "boolean" },
114
118
  domain: { type: "string" },
115
119
  "dkim-selector": { type: "string", multiple: true },
116
120
  "spf-host": { type: "string", multiple: true },
@@ -171,17 +175,19 @@ async function runCampaign(command, options, output) {
171
175
  output.error("softure-mail campaign: the config has no database; set database.url in softure.config");
172
176
  return EXIT_FAILED;
173
177
  }
174
- let handle;
178
+ let opened;
175
179
  try {
176
- handle = await (options.openDatabase ?? openDatabase)(config.database.url);
180
+ opened = await openDatabase(config.database, options.openDatabase);
177
181
  }
178
182
  catch (error) {
179
183
  output.error(`softure-mail campaign: ${describeError(error)}`);
180
184
  return EXIT_FAILED;
181
185
  }
182
- const ctx = { db: handle.db, clock: systemClock, config };
186
+ const ctx = { db: opened.handle.db, clock: systemClock, config };
183
187
  try {
184
- return command.dryRun ? await reportPlan(ctx, loaded, output) : await reportSend(ctx, loaded, { pauseMs: command.pauseMs, sleep: options.sleep }, output);
188
+ return command.dryRun
189
+ ? await reportPlan(ctx, loaded, command.resendUncertain, output)
190
+ : await reportSend(ctx, loaded, { pauseMs: command.pauseMs, sleep: options.sleep, retakeUncertain: command.resendUncertain }, output);
185
191
  }
186
192
  catch (error) {
187
193
  // Driver errors: the message only, never a stack, an address or the database URL.
@@ -189,7 +195,7 @@ async function runCampaign(command, options, output) {
189
195
  return EXIT_FAILED;
190
196
  }
191
197
  finally {
192
- await handle.close();
198
+ await opened.close();
193
199
  }
194
200
  }
195
201
  async function loadCampaign(contentPath, recipientsPath) {
@@ -214,10 +220,17 @@ async function loadCampaign(contentPath, recipientsPath) {
214
220
  return `cannot read the recipients file ${recipientsPath}`;
215
221
  return { campaign: { ...content, html }, recipients: parseRecipientList(list) };
216
222
  }
217
- async function reportPlan(ctx, loaded, output) {
218
- const plan = await planCampaign(ctx, loaded);
223
+ async function reportPlan(ctx, loaded, retakeUncertain, output) {
224
+ const plan = await planCampaign(ctx, loaded, { retakeUncertain });
219
225
  output.log(`campaign ${loaded.campaign.id} (${loaded.campaign.kind}), dry run: nothing sent or written`);
220
- output.log(`recipients ${String(plan.recipients)}, already done ${String(plan.done)}, unsubscribed ${String(plan.suppressed)}, to send ${String(plan.toSend)}`);
226
+ output.log([
227
+ `recipients ${String(plan.recipients)}`,
228
+ `already done ${String(plan.done)}`,
229
+ `unsubscribed ${String(plan.suppressed)}`,
230
+ `filtered out ${String(plan.filtered)}`,
231
+ `uncertain ${String(plan.uncertain)}`,
232
+ `to send ${String(plan.toSend)}`,
233
+ ].join(", "));
221
234
  if (plan.contentChanged) {
222
235
  output.error(`campaign ${loaded.campaign.id} was sent with other content; give this content a new id`);
223
236
  return EXIT_FAILED;
@@ -229,6 +242,7 @@ async function reportSend(ctx, loaded, pace, output) {
229
242
  const total = loaded.recipients.length;
230
243
  const result = await sendCampaign(ctx, loaded, {
231
244
  pauseMs: pace.pauseMs,
245
+ retakeUncertain: pace.retakeUncertain,
232
246
  ...(pace.sleep === undefined ? {} : { sleep: pace.sleep }),
233
247
  onDelivery: () => {
234
248
  handled += 1;
@@ -240,14 +254,29 @@ async function reportSend(ctx, loaded, pace, output) {
240
254
  output.error(`campaign ${loaded.campaign.id} was sent with other content; give this content a new id`);
241
255
  return EXIT_FAILED;
242
256
  }
243
- output.log(`campaign ${loaded.campaign.id} (${loaded.campaign.kind}): ${formatSummary(result.value)}`);
244
- const open = result.value.inFlight + result.value.retryLater;
245
- if (open > 0) {
257
+ const summary = result.value;
258
+ output.log(`campaign ${loaded.campaign.id} (${loaded.campaign.kind}): ${formatSummary(summary)}`);
259
+ let code = EXIT_OK;
260
+ if (summary.halted !== null) {
261
+ const status = summary.halted.httpStatus === undefined ? "" : ` (HTTP ${String(summary.halted.httpStatus)})`;
262
+ output.error(`stopped: ${HALT_EXPLANATIONS[summary.halted.reason]}${status}; the remaining recipients were not touched, run the same command again once it is fixed`);
263
+ code = EXIT_FAILED;
264
+ }
265
+ const open = summary.inFlight + summary.retryLater;
266
+ if (open > 0 && summary.halted === null) {
246
267
  output.error(`${String(open)} recipient(s) have no outcome yet; run the same command again later`);
247
- return EXIT_FAILED;
268
+ code = EXIT_FAILED;
248
269
  }
249
- return EXIT_OK;
270
+ if (summary.uncertain > 0) {
271
+ output.error(`${String(summary.uncertain)} recipient(s) are uncertain: a send was interrupted long ago and may have gone out; check the provider's log, then run again with --resend-uncertain to send them anyway`);
272
+ code = EXIT_FAILED;
273
+ }
274
+ return code;
250
275
  }
276
+ const HALT_EXPLANATIONS = {
277
+ "mailing.provider_refused": "the provider refused the account (check the API key and the account)",
278
+ "mailing.quota_exceeded": "the account's sending quota is spent",
279
+ };
251
280
  function formatSummary(summary) {
252
281
  const rejected = Object.entries(summary.rejected).filter(([, count]) => count > 0);
253
282
  const rejectedTotal = rejected.reduce((sum, [, count]) => sum + count, 0);
@@ -259,10 +288,15 @@ function formatSummary(summary) {
259
288
  `already done ${String(summary.done)}`,
260
289
  `in flight ${String(summary.inFlight)}`,
261
290
  `retry later ${String(summary.retryLater)}`,
291
+ `filtered out ${String(summary.filtered)}`,
292
+ `uncertain ${String(summary.uncertain)}`,
262
293
  ].join(", ");
263
294
  }
264
- function openDatabase(url) {
265
- return createDatabase(url, { max: 1 });
295
+ async function openDatabase(database, open) {
296
+ if (open === undefined)
297
+ return openCommandDatabase(database, { max: 1 });
298
+ const handle = await open(database.url);
299
+ return { handle, close: handle.close };
266
300
  }
267
301
  async function readText(path) {
268
302
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,4FAA4F;AAC5F,iCAAiC;AACjC,EAAE;AACF,uBAAuB;AACvB,0DAA0D;AAC1D,4CAA4C;AAC5C,kFAAkF;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,WAAW,EAAkD,MAAM,kBAAkB,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAA6C,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAwB,MAAM,4BAA4B,CAAC;AACzG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAwB,MAAM,wBAAwB,CAAC;AAE1F,OAAO,EAAE,cAAc,EAAE,eAAe,EAAkC,MAAM,kBAAkB,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,sBAAsB,EAAY,MAAM,+BAA+B,CAAC;AAsBxI,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AACzB,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAE5B,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAEpC,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;yDAS+B,MAAM,CAAC,gBAAgB,CAAC;;;;;;;;;;;;oCAY7C,CAAC;AAErC,MAAM,aAAa,GAAc;IAC/B,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;CACrC,CAAC;AAcF,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAA0B;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;IAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,CAAC,KAAK,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1C,KAAK,UAAU;YACb,OAAO,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAuB;IAC3C,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,sCAAsC,CAAC;IACtE,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC/C,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,oBAAoB,IAAI,wBAAwB,CAAC;IAEnG,IAAI,MAA2C,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IACvC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAElD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,SAAS,GAAI,CAAC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,kBAAkB,CAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC;QACxI,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,uBAAuB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACjF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,2BAA2B,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACvF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;IACtH,CAAC;IAED,MAAM,SAAS,GAAI,CAAC,QAAQ,EAAE,eAAe,EAAE,UAAU,CAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC;IACtH,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,4BAA4B,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACtF,MAAM,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,GAAG,WAAW,CAAC;IAC5C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,+BAA+B,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,8CAA8C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC9F,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,oCAAoC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IACjG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,MAAM;QAAE,OAAO,+DAA+D,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC;IACrK,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE,CAAC;AACjL,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAuB;IAC/C,OAAO,SAAS,CAAC;QACf,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,OAAO,EAAE;YACP,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC9B,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YAC9B,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC9B,kBAAkB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YACvC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC1B,eAAe,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YACnD,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC9C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;SAC1B;QACD,MAAM,EAAE,IAAI;QACZ,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,OAA0C,EAAE,OAA0B,EAAE,MAAiB;IAC7G,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,KAAK,CAAC,wGAAwG,CAAC,CAAC;YACvH,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,MAAM,GAAG,eAAe,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE;QAC1C,GAAG,CAAC,OAAO,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;QACxF,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzE,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;KAChF,CAAC,CAAC;IACH,MAAM,MAAM,GAAyB;QACnC,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC;QACnB,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;KACxB,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,iBAAiB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACrJ,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;;YAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC5E,MAAM,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,kDAAkD,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,8EAA8E,CAAC,CAAC;IAChL,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;AAC9C,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,OAA+C,EAAE,OAA0B,EAAE,MAAiB;IACvH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC9D,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAC3G,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAC;QACjD,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,sBAAsB,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC3F,MAAM,CAAC,KAAK,CAAC,uDAAuD,sBAAsB,cAAc,MAAM,CAAC,6BAA6B,CAAC,8CAA8C,CAAC,CAAC;QAC7L,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,uFAAuF,CAAC,CAAC;QACtG,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,MAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,0BAA0B,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,GAAG,GAAoB,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IAClF,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM;YACnB,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE,MAAM,CAAC;YAChE,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1I,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kFAAkF;QAClF,MAAM,CAAC,KAAK,CAAC,0BAA0B,aAAa,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC1F,OAAO,WAAW,CAAC;IACrB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAOD,KAAK,UAAU,YAAY,CAAC,WAAmB,EAAE,cAAsB;IACrE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,gCAAgC,WAAW,EAAE,CAAC;IAC1E,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,GAAG,WAAW,0BAA0B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9F,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;IAC9C,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,CAAC;QACzD,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,6BAA6B,QAAQ,EAAE,CAAC;QAClE,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,iBAAiB,QAAQ,WAAW,CAAC;IACtE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,mCAAmC,cAAc,EAAE,CAAC;IAC9E,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;AAClF,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAoB,EAAE,MAAsB,EAAE,eAAwB,EAAE,MAAiB;IACjH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,IAAI,qCAAqC,CAAC,CAAC;IACzG,MAAM,CAAC,GAAG,CACR;QACE,cAAc,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;QACvC,gBAAgB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACnC,gBAAgB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;QACzC,gBAAgB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;QACvC,aAAa,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;QACrC,WAAW,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;KACjC,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,EAAE,0DAA0D,CAAC,CAAC;QACvG,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,GAAoB,EACpB,MAAsB,EACtB,IAAkI,EAClI,MAAiB;IAEjB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE;QAC7C,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1D,UAAU,EAAE,GAAG,EAAE;YACf,OAAO,IAAI,CAAC,CAAC;YACb,IAAI,OAAO,GAAG,GAAG,KAAK,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,EAAE,0DAA0D,CAAC,CAAC;QACvG,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7B,MAAM,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClG,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAC7G,MAAM,CAAC,KAAK,CAAC,YAAY,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,0FAA0F,CAAC,CAAC;QACtK,IAAI,GAAG,WAAW,CAAC;IACrB,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC;IACnD,IAAI,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QACnG,IAAI,GAAG,WAAW,CAAC;IACrB,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,KAAK,CACV,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,0KAA0K,CACvM,CAAC;QACF,IAAI,GAAG,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,iBAAiB,GAA+C;IACpE,0BAA0B,EAAE,sEAAsE;IAClG,wBAAwB,EAAE,sCAAsC;CACjE,CAAC;AAEF,SAAS,aAAa,CAAC,OAAwB;IAC7C,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACnF,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACrJ,OAAO;QACL,cAAc,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QAC1C,QAAQ,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QAC9B,YAAY,MAAM,CAAC,aAAa,CAAC,GAAG,OAAO,EAAE;QAC7C,gBAAgB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACtC,aAAa,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QACvC,eAAe,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QAC3C,gBAAgB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QAC1C,aAAa,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;KACzC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,QAA+B,EAAE,IAA4D;IACvH,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,iFAAiF;QACjF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;AAC5E,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { ModuleContext, Result } from "@softure-ai/core";
1
+ import type { Err, ModuleContext, Ok } from "@softure-ai/core";
2
2
  import type { Queryable } from "@softure-ai/db";
3
3
  export type MailingErrorCode =
4
4
  /** The mail or its options were refused before anything left the process. Fix the input; do not retry. */
@@ -8,7 +8,16 @@ export type MailingErrorCode =
8
8
  /** The provider could not be reached or did not answer in time. Retry later, with the same idempotency key. */
9
9
  | "mailing.unavailable"
10
10
  /** A list mail to a recipient who unsubscribed. Nothing was sent; do not retry. */
11
- | "mailing.suppressed";
11
+ | "mailing.suppressed"
12
+ /**
13
+ * The provider refuses the sender, not the mail: a wrong or revoked API key, a suspended or restricted account
14
+ * (HTTP 401/403). Every other mail would fail the same way: stop sending, fix the account, then retry.
15
+ */
16
+ | "mailing.provider_refused"
17
+ /** The account's sending quota is spent (HTTP 429 that is not a rate limit). Stop sending; retry when it renews. */
18
+ | "mailing.quota_exceeded";
19
+ /** The codes that are about the sending account, not the mail: a run of sends stops at the first one. */
20
+ export declare const HALTING_ERROR_CODES: ReadonlySet<MailingErrorCode>;
12
21
  /** Why an unsubscribe link was refused: missing, malformed, or not signed by a current secret. */
13
22
  export type UnsubscribeErrorCode = "mailing.invalid_link";
14
23
  /** The kind of every mail that is not list mail: sent whatever the recipient unsubscribed from. */
@@ -27,6 +36,32 @@ export interface UnsubscribeEvent {
27
36
  * and the suppression list never disagree. Set in `mailing({ onUnsubscribed })`.
28
37
  */
29
38
  export type OnUnsubscribedHook = (event: UnsubscribeEvent, ctx: ModuleContext<Queryable>) => Promise<void>;
39
+ /** What `filterCampaignRecipient` receives for each recipient of a campaign. */
40
+ export interface CampaignRecipient {
41
+ readonly address: string;
42
+ /** The recipient key of the address (`getRecipientKey`). */
43
+ readonly recipientKey: string;
44
+ readonly campaign: {
45
+ readonly id: string;
46
+ readonly kind: string;
47
+ };
48
+ }
49
+ /**
50
+ * Decides whether a campaign goes to a recipient, e.g. by the consent scope the app stored for the address. A
51
+ * recipient it refuses is skipped before the ledger is touched, so a later re-run sends to them once they qualify.
52
+ * A throw stops the campaign, like a database failure. Set in `mailing({ filterCampaignRecipient })`.
53
+ */
54
+ export type CampaignRecipientFilter = (recipient: CampaignRecipient, ctx: ModuleContext<Queryable>) => Promise<boolean>;
55
+ /**
56
+ * Unsubscribe links an app sent before it adopted the module, in its own scheme. `params` are the query names of
57
+ * such a link; `verify` receives their values (all present once, each at most 512 characters) and returns the
58
+ * recipient's address when the link is genuine, else `null`. It must check the link's signature itself: whatever
59
+ * address it returns is unsubscribed. A throw reads as a failure (the person may try again), not as a bad link.
60
+ */
61
+ export interface LegacyUnsubscribe {
62
+ readonly params: readonly string[];
63
+ readonly verify: (values: Readonly<Record<string, string>>, ctx: ModuleContext<Queryable>) => Promise<string | null>;
64
+ }
30
65
  /** One mail to one recipient. A mail to a list is a loop over recipients, never a list in `to`. */
31
66
  export interface OutgoingMail {
32
67
  /** Exactly one address: `ada@example.com`. */
@@ -65,7 +100,11 @@ export interface SentMail {
65
100
  /** The provider's name, e.g. `resend`. */
66
101
  readonly provider: string;
67
102
  }
68
- export type SendMailResult = Result<SentMail, MailingErrorCode>;
103
+ /** A failed send: the code, and the provider's HTTP status when it answered with one. */
104
+ export type SendMailFailure = Err<MailingErrorCode> & {
105
+ readonly httpStatus?: number;
106
+ };
107
+ export type SendMailResult = Ok<SentMail> | SendMailFailure;
69
108
  /** What a provider receives: a validated mail with the sender and reply-to from the configuration. */
70
109
  export interface ProviderMessage {
71
110
  readonly from: string;
@@ -77,17 +116,18 @@ export interface ProviderMessage {
77
116
  readonly headers: Readonly<Record<string, string>>;
78
117
  readonly idempotencyKey: string | null;
79
118
  }
80
- /** What a provider answers. `httpStatus` is logged; the mail never is. */
119
+ /**
120
+ * What a provider answers. `httpStatus` is logged and returned with the failure; the mail never is. `refused` is the
121
+ * provider refusing the sender (key, account), `quota_exceeded` a spent sending quota; `rejected` is about this mail.
122
+ */
81
123
  export type ProviderOutcome = {
82
124
  readonly status: "sent";
83
125
  readonly id: string;
84
126
  } | {
85
- readonly status: "rejected";
86
- readonly httpStatus?: number;
87
- } | {
88
- readonly status: "unavailable";
127
+ readonly status: ProviderFailureStatus;
89
128
  readonly httpStatus?: number;
90
129
  };
130
+ export type ProviderFailureStatus = "rejected" | "unavailable" | "refused" | "quota_exceeded";
91
131
  /**
92
132
  * A mail service adapter. `send` should resolve with an outcome for every failure it knows; a
93
133
  * throw reads as `unavailable`. It receives a signal that aborts at the configured timeout.
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
package/dist/contract.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** The codes that are about the sending account, not the mail: a run of sends stops at the first one. */
2
+ export const HALTING_ERROR_CODES = new Set(["mailing.provider_refused", "mailing.quota_exceeded"]);
1
3
  /** The kind of every mail that is not list mail: sent whatever the recipient unsubscribed from. */
2
4
  export const TRANSACTIONAL_KIND = "transactional";
3
5
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAkBA,mGAAmG;AACnG,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAsBA,yGAAyG;AACzG,MAAM,CAAC,MAAM,mBAAmB,GAAkC,IAAI,GAAG,CAAC,CAAC,0BAA0B,EAAE,wBAAwB,CAAC,CAAC,CAAC;AAKlI,mGAAmG;AACnG,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC"}