@softure-ai/mailing 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +85 -6
  3. package/dist/contract.d.ts +13 -1
  4. package/dist/contract.d.ts.map +1 -1
  5. package/dist/contract.js.map +1 -1
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/jobs/delivery-job.d.ts +72 -0
  11. package/dist/jobs/delivery-job.d.ts.map +1 -0
  12. package/dist/jobs/delivery-job.js +85 -0
  13. package/dist/jobs/delivery-job.js.map +1 -0
  14. package/dist/jobs/index.d.ts +2 -0
  15. package/dist/jobs/index.d.ts.map +1 -0
  16. package/dist/jobs/index.js +4 -0
  17. package/dist/jobs/index.js.map +1 -0
  18. package/dist/messages/en.d.ts +2 -0
  19. package/dist/messages/en.d.ts.map +1 -1
  20. package/dist/messages/en.js +2 -0
  21. package/dist/messages/en.js.map +1 -1
  22. package/dist/messages/index.d.ts +4 -2
  23. package/dist/messages/index.d.ts.map +1 -1
  24. package/dist/messages/index.js.map +1 -1
  25. package/dist/next/actions.d.ts.map +1 -1
  26. package/dist/next/actions.js +8 -26
  27. package/dist/next/actions.js.map +1 -1
  28. package/dist/next/page-submit.d.ts +12 -0
  29. package/dist/next/page-submit.d.ts.map +1 -0
  30. package/dist/next/page-submit.js +30 -0
  31. package/dist/next/page-submit.js.map +1 -0
  32. package/dist/next/pages.d.ts.map +1 -1
  33. package/dist/next/pages.js +8 -5
  34. package/dist/next/pages.js.map +1 -1
  35. package/dist/next/route.d.ts +4 -1
  36. package/dist/next/route.d.ts.map +1 -1
  37. package/dist/next/route.js +58 -2
  38. package/dist/next/route.js.map +1 -1
  39. package/dist/next/strip-link-query.d.ts +11 -0
  40. package/dist/next/strip-link-query.d.ts.map +1 -0
  41. package/dist/next/strip-link-query.js +25 -0
  42. package/dist/next/strip-link-query.js.map +1 -0
  43. package/dist/providers/resend.d.ts +5 -0
  44. package/dist/providers/resend.d.ts.map +1 -1
  45. package/dist/providers/resend.js +23 -3
  46. package/dist/providers/resend.js.map +1 -1
  47. package/dist/server/deliveries.d.ts +6 -1
  48. package/dist/server/deliveries.d.ts.map +1 -1
  49. package/dist/server/deliveries.js +3 -2
  50. package/dist/server/deliveries.js.map +1 -1
  51. package/dist/server/send-mail.d.ts +2 -1
  52. package/dist/server/send-mail.d.ts.map +1 -1
  53. package/dist/server/send-mail.js +17 -5
  54. package/dist/server/send-mail.js.map +1 -1
  55. package/module.json +2 -2
  56. package/package.json +12 -1
  57. package/src/contract.ts +14 -3
  58. package/src/index.ts +4 -3
  59. package/src/jobs/delivery-job.ts +129 -0
  60. package/src/jobs/index.ts +12 -0
  61. package/src/messages/en.ts +2 -0
  62. package/src/messages/index.ts +2 -2
  63. package/src/next/actions.ts +8 -24
  64. package/src/next/page-submit.ts +35 -0
  65. package/src/next/pages.tsx +9 -5
  66. package/src/next/route.ts +59 -4
  67. package/src/next/strip-link-query.tsx +30 -0
  68. package/src/providers/resend.ts +22 -3
  69. package/src/server/deliveries.ts +9 -5
  70. package/src/server/send-mail.ts +18 -5
package/CHANGELOG.md CHANGED
@@ -4,6 +4,54 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
4
4
  production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
5
  "Definition of done"). Versions before the first one below are described in their GitHub Releases (`mailing@x.y.z`).
6
6
 
7
+ ## 0.2.2
8
+
9
+ - The unsubscribe page no longer posts to the URL that carries the signed token (#556). Its form sends the link in
10
+ hidden fields to the one-click route's bare path (`routes.oneClick`); `postUnsubscribeRoute` records a POST
11
+ whose URL has no link and whose URL-encoded body (at most 8 KiB) has one as a `page` opt-out and answers with a
12
+ 303 to the page's outcome (`/unsubscribe?status=done`, `invalid` or `failed`), as the server action did. A mail
13
+ client's RFC 8058 one-click POST (link in the URL) is unchanged. With JavaScript the page also strips the link's
14
+ parameters from the address bar after load (`history.replaceState`); without it the page still works.
15
+ - `unsubscribeAction` is unchanged and still exported, but the package page no longer uses it: a server action
16
+ posts to the page's URL, query included.
17
+ - Migrate an app:
18
+ 1. Using the package page and route (`UnsubscribePage` or `createUnsubscribePage`, `postUnsubscribeRoute` on
19
+ `routes.oneClick`): nothing to change. The route must stay mounted and public, as before.
20
+ 2. An app that kept its own unsubscribe page only to move the token out of the URL (hidden fields plus
21
+ `history.replaceState`): delete that page and mount the package page instead
22
+ (`export { UnsubscribePage as default } from "@softure-ai/mailing/next"`, or `createUnsubscribePage({ Layout })`
23
+ for the app's frame).
24
+ 3. An app with its own form posting to `unsubscribeAction`: point the form at the route instead
25
+ (`<form action="/api/mailing/unsubscribe" method="post">` with the link's `r` and `t`, or the legacy
26
+ parameters, as hidden fields) to keep the token out of the POST URL.
27
+
28
+ ## 0.2.1
29
+
30
+ - New entry `@softure-ai/mailing/jobs` (#492), with `@softure-ai/jobs` as an optional peer dependency:
31
+ `enqueueDelivery(ctx, { queue, scope, payload })` enqueues a delivery in the caller's transaction (the scope is
32
+ the job's dedup key); `defineDeliveryJobHandler({ payload, buildMail })` sends it through `deliverOnce` from the
33
+ jobs worker and retries `retry-later` and `halted` (after the provider's `Retry-After`, else the queue's backoff);
34
+ rejected mail fails at once; `getDeliveryJobResult` is the same mapping for an app's own handler. The payload holds
35
+ the app's ids; the handler builds the mail when it runs. New job codes `mailing.delivery_in_flight` and
36
+ `mailing.delivery_uncertain` with copy in `errors.mailing`. README, "Deliveries through jobs".
37
+ - `resend()` reads `Retry-After` (seconds or an HTTP date); `ProviderOutcome` failures, `SendMailFailure` and
38
+ `deliverOnce`'s `retry-later` and `halted` outcomes carry it as the optional `retryAfterSeconds`. New export
39
+ `readRetryAfter(header, nowMs)` for a provider adapter of the app's own. A strict equality on these results
40
+ (`toStrictEqual`) sees the new field when the provider sends the header.
41
+ - `module.json → dependsOn` names `jobs` as optional (`"*?"`); an app with both modules lists `jobs()` anywhere in
42
+ `modules`.
43
+ - Migrate an app that retries `retry-later` by hand (a cron that calls `deliverOnce` again, a "try again" kept for
44
+ the user):
45
+ 1. `npm install @softure-ai/jobs`, add `jobs({ queues: { "<queue>": { maxAttempts } } })` and run the worker
46
+ (jobs README, "Running the worker"); run the migrations.
47
+ 2. Register `defineDeliveryJobHandler({ payload, buildMail })` for the queue; move the code that built the mail
48
+ into `buildMail`, reading the app's data by the ids in the payload.
49
+ 3. Replace the `deliverOnce` call in the request with `enqueueDelivery({ ...ctx, db: tx }, { queue, scope,
50
+ payload })` inside the transaction of the write it belongs to, keeping the same `scope` (the ledger rows already
51
+ sent stay sent).
52
+ 4. Drop the retry cron and the per-app attempt counting; `mailing({ maxAttempts })` now only applies to direct
53
+ `deliverOnce` calls.
54
+
7
55
  ## 0.2.0
8
56
 
9
57
  - **Breaking (#446):** the built-in `pl` dictionary is gone; English is the only built-in copy and the fallback for
package/README.md CHANGED
@@ -44,6 +44,8 @@ can be imported into it, and `softure-mail` sends campaigns from a content file.
44
44
  in `/next`) sends a mail at most once per scope and recipient through the delivery ledger;
45
45
  `runDeliveries` does it for a whole list of recipients, with a limit, a pause and a dry run;
46
46
  `importDeliveries` (and `softure-mail import`) seeds it with the app's earlier history.
47
+ - **Deliveries through jobs.** `enqueueDelivery` and `defineDeliveryJobHandler` (`/jobs`) let an app enqueue a
48
+ mail with its own write and have the `@softure-ai/jobs` worker send it, retrying while the provider is down.
47
49
  - **Preview and test send.** `previewMail(config, mail)` returns exactly what `sendMail` hands the
48
50
  provider; `softure-mail test` sends a mail to the configured `testAddress` and nowhere else.
49
51
  - **A SQL surface** for the app's own queries: `mailing.recipient_key(address)`,
@@ -128,8 +130,8 @@ const outcome = await deliverOnce({
128
130
  | `{ status: "done", outcome }` | an earlier call closed it (`sent` or `rejected`); nothing sent |
129
131
  | `{ status: "in-flight" }` | another sender holds a fresh claim; nothing sent |
130
132
  | `{ status: "uncertain" }` | a claim older than `uncertainClaimMs` is open: its send may have gone out; nothing sent (see Retries) |
131
- | `{ status: "retry-later", httpStatus? }` | the provider was unavailable; the claim is released, call again later |
132
- | `{ 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 |
133
+ | `{ status: "retry-later", httpStatus?, retryAfterSeconds? }` | the provider was unavailable; the claim is released, call again later (after `retryAfterSeconds` when the provider named a wait), or let a job do it (Deliveries through jobs) |
134
+ | `{ status: "halted", reason, httpStatus?, retryAfterSeconds? }` | `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 |
133
135
 
134
136
  - **Scopes** name what the mail is about and are the only registration a lifecycle mail needs:
135
137
  `<module>.<event>:<entity>` (`billing.trial-ending:sub_42`, `waitlist.welcome:<signup id>`).
@@ -153,6 +155,69 @@ const outcome = await deliverOnce({
153
155
  claim cannot overwrite the one that took over.
154
156
  - A malformed scope or kind throws (a bug); a database failure propagates.
155
157
 
158
+ ### Deliveries through jobs: `@softure-ai/mailing/jobs`
159
+
160
+ A mail sent from a request path (a welcome mail, a notice) should not be lost when the provider is down, nor slow
161
+ the request. With `@softure-ai/jobs` (an optional peer dependency) the app enqueues the delivery in the same
162
+ transaction as its own write, and the jobs worker sends it through `deliverOnce`, retrying until it is sent. The
163
+ job runs at least once; the ledger and its idempotency key make the mail go out once.
164
+
165
+ ```ts
166
+ // softure.config.ts: a queue for the mail, next to the mailing module
167
+ jobs({ queues: { "mail.welcome": { maxAttempts: 8, backoff: { baseSeconds: 30, maxSeconds: 3600 } } } }),
168
+ mailing({ from: "Example <hello@mail.example.com>", provider: resend() }),
169
+
170
+ // src/jobs/handlers.ts: the handler builds the mail from the app's data when it runs
171
+ import { defineDeliveryJobHandler } from "@softure-ai/mailing/jobs";
172
+
173
+ export const handlers: JobHandlers = {
174
+ "mail.welcome": defineDeliveryJobHandler({
175
+ payload: z.object({ userId: z.string() }),
176
+ buildMail: async ({ userId }, { db }) => {
177
+ const user = await findUser(db, userId);
178
+ return user === null ? null : { to: user.email, subject: "Welcome", text: renderWelcome(user) };
179
+ },
180
+ }),
181
+ };
182
+
183
+ // the request: enqueue with the write, answer at once
184
+ import { enqueueDelivery } from "@softure-ai/mailing/jobs";
185
+
186
+ await db.transaction(async (tx) => {
187
+ const [user] = await tx.insert(users).values(input).returning();
188
+ await enqueueDelivery({ db: tx, clock, config }, { queue: "mail.welcome", scope: `app.welcome:${user.id}`, payload: { userId: user.id } });
189
+ });
190
+ ```
191
+
192
+ - **The payload holds ids, never the mail.** `enqueueDelivery` stores `{ scope, payload }`; no address, name or
193
+ token reaches `jobs.jobs`. `buildMail` reads the app's data on every attempt, before the ledger is read, so it
194
+ must have no side effects and return the same mail each time: the provider refuses a different body under the
195
+ same idempotency key (`mailing.rejected`). A link with a one-time token must be reproducible per request (derive
196
+ the token from the request's row, or store it encrypted), never minted per attempt. `buildMail` returning `null`
197
+ ends the job as done with nothing sent (the account is gone).
198
+ - **The scope is the ledger key and the job's dedup key.** One scope per mail the app means to send once:
199
+ `auth.password-reset:<request id>`, `app.welcome:<user id>`. While a job for it is pending or running, a second
200
+ `enqueueDelivery` returns that job (`isDuplicate: true`). `runAt`, `delaySeconds` and `maxAttempts` pass through to
201
+ `enqueueJob`; a malformed scope throws.
202
+ - **Outcomes** (`getDeliveryJobResult`, also exported for an app that writes its own handler):
203
+
204
+ | `deliverOnce` | Job |
205
+ | --- | --- |
206
+ | `sent`, `done` | succeeded |
207
+ | `rejected` (`invalid_input`, `suppressed`, `rejected`) | failed at once with the reason |
208
+ | `retry-later` | retried after the provider's `Retry-After`, else after the queue's backoff (`mailing.unavailable`) |
209
+ | `halted` | the same, with `mailing.quota_exceeded` or `mailing.provider_refused`: the job waits while the account is fixed |
210
+ | `in-flight` | retried once the other sender's claim could be stale (`staleClaimMs`), as `mailing.delivery_in_flight` |
211
+ | `uncertain` | failed as `mailing.delivery_uncertain`: the operator checks the provider's log first |
212
+
213
+ - **Attempts are the job's.** The handler calls `deliverOnce` with `maxAttempts: null`, so the queue's
214
+ `maxAttempts` decides when a delivery stops; the ledger never closes it as rejected for being unavailable. A job
215
+ that runs out of attempts leaves the ledger row `pending`: `retryJob(ctx, id)` (`@softure-ai/jobs/server`) runs it
216
+ again, and the ledger still sends at most once. `deliverOptions` passes the claim windows
217
+ (`staleClaimMs`, `uncertainClaimMs`, `retakeUncertain`) to `deliverOnce`.
218
+ - Without jobs, `deliverOnce` keeps its own attempt counting (`maxAttempts`, Retries above): nothing changes for an
219
+ app that does not import `/jobs`. Campaigns and `runDeliveries` stay operator commands that stop at an outage.
220
+
156
221
  ### Many recipients at once: `runDeliveries`
157
222
 
158
223
  Lifecycle mail (a trial that ends, a renewal) goes to every recipient a query finds, each at most
@@ -423,7 +488,8 @@ It cannot see whether the provider signs with the published key. The function ta
423
488
  npm install @softure-ai/mailing @softure-ai/core @softure-ai/db @softure-ai/ui drizzle-orm
424
489
  ```
425
490
 
426
- Peer dependencies: `drizzle-orm`, and `next` and `react` for the `/next` adapter. The module needs a
491
+ Peer dependencies: `drizzle-orm`, `next` and `react` for the `/next` adapter, and `@softure-ai/jobs` (optional)
492
+ for the `/jobs` entry (`npm install @softure-ai/jobs`). The module needs a
427
493
  database (its suppression list). The `/testing` entry imports `node:fs`.
428
494
 
429
495
  ## 3. Configuration
@@ -517,6 +583,16 @@ function AppShell({ title, lead, children }: UnsubscribeLayoutProps) {
517
583
  export default createUnsubscribePage({ Layout: AppShell, classNames: { submit: "w-full" } });
518
584
  ```
519
585
 
586
+ **The token stays out of request lines.** The page's form posts the link in hidden fields to the
587
+ route's bare path (`routes.oneClick`), which records it as a `page` opt-out and answers with a 303
588
+ to the page's outcome (`?status=done`, `invalid` or `failed`). The route tells the two callers
589
+ apart by where the link is: in the URL, a mail client's one-click POST; only in a URL-encoded body
590
+ (at most 8 KiB), the page's form. With JavaScript the page also takes the link out of the address
591
+ bar once loaded (`history.replaceState`), so it does not stay in the tab's history. The page sets
592
+ `<meta name="referrer" content="same-origin">`, so no other site sees it in a `Referer`.
593
+ `unsubscribeAction` stays exported for an app's own form, but a server action posts to the page's
594
+ URL, query included: prefer posting to the route as the package page does.
595
+
520
596
  Both must stay public: keep them out of the auth guard's `protect` list. The route has no per-IP
521
597
  rate limit on purpose: mail providers post from a few shared addresses that act for millions of
522
598
  people, so a bucket would refuse real unsubscribes; the signature is checked before any database
@@ -585,13 +661,15 @@ None.
585
661
 
586
662
  ## 8. Appearance
587
663
 
588
- The unsubscribe page is a `Card` from `@softure-ai/ui` with a plain form (no client JavaScript),
664
+ The unsubscribe page is a `Card` from `@softure-ai/ui` with a plain form (it works without client
665
+ JavaScript; one small client component only cleans the address bar),
589
666
  styled by the app's tokens. `createUnsubscribePage({ classNames, unstyled, Layout })` adds the app's
590
667
  classes or its own layout (section 4).
591
668
 
592
669
  ## 9. Copy
593
670
 
594
- `src/messages/en.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed,provider_refused,quota_exceeded}`,
671
+ `src/messages/en.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed,provider_refused,quota_exceeded,delivery_in_flight,delivery_uncertain}`
672
+ (the last two are job codes, for an admin screen that shows a delivery job's state),
595
673
  `footer.{text,htmlLead,htmlLink}` (the list-mail footer, in the app's locale) and
596
674
  `unsubscribe.*` (the page). `getMailingErrorMessage(messages, code)` returns the copy for a code;
597
675
  override any text with `mailing({ messages: { en: { footer: { text: "…" } } } })`.
@@ -688,7 +766,8 @@ personal data. So the module neither exports nor deletes per user (`privacy: { e
688
766
 
689
767
  - One recipient per call; no cc, bcc or attachments.
690
768
  - `sendMail` does not retry: the caller retries `unavailable` with the same `idempotencyKey` (Resend
691
- keeps keys for 24 hours), or uses `deliverOnce`, which keeps the state between runs.
769
+ keeps keys for 24 hours), uses `deliverOnce`, which keeps the state between runs, or enqueues it with
770
+ `enqueueDelivery`, which retries it from a job.
692
771
  - Exactly-once ends where the provider's idempotency window ends: a sender that dies after the
693
772
  provider accepted a mail and before the outcome was written leaves an `uncertain` claim once
694
773
  `uncertainClaimMs` passes; retaking it (`retakeUncertain`) may send that mail twice. No delivery,
@@ -16,6 +16,12 @@ export type MailingErrorCode =
16
16
  | "mailing.provider_refused"
17
17
  /** The account's sending quota is spent (HTTP 429 that is not a rate limit). Stop sending; retry when it renews. */
18
18
  | "mailing.quota_exceeded";
19
+ /** The codes a delivery job fails or retries with that are not a provider's answer (`@softure-ai/mailing/jobs`). */
20
+ export type DeliveryJobErrorCode =
21
+ /** Another sender holds a fresh claim on the delivery; the job looks again once that claim could be stale. */
22
+ "mailing.delivery_in_flight"
23
+ /** A claim older than the provider keeps its idempotency key is open: the mail may have gone out. Not retried. */
24
+ | "mailing.delivery_uncertain";
19
25
  /** The codes that are about the sending account, not the mail: a run of sends stops at the first one. */
20
26
  export declare const HALTING_ERROR_CODES: ReadonlySet<MailingErrorCode>;
21
27
  /** Why an unsubscribe link was refused: missing, malformed, or not signed by a current secret. */
@@ -137,9 +143,13 @@ export interface SentMail {
137
143
  /** The provider's name, e.g. `resend`. */
138
144
  readonly provider: string;
139
145
  }
140
- /** A failed send: the code, and the provider's HTTP status when it answered with one. */
146
+ /**
147
+ * A failed send: the code, the provider's HTTP status when it answered with one, and how many seconds it asked to
148
+ * wait before the next try when it said so (`Retry-After`).
149
+ */
141
150
  export type SendMailFailure = Err<MailingErrorCode> & {
142
151
  readonly httpStatus?: number;
152
+ readonly retryAfterSeconds?: number;
143
153
  };
144
154
  export type SendMailResult = Ok<SentMail> | SendMailFailure;
145
155
  /** What a provider receives: a validated mail with the sender from the configuration and the mail's or the configured reply-to. */
@@ -156,6 +166,7 @@ export interface ProviderMessage {
156
166
  /**
157
167
  * What a provider answers. `httpStatus` is logged and returned with the failure; the mail never is. `refused` is the
158
168
  * provider refusing the sender (key, account), `quota_exceeded` a spent sending quota; `rejected` is about this mail.
169
+ * `retryAfterSeconds` is the wait the provider asked for (`Retry-After`), a whole number of seconds, 0 or more.
159
170
  */
160
171
  export type ProviderOutcome = {
161
172
  readonly status: "sent";
@@ -163,6 +174,7 @@ export type ProviderOutcome = {
163
174
  } | {
164
175
  readonly status: ProviderFailureStatus;
165
176
  readonly httpStatus?: number;
177
+ readonly retryAfterSeconds?: number;
166
178
  };
167
179
  export type ProviderFailureStatus = "rejected" | "unavailable" | "refused" | "quota_exceeded";
168
180
  /**
@@ -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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"}
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AA6BA,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"}
package/dist/index.d.ts CHANGED
@@ -15,6 +15,8 @@ export declare const mailing: import("@softure-ai/core").ModuleFactory<{
15
15
  suppressed: string;
16
16
  provider_refused: string;
17
17
  quota_exceeded: string;
18
+ delivery_in_flight: string;
19
+ delivery_uncertain: string;
18
20
  };
19
21
  };
20
22
  footer: {
@@ -62,9 +64,9 @@ export declare const mailing: import("@softure-ai/core").ModuleFactory<{
62
64
  }, import("zod/v4/core").$strict>>;
63
65
  export { isMailbox, isMailKind, isSingleAddress, LIST_MAIL_HEADERS, MAX_ADDRESS_LENGTH, MAX_IDEMPOTENCY_KEY_LENGTH, MAX_MAIL_KIND_LENGTH, MAX_SUBJECT_LENGTH, RESERVED_HEADERS, } from "./address.js";
64
66
  export { HALTING_ERROR_CODES, TRANSACTIONAL_KIND } from "./contract.js";
65
- export type { CampaignRecipient, CampaignRecipientFilter, CampaignRecipientSource, LegacyUnsubscribe, LegacyUnsubscribeParams, MailingErrorCode, MailProvider, OnUnsubscribedHook, OutgoingMail, ProviderFailureStatus, ProviderMessage, ProviderOutcome, SendMailFailure, SendMailOptions, SendMailResult, SentMail, SuppressionSource, UnsubscribeErrorCode, UnsubscribeEvent, VerifiedUnsubscribeLink, } from "./contract.js";
67
+ export type { CampaignRecipient, CampaignRecipientFilter, CampaignRecipientSource, DeliveryJobErrorCode, LegacyUnsubscribe, LegacyUnsubscribeParams, MailingErrorCode, MailProvider, OnUnsubscribedHook, OutgoingMail, ProviderFailureStatus, ProviderMessage, ProviderOutcome, SendMailFailure, SendMailOptions, SendMailResult, SentMail, SuppressionSource, UnsubscribeErrorCode, UnsubscribeEvent, VerifiedUnsubscribeLink, } from "./contract.js";
66
68
  export { getMailingErrorMessage, mailingMessages, type MailingMessages } from "./messages/index.js";
67
69
  export { DEFAULT_STALE_CLAIM_MS, DEFAULT_TIMEOUT_MS, DEFAULT_UNCERTAIN_CLAIM_MS, type MailingOptions, type MailingOptionsInput } from "./options.js";
68
- export { resend, RESEND_API_KEY_ENV, RESEND_ENDPOINT, type ResendOptions } from "./providers/resend.js";
70
+ export { readRetryAfter, resend, RESEND_API_KEY_ENV, RESEND_ENDPOINT, type ResendOptions } from "./providers/resend.js";
69
71
  export { campaigns, deliveries, mailingSchema, suppressions, type DeliveryStatus } from "./schema.js";
70
72
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAqClB,CAAC;AAEH,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACrJ,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,YAAY,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAqClB,CAAC;AAEH,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACrJ,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACxH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -15,8 +15,8 @@ export const MODULE_ID = "mailing";
15
15
  export const mailing = defineModule({
16
16
  manifest: {
17
17
  id: MODULE_ID,
18
- version: "0.2.0",
19
- dependsOn: {},
18
+ version: "0.2.2",
19
+ dependsOn: { jobs: "*?" },
20
20
  dbSchema: "mailing",
21
21
  tables: ["suppressions", "campaigns", "deliveries"],
22
22
  env: [
@@ -54,6 +54,6 @@ export { isMailbox, isMailKind, isSingleAddress, LIST_MAIL_HEADERS, MAX_ADDRESS_
54
54
  export { HALTING_ERROR_CODES, TRANSACTIONAL_KIND } from "./contract.js";
55
55
  export { getMailingErrorMessage, mailingMessages } from "./messages/index.js";
56
56
  export { DEFAULT_STALE_CLAIM_MS, DEFAULT_TIMEOUT_MS, DEFAULT_UNCERTAIN_CLAIM_MS } from "./options.js";
57
- export { resend, RESEND_API_KEY_ENV, RESEND_ENDPOINT } from "./providers/resend.js";
57
+ export { readRetryAfter, resend, RESEND_API_KEY_ENV, RESEND_ENDPOINT } from "./providers/resend.js";
58
58
  export { campaigns, deliveries, mailingSchema, suppressions } from "./schema.js";
59
59
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,mGAAmG;AACnG,mGAAmG;AACnG,mGAAmG;AACnG,gDAAgD;AAChD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,YAAY,CAAC;IAClC,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,EAAE,YAAY,CAAC;QACnD,GAAG,EAAE;YACH;gBACE,IAAI,EAAE,gBAAgB;gBACtB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,+GAA+G;aAC7H;YACD;gBACE,IAAI,EAAE,4BAA4B;gBAClC,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,6MAA6M;aAC3N;YACD;gBACE,IAAI,EAAE,qCAAqC;gBAC3C,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,yGAAyG;aACvH;SACF;QACD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,0BAA0B,EAAE;QAC7E,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,iBAAiB,EAAE;YAC7E,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,sCAAsC,EAAE,MAAM,EAAE,sBAAsB,EAAE;YACvG,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,sCAAsC,EAAE,MAAM,EAAE,qBAAqB,EAAE;SACvG;QACD,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;KAC5C;IACD,QAAQ,EAAE,eAAe;IACzB,OAAO,EAAE,oBAAoB;IAC7B,UAAU,EAAE,EAAE,GAAG,EAAE,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE;IAC5E,MAAM,EAAE,kBAAkB;CAC3B,CAAC,CAAC;AAEH,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAuBxE,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAwB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,0BAA0B,EAAiD,MAAM,cAAc,CAAC;AACrJ,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,mGAAmG;AACnG,mGAAmG;AACnG,mGAAmG;AACnG,gDAAgD;AAChD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAC;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,YAAY,CAAC;IAClC,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;QACzB,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,CAAC,cAAc,EAAE,WAAW,EAAE,YAAY,CAAC;QACnD,GAAG,EAAE;YACH;gBACE,IAAI,EAAE,gBAAgB;gBACtB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,+GAA+G;aAC7H;YACD;gBACE,IAAI,EAAE,4BAA4B;gBAClC,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,6MAA6M;aAC3N;YACD;gBACE,IAAI,EAAE,qCAAqC;gBAC3C,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,yGAAyG;aACvH;SACF;QACD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,0BAA0B,EAAE;QAC7E,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,iBAAiB,EAAE;YAC7E,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,sCAAsC,EAAE,MAAM,EAAE,sBAAsB,EAAE;YACvG,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,sCAAsC,EAAE,MAAM,EAAE,qBAAqB,EAAE;SACvG;QACD,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;KAC5C;IACD,QAAQ,EAAE,eAAe;IACzB,OAAO,EAAE,oBAAoB;IAC7B,UAAU,EAAE,EAAE,GAAG,EAAE,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE;IAC5E,MAAM,EAAE,kBAAkB;CAC3B,CAAC,CAAC;AAEH,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAwBxE,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAwB,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,0BAA0B,EAAiD,MAAM,cAAc,CAAC;AACrJ,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,kBAAkB,EAAE,eAAe,EAAsB,MAAM,uBAAuB,CAAC;AACxH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAuB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { type Ok } from "@softure-ai/core";
2
+ import { type JobFailure, type JobHandler, type JobHandlerContext } from "@softure-ai/jobs";
3
+ import { type EnqueuedJob, type JobsContext } from "@softure-ai/jobs/server";
4
+ import { z } from "zod";
5
+ import type { OutgoingMail } from "../contract.js";
6
+ export type { DeliveryJobErrorCode } from "../contract.js";
7
+ /** What a delivery job returns: done, or a failure the jobs worker retries or fails. */
8
+ export type DeliveryJobResult = Ok<undefined> | JobFailure;
9
+ import { type DeliverOptions, type DeliveryOutcome } from "../server/deliveries.js";
10
+ export interface EnqueueDeliveryInput<TPayload> {
11
+ /** A queue defined in `jobs({ queues })` whose handler is a `defineDeliveryJobHandler`. */
12
+ readonly queue: string;
13
+ /**
14
+ * The ledger scope of the mail (`deliverOnce`'s `scope`), e.g. `auth.password-reset:<request id>`. Also the job's
15
+ * dedup key: while a job for this scope is pending or running, a second enqueue returns it.
16
+ */
17
+ readonly scope: string;
18
+ /** The app's ids the handler builds the mail from. JSON; no addresses, names or tokens. */
19
+ readonly payload: TPayload;
20
+ /** When the job becomes due; default now. Not together with `delaySeconds`. */
21
+ readonly runAt?: Date;
22
+ readonly delaySeconds?: number;
23
+ /** Overrides the queue's `maxAttempts` for this job. */
24
+ readonly maxAttempts?: number;
25
+ }
26
+ /**
27
+ * Enqueues a delivery. Pass the caller's transaction as `ctx.db`, so the job exists exactly when the caller's own
28
+ * change commits. Throws for a malformed scope, and like `enqueueJob` (an unknown queue, a database failure).
29
+ */
30
+ export declare function enqueueDelivery<TPayload>(ctx: JobsContext, input: EnqueueDeliveryInput<TPayload>): Promise<EnqueuedJob>;
31
+ /** What `buildMail` receives besides the app's payload: the job's context and the delivery's scope. */
32
+ export interface DeliveryJobContext extends JobHandlerContext {
33
+ readonly scope: string;
34
+ }
35
+ export interface DeliveryJobHandlerOptions<TSchema extends z.ZodType> {
36
+ /** The schema of the app's payload (the `payload` given to `enqueueDelivery`). */
37
+ readonly payload: TSchema;
38
+ /**
39
+ * Builds the mail from the app's data. Called on every attempt, before the ledger is read, so it must have no side
40
+ * effects and return the same mail each time: a different body under the same idempotency key is refused by the
41
+ * provider. Return `null` when there is nothing to send any more (the account is gone); the job then succeeds.
42
+ */
43
+ readonly buildMail: (payload: z.output<TSchema>, context: DeliveryJobContext) => Promise<OutgoingMail | null>;
44
+ /** The ledger's claim windows; `maxAttempts` is the job's, so the ledger does not count attempts here. */
45
+ readonly deliverOptions?: Omit<DeliverOptions, "maxAttempts">;
46
+ }
47
+ /**
48
+ * A jobs handler that sends one delivery through the ledger (`deliverOnce`) and turns its outcome into the job's
49
+ * (`getDeliveryJobResult`). Register it for the queue `enqueueDelivery` writes to:
50
+ *
51
+ * export const handlers: JobHandlers = {
52
+ * "mail.welcome": defineDeliveryJobHandler({ payload: z.object({ userId: z.string() }), buildMail: buildWelcomeMail }),
53
+ * };
54
+ */
55
+ export declare function defineDeliveryJobHandler<TSchema extends z.ZodType>(options: DeliveryJobHandlerOptions<TSchema>): JobHandler<{
56
+ scope: string;
57
+ payload: z.output<TSchema>;
58
+ }>;
59
+ /**
60
+ * Turns a `deliverOnce` outcome into a job result, for an app that writes its own handler:
61
+ *
62
+ * - `sent`, `done`: done.
63
+ * - `rejected` (invalid, suppressed, refused mail): failed at once with its reason.
64
+ * - `retry-later`, `halted` (the provider is down, the account refused or out of quota): retried after the provider's
65
+ * `Retry-After`, else after the queue's backoff.
66
+ * - `in-flight`: retried once the other sender's claim could be stale (`staleClaimMs`).
67
+ * - `uncertain`: failed with `mailing.delivery_uncertain`; the operator decides whether to send it again.
68
+ */
69
+ export declare function getDeliveryJobResult(outcome: DeliveryOutcome, options: {
70
+ readonly staleClaimMs: number;
71
+ }): DeliveryJobResult;
72
+ //# sourceMappingURL=delivery-job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-job.d.ts","sourceRoot":"","sources":["../../src/jobs/delivery-job.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAA4C,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtI,OAAO,EAAc,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,UAAU,CAAC;AAC3D,OAAO,EAAgC,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAGlH,MAAM,WAAW,oBAAoB,CAAC,QAAQ;IAC5C,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,oBAAoB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAY7H;AAED,uGAAuG;AACvG,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO;IAClE,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC9G,0GAA0G;IAC1G,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;CAC/D;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,EAChE,OAAO,EAAE,yBAAyB,CAAC,OAAO,CAAC,GAC1C,UAAU,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAgB3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CAiB5H"}
@@ -0,0 +1,85 @@
1
+ // Deliveries through @softure-ai/jobs: the app enqueues a delivery in its own transaction and the jobs worker sends
2
+ // it, retrying while the provider is unavailable. The job runs at least once; the delivery ledger and its idempotency
3
+ // key make the mail go out once. The job payload holds the ledger scope and the app's ids, never the mail: the
4
+ // handler builds the mail from the app's data when it runs.
5
+ import { err, ok } from "@softure-ai/core";
6
+ import { defineJobHandler, failJob, retryJobLater } from "@softure-ai/jobs";
7
+ import { enqueueJob } from "@softure-ai/jobs/server";
8
+ import { z } from "zod";
9
+ import { deliverOnce, isDeliveryScope } from "../server/deliveries.js";
10
+ import { getMailingOptions } from "../server/options.js";
11
+ /**
12
+ * Enqueues a delivery. Pass the caller's transaction as `ctx.db`, so the job exists exactly when the caller's own
13
+ * change commits. Throws for a malformed scope, and like `enqueueJob` (an unknown queue, a database failure).
14
+ */
15
+ export async function enqueueDelivery(ctx, input) {
16
+ if (!isDeliveryScope(input.scope)) {
17
+ throw new Error(`@softure-ai/mailing: enqueueDelivery scope "${input.scope}" must be lowercase letters, digits and ._:- (at most 128 characters)`);
18
+ }
19
+ return enqueueJob(ctx, {
20
+ queue: input.queue,
21
+ payload: { scope: input.scope, payload: input.payload },
22
+ dedupKey: input.scope,
23
+ ...(input.runAt === undefined ? {} : { runAt: input.runAt }),
24
+ ...(input.delaySeconds === undefined ? {} : { delaySeconds: input.delaySeconds }),
25
+ ...(input.maxAttempts === undefined ? {} : { maxAttempts: input.maxAttempts }),
26
+ });
27
+ }
28
+ /**
29
+ * A jobs handler that sends one delivery through the ledger (`deliverOnce`) and turns its outcome into the job's
30
+ * (`getDeliveryJobResult`). Register it for the queue `enqueueDelivery` writes to:
31
+ *
32
+ * export const handlers: JobHandlers = {
33
+ * "mail.welcome": defineDeliveryJobHandler({ payload: z.object({ userId: z.string() }), buildMail: buildWelcomeMail }),
34
+ * };
35
+ */
36
+ export function defineDeliveryJobHandler(options) {
37
+ // The cast keeps the payload's own output type, which zod's object inference widens for a generic schema.
38
+ const schema = z.object({
39
+ scope: z.string().refine(isDeliveryScope),
40
+ payload: options.payload,
41
+ });
42
+ return defineJobHandler({
43
+ payload: schema,
44
+ run: async ({ scope, payload }, context) => {
45
+ const mail = await options.buildMail(payload, { ...context, scope });
46
+ if (mail === null)
47
+ return ok();
48
+ const outcome = await deliverOnce(context, { scope, mail }, { ...options.deliverOptions, maxAttempts: null });
49
+ const staleClaimMs = options.deliverOptions?.staleClaimMs ?? getMailingOptions(context.config).staleClaimMs;
50
+ return getDeliveryJobResult(outcome, { staleClaimMs });
51
+ },
52
+ });
53
+ }
54
+ /**
55
+ * Turns a `deliverOnce` outcome into a job result, for an app that writes its own handler:
56
+ *
57
+ * - `sent`, `done`: done.
58
+ * - `rejected` (invalid, suppressed, refused mail): failed at once with its reason.
59
+ * - `retry-later`, `halted` (the provider is down, the account refused or out of quota): retried after the provider's
60
+ * `Retry-After`, else after the queue's backoff.
61
+ * - `in-flight`: retried once the other sender's claim could be stale (`staleClaimMs`).
62
+ * - `uncertain`: failed with `mailing.delivery_uncertain`; the operator decides whether to send it again.
63
+ */
64
+ export function getDeliveryJobResult(outcome, options) {
65
+ switch (outcome.status) {
66
+ case "sent":
67
+ case "done":
68
+ return ok();
69
+ case "rejected":
70
+ return failJob(outcome.reason);
71
+ case "retry-later":
72
+ return retryAfter("mailing.unavailable", outcome.retryAfterSeconds);
73
+ case "halted":
74
+ return retryAfter(outcome.reason, outcome.retryAfterSeconds);
75
+ case "in-flight":
76
+ // One second past the window: a claim exactly `staleClaimMs` old is still fresh.
77
+ return retryJobLater("mailing.delivery_in_flight", Math.ceil(options.staleClaimMs / 1000) + 1);
78
+ case "uncertain":
79
+ return failJob("mailing.delivery_uncertain");
80
+ }
81
+ }
82
+ function retryAfter(code, seconds) {
83
+ return seconds === undefined ? err(code) : retryJobLater(code, seconds);
84
+ }
85
+ //# sourceMappingURL=delivery-job.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-job.js","sourceRoot":"","sources":["../../src/jobs/delivery-job.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { defineDeliveryJobHandler, enqueueDelivery, getDeliveryJobResult, type DeliveryJobContext, type DeliveryJobErrorCode, type DeliveryJobHandlerOptions, type DeliveryJobResult, type EnqueueDeliveryInput, } from "./delivery-job.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/jobs/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,4 @@
1
+ // The jobs adapter of @softure-ai/mailing: deliveries sent by the @softure-ai/jobs worker, with retries (an optional
2
+ // peer dependency; only apps that import this entry need it).
3
+ export { defineDeliveryJobHandler, enqueueDelivery, getDeliveryJobResult, } from "./delivery-job.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/jobs/index.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,8DAA8D;AAC9D,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,oBAAoB,GAMrB,MAAM,mBAAmB,CAAC"}
@@ -7,6 +7,8 @@ export declare const en: {
7
7
  suppressed: string;
8
8
  provider_refused: string;
9
9
  quota_exceeded: string;
10
+ delivery_in_flight: string;
11
+ delivery_uncertain: string;
10
12
  };
11
13
  };
12
14
  footer: {
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Bd,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Bd,CAAC"}
@@ -7,6 +7,8 @@ export const en = {
7
7
  suppressed: "This address unsubscribed from these emails, so nothing was sent.",
8
8
  provider_refused: "The mail service refused our account, so nothing was sent. We need to fix it on our side.",
9
9
  quota_exceeded: "We reached our sending limit for now. Try again later.",
10
+ delivery_in_flight: "This message is being sent right now.",
11
+ delivery_uncertain: "This message may already have been sent. Check before sending it again.",
10
12
  },
11
13
  },
12
14
  footer: {
@@ -1 +1 @@
1
- {"version":3,"file":"en.js","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE;QACN,OAAO,EAAE;YACP,aAAa,EAAE,+DAA+D;YAC9E,QAAQ,EAAE,wCAAwC;YAClD,WAAW,EAAE,2DAA2D;YACxE,UAAU,EAAE,mEAAmE;YAC/E,gBAAgB,EAAE,2FAA2F;YAC7G,cAAc,EAAE,wDAAwD;SACzE;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE,4CAA4C;QAClD,QAAQ,EAAE,0BAA0B;QACpC,QAAQ,EAAE,aAAa;KACxB;IACD,WAAW,EAAE;QACX,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,8GAA8G;QACpH,MAAM,EAAE,gBAAgB;QACxB,SAAS,EAAE,sBAAsB;QACjC,QAAQ,EAAE,6CAA6C;QACvD,YAAY,EAAE,yBAAyB;QACvC,WAAW,EAAE,gHAAgH;QAC7H,MAAM,EAAE,gDAAgD;KACzD;CACF,CAAC"}
1
+ {"version":3,"file":"en.js","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE;QACN,OAAO,EAAE;YACP,aAAa,EAAE,+DAA+D;YAC9E,QAAQ,EAAE,wCAAwC;YAClD,WAAW,EAAE,2DAA2D;YACxE,UAAU,EAAE,mEAAmE;YAC/E,gBAAgB,EAAE,2FAA2F;YAC7G,cAAc,EAAE,wDAAwD;YACxE,kBAAkB,EAAE,uCAAuC;YAC3D,kBAAkB,EAAE,yEAAyE;SAC9F;KACF;IACD,MAAM,EAAE;QACN,IAAI,EAAE,4CAA4C;QAClD,QAAQ,EAAE,0BAA0B;QACpC,QAAQ,EAAE,aAAa;KACxB;IACD,WAAW,EAAE;QACX,KAAK,EAAE,aAAa;QACpB,IAAI,EAAE,8GAA8G;QACpH,MAAM,EAAE,gBAAgB;QACxB,SAAS,EAAE,sBAAsB;QACjC,QAAQ,EAAE,6CAA6C;QACvD,YAAY,EAAE,yBAAyB;QACvC,WAAW,EAAE,gHAAgH;QAC7H,MAAM,EAAE,gDAAgD;KACzD;CACF,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { MailingErrorCode } from "../contract.js";
1
+ import type { DeliveryJobErrorCode, MailingErrorCode } from "../contract.js";
2
2
  import { en } from "./en.js";
3
3
  /** The built-in English dictionary; an app passes partial overrides or its own translation per locale. */
4
4
  export declare const mailingMessages: {
@@ -11,6 +11,8 @@ export declare const mailingMessages: {
11
11
  suppressed: string;
12
12
  provider_refused: string;
13
13
  quota_exceeded: string;
14
+ delivery_in_flight: string;
15
+ delivery_uncertain: string;
14
16
  };
15
17
  };
16
18
  footer: {
@@ -32,5 +34,5 @@ export declare const mailingMessages: {
32
34
  };
33
35
  export type MailingMessages = typeof en;
34
36
  /** The copy for a mailing error code; a code the dictionary does not know reads as `unavailable`. */
35
- export declare function getMailingErrorMessage(messages: MailingMessages, code: MailingErrorCode): string;
37
+ export declare function getMailingErrorMessage(messages: MailingMessages, code: MailingErrorCode | DeliveryJobErrorCode): string;
36
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,0GAA0G;AAC1G,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAS,CAAC;AAEtC,MAAM,MAAM,eAAe,GAAG,OAAO,EAAE,CAAC;AAExC,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAIhG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,0GAA0G;AAC1G,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAS,CAAC;AAEtC,MAAM,MAAM,eAAe,GAAG,OAAO,EAAE,CAAC;AAExC,qGAAqG;AACrG,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,MAAM,CAIvH"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,0GAA0G;AAC1G,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,EAAE,EAAE,CAAC;AAItC,qGAAqG;AACrG,MAAM,UAAU,sBAAsB,CAAC,QAAyB,EAAE,IAAsB;IACtF,MAAM,KAAK,GAAqC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;IACxE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;AACvG,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,0GAA0G;AAC1G,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,EAAE,EAAE,CAAC;AAItC,qGAAqG;AACrG,MAAM,UAAU,sBAAsB,CAAC,QAAyB,EAAE,IAA6C;IAC7G,MAAM,KAAK,GAAqC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;IACxE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;AACvG,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/next/actions.ts"],"names":[],"mappings":"AAeA,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzE"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/next/actions.ts"],"names":[],"mappings":"AAaA,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE"}