@softure-ai/mailing 0.1.14 → 0.2.1

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 (63) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +76 -7
  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 +5 -30
  23. package/dist/messages/index.d.ts.map +1 -1
  24. package/dist/messages/index.js +2 -3
  25. package/dist/messages/index.js.map +1 -1
  26. package/dist/next/messages.d.ts +1 -1
  27. package/dist/next/messages.d.ts.map +1 -1
  28. package/dist/next/messages.js +2 -1
  29. package/dist/next/messages.js.map +1 -1
  30. package/dist/providers/resend.d.ts +5 -0
  31. package/dist/providers/resend.d.ts.map +1 -1
  32. package/dist/providers/resend.js +23 -3
  33. package/dist/providers/resend.js.map +1 -1
  34. package/dist/server/deliveries.d.ts +6 -1
  35. package/dist/server/deliveries.d.ts.map +1 -1
  36. package/dist/server/deliveries.js +3 -2
  37. package/dist/server/deliveries.js.map +1 -1
  38. package/dist/server/dns.d.ts +2 -1
  39. package/dist/server/dns.d.ts.map +1 -1
  40. package/dist/server/dns.js +14 -6
  41. package/dist/server/dns.js.map +1 -1
  42. package/dist/server/send-mail.d.ts +2 -1
  43. package/dist/server/send-mail.d.ts.map +1 -1
  44. package/dist/server/send-mail.js +19 -7
  45. package/dist/server/send-mail.js.map +1 -1
  46. package/module.json +2 -2
  47. package/package.json +15 -4
  48. package/src/contract.ts +14 -3
  49. package/src/index.ts +4 -3
  50. package/src/jobs/delivery-job.ts +129 -0
  51. package/src/jobs/index.ts +12 -0
  52. package/src/messages/en.ts +2 -0
  53. package/src/messages/index.ts +4 -5
  54. package/src/next/messages.ts +2 -2
  55. package/src/providers/resend.ts +22 -3
  56. package/src/server/deliveries.ts +9 -5
  57. package/src/server/dns.ts +16 -7
  58. package/src/server/send-mail.ts +20 -7
  59. package/dist/messages/pl.d.ts +0 -3
  60. package/dist/messages/pl.d.ts.map +0 -1
  61. package/dist/messages/pl.js +0 -29
  62. package/dist/messages/pl.js.map +0 -1
  63. package/src/messages/pl.ts +0 -30
package/CHANGELOG.md CHANGED
@@ -4,6 +4,44 @@ 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.1
8
+
9
+ - New entry `@softure-ai/mailing/jobs` (#492), with `@softure-ai/jobs` as an optional peer dependency:
10
+ `enqueueDelivery(ctx, { queue, scope, payload })` enqueues a delivery in the caller's transaction (the scope is
11
+ the job's dedup key); `defineDeliveryJobHandler({ payload, buildMail })` sends it through `deliverOnce` from the
12
+ jobs worker and retries `retry-later` and `halted` (after the provider's `Retry-After`, else the queue's backoff);
13
+ rejected mail fails at once; `getDeliveryJobResult` is the same mapping for an app's own handler. The payload holds
14
+ the app's ids; the handler builds the mail when it runs. New job codes `mailing.delivery_in_flight` and
15
+ `mailing.delivery_uncertain` with copy in `errors.mailing`. README, "Deliveries through jobs".
16
+ - `resend()` reads `Retry-After` (seconds or an HTTP date); `ProviderOutcome` failures, `SendMailFailure` and
17
+ `deliverOnce`'s `retry-later` and `halted` outcomes carry it as the optional `retryAfterSeconds`. New export
18
+ `readRetryAfter(header, nowMs)` for a provider adapter of the app's own. A strict equality on these results
19
+ (`toStrictEqual`) sees the new field when the provider sends the header.
20
+ - `module.json → dependsOn` names `jobs` as optional (`"*?"`); an app with both modules lists `jobs()` anywhere in
21
+ `modules`.
22
+ - Migrate an app that retries `retry-later` by hand (a cron that calls `deliverOnce` again, a "try again" kept for
23
+ the user):
24
+ 1. `npm install @softure-ai/jobs`, add `jobs({ queues: { "<queue>": { maxAttempts } } })` and run the worker
25
+ (jobs README, "Running the worker"); run the migrations.
26
+ 2. Register `defineDeliveryJobHandler({ payload, buildMail })` for the queue; move the code that built the mail
27
+ into `buildMail`, reading the app's data by the ids in the payload.
28
+ 3. Replace the `deliverOnce` call in the request with `enqueueDelivery({ ...ctx, db: tx }, { queue, scope,
29
+ payload })` inside the transaction of the write it belongs to, keeping the same `scope` (the ledger rows already
30
+ sent stay sent).
31
+ 4. Drop the retry cron and the per-app attempt counting; `mailing({ maxAttempts })` now only applies to direct
32
+ `deliverOnce` calls.
33
+
34
+ ## 0.2.0
35
+
36
+ - **Breaking (#446):** the built-in `pl` dictionary is gone; English is the only built-in copy and the fallback for
37
+ every other locale, which is now any BCP 47 tag. Migrate: an app that shows Polish keeps its own translation and
38
+ passes it as the overrides for its locale, `mailing({ messages: { pl: myTranslation } })`; a key it leaves out reads
39
+ English.
40
+ - `checkSenderDns(domain, { inbound: "cloudflare" })` (and `softure-mail dns --inbound cloudflare`) fails the inbound
41
+ SPF check as `weak` unless the record's first `all` is `-all` or `~all`. Before, a record with the Email Routing
42
+ include and `?all`, `+all`, a bare `all` or no `all` passed, though it lets anyone send as the domain. An app that
43
+ kept its own check for this next to the report can drop it.
44
+
7
45
  ## 0.1.14
8
46
 
9
47
  - The `@softure-ai/ui` peer range also accepts 0.2.0.
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
@@ -410,8 +475,9 @@ It reports each check as `pass`, `warn` or `fail`, exiting 1 on any `fail`:
410
475
  - **INBOUND** (`inbound: "cloudflare"`, `--inbound cloudflare`): Cloudflare Email Routing receives the reply-to
411
476
  domain's mail (else the checked domain's). Every MX record must be under `mx.cloudflare.net`
412
477
  (`unexpected-target` otherwise, `null-mx`, `missing`), and the domain must publish exactly one SPF record
413
- (`multiple`) that includes `_spf.mx.cloudflare.net` (`missing-include`). The report's `inbound` holds the MX and
414
- the SPF check, `[]` when not asked.
478
+ (`multiple`) that includes `_spf.mx.cloudflare.net` (`missing-include`) and ends in `-all` or `~all` (`weak`
479
+ otherwise: `?all`, `+all`, a bare `all` or no `all` lets anyone send as the domain; the first `all` decides, and a
480
+ `redirect=` is not followed). The report's `inbound` holds the MX and the SPF check, `[]` when not asked.
415
481
 
416
482
  It cannot see whether the provider signs with the published key. The function takes `resolveTxt`, `resolveMx` and
417
483
  `resolveCname` for tests; by default it asks the system resolver.
@@ -422,7 +488,8 @@ It cannot see whether the provider signs with the published key. The function ta
422
488
  npm install @softure-ai/mailing @softure-ai/core @softure-ai/db @softure-ai/ui drizzle-orm
423
489
  ```
424
490
 
425
- 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
426
493
  database (its suppression list). The `/testing` entry imports `node:fs`.
427
494
 
428
495
  ## 3. Configuration
@@ -590,7 +657,8 @@ classes or its own layout (section 4).
590
657
 
591
658
  ## 9. Copy
592
659
 
593
- `src/messages/{en,pl}.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed,provider_refused,quota_exceeded}`,
660
+ `src/messages/en.ts`: `errors.mailing.{invalid_input,rejected,unavailable,suppressed,provider_refused,quota_exceeded,delivery_in_flight,delivery_uncertain}`
661
+ (the last two are job codes, for an admin screen that shows a delivery job's state),
594
662
  `footer.{text,htmlLead,htmlLink}` (the list-mail footer, in the app's locale) and
595
663
  `unsubscribe.*` (the page). `getMailingErrorMessage(messages, code)` returns the copy for a code;
596
664
  override any text with `mailing({ messages: { en: { footer: { text: "…" } } } })`.
@@ -687,7 +755,8 @@ personal data. So the module neither exports nor deletes per user (`privacy: { e
687
755
 
688
756
  - One recipient per call; no cc, bcc or attachments.
689
757
  - `sendMail` does not retry: the caller retries `unavailable` with the same `idempotencyKey` (Resend
690
- keeps keys for 24 hours), or uses `deliverOnce`, which keeps the state between runs.
758
+ keeps keys for 24 hours), uses `deliverOnce`, which keeps the state between runs, or enqueues it with
759
+ `enqueueDelivery`, which retries it from a job.
691
760
  - Exactly-once ends where the provider's idempotency window ends: a sender that dies after the
692
761
  provider accepted a mail and before the outcome was written leaves an `uncertain` claim once
693
762
  `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.1.14",
19
- dependsOn: {},
18
+ version: "0.2.1",
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,QAAQ;QACjB,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":"AAAA,oHAAoH;AACpH,sHAAsH;AACtH,+GAA+G;AAC/G,4DAA4D;AAC5D,OAAO,EAAE,GAAG,EAAE,EAAE,EAAW,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,EAA4D,MAAM,kBAAkB,CAAC;AACtI,OAAO,EAAE,UAAU,EAAsC,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,EAAE,WAAW,EAAE,eAAe,EAA6C,MAAM,yBAAyB,CAAC;AAClH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAmBzD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAW,GAAgB,EAAE,KAAqC;IACrG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,+CAA+C,KAAK,CAAC,KAAK,uEAAuE,CAAC,CAAC;IACrJ,CAAC;IACD,OAAO,UAAU,CAAC,GAAG,EAAE;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;QACvD,QAAQ,EAAE,KAAK,CAAC,KAAK;QACrB,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5D,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;QACjF,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;KAC/E,CAAC,CAAC;AACL,CAAC;AAoBD;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAA2C;IAE3C,0GAA0G;IAC1G,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QACtB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC;QACzC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAwE,CAAC;IAC1E,OAAO,gBAAgB,CAAC;QACtB,OAAO,EAAE,MAAM;QACf,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE;YACzC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YACrE,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,EAAE,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9G,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,EAAE,YAAY,IAAI,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC;YAC5G,OAAO,oBAAoB,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;QACzD,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAwB,EAAE,OAA0C;IACvG,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,EAAE,EAAE,CAAC;QACd,KAAK,UAAU;YACb,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,aAAa;YAChB,OAAO,UAAU,CAAC,qBAAqB,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACtE,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC/D,KAAK,WAAW;YACd,iFAAiF;YACjF,OAAO,aAAa,CAAC,4BAA4B,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACjG,KAAK,WAAW;YACd,OAAO,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAmF,EAAE,OAA2B;IAClI,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC1E,CAAC"}
@@ -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,6 +1,6 @@
1
- import type { MailingErrorCode } from "../contract.js";
1
+ import type { DeliveryJobErrorCode, MailingErrorCode } from "../contract.js";
2
2
  import { en } from "./en.js";
3
- /** Complete default dictionaries; apps pass partial overrides per locale. */
3
+ /** The built-in English dictionary; an app passes partial overrides or its own translation per locale. */
4
4
  export declare const mailingMessages: {
5
5
  en: {
6
6
  errors: {
@@ -11,33 +11,8 @@ export declare const mailingMessages: {
11
11
  suppressed: string;
12
12
  provider_refused: string;
13
13
  quota_exceeded: string;
14
- };
15
- };
16
- footer: {
17
- text: string;
18
- htmlLead: string;
19
- htmlLink: string;
20
- };
21
- unsubscribe: {
22
- title: string;
23
- lead: string;
24
- submit: string;
25
- doneTitle: string;
26
- doneBody: string;
27
- invalidTitle: string;
28
- invalidBody: string;
29
- failed: string;
30
- };
31
- };
32
- pl: {
33
- errors: {
34
- mailing: {
35
- invalid_input: string;
36
- rejected: string;
37
- unavailable: string;
38
- suppressed: string;
39
- provider_refused: string;
40
- quota_exceeded: string;
14
+ delivery_in_flight: string;
15
+ delivery_uncertain: string;
41
16
  };
42
17
  };
43
18
  footer: {
@@ -59,5 +34,5 @@ export declare const mailingMessages: {
59
34
  };
60
35
  export type MailingMessages = typeof en;
61
36
  /** The copy for a mailing error code; a code the dictionary does not know reads as `unavailable`. */
62
- export declare function getMailingErrorMessage(messages: MailingMessages, code: MailingErrorCode): string;
37
+ export declare function getMailingErrorMessage(messages: MailingMessages, code: MailingErrorCode | DeliveryJobErrorCode): string;
63
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;AAG7B,6EAA6E;AAC7E,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAa,CAAC;AAE1C,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,7 +1,6 @@
1
1
  import { en } from "./en.js";
2
- import { pl } from "./pl.js";
3
- /** Complete default dictionaries; apps pass partial overrides per locale. */
4
- export const mailingMessages = { en, pl };
2
+ /** The built-in English dictionary; an app passes partial overrides or its own translation per locale. */
3
+ export const mailingMessages = { en };
5
4
  /** The copy for a mailing error code; a code the dictionary does not know reads as `unavailable`. */
6
5
  export function getMailingErrorMessage(messages, code) {
7
6
  const group = messages.errors.mailing;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/messages/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;AAI1C,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,4 +1,4 @@
1
- import type { SoftureConfig } from "@softure-ai/core";
1
+ import { type SoftureConfig } from "@softure-ai/core";
2
2
  import type { MailingMessages } from "../messages/index.js";
3
3
  /** The module's copy in the app's locale, with the app's overrides applied. */
4
4
  export declare function getMailingMessages(config: SoftureConfig): MailingMessages;