@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.
- package/CHANGELOG.md +48 -0
- package/README.md +85 -6
- package/dist/contract.d.ts +13 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/jobs/delivery-job.d.ts +72 -0
- package/dist/jobs/delivery-job.d.ts.map +1 -0
- package/dist/jobs/delivery-job.js +85 -0
- package/dist/jobs/delivery-job.js.map +1 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +4 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/messages/en.d.ts +2 -0
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +2 -0
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/index.d.ts +4 -2
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/index.js.map +1 -1
- package/dist/next/actions.d.ts.map +1 -1
- package/dist/next/actions.js +8 -26
- package/dist/next/actions.js.map +1 -1
- package/dist/next/page-submit.d.ts +12 -0
- package/dist/next/page-submit.d.ts.map +1 -0
- package/dist/next/page-submit.js +30 -0
- package/dist/next/page-submit.js.map +1 -0
- package/dist/next/pages.d.ts.map +1 -1
- package/dist/next/pages.js +8 -5
- package/dist/next/pages.js.map +1 -1
- package/dist/next/route.d.ts +4 -1
- package/dist/next/route.d.ts.map +1 -1
- package/dist/next/route.js +58 -2
- package/dist/next/route.js.map +1 -1
- package/dist/next/strip-link-query.d.ts +11 -0
- package/dist/next/strip-link-query.d.ts.map +1 -0
- package/dist/next/strip-link-query.js +25 -0
- package/dist/next/strip-link-query.js.map +1 -0
- package/dist/providers/resend.d.ts +5 -0
- package/dist/providers/resend.d.ts.map +1 -1
- package/dist/providers/resend.js +23 -3
- package/dist/providers/resend.js.map +1 -1
- package/dist/server/deliveries.d.ts +6 -1
- package/dist/server/deliveries.d.ts.map +1 -1
- package/dist/server/deliveries.js +3 -2
- package/dist/server/deliveries.js.map +1 -1
- package/dist/server/send-mail.d.ts +2 -1
- package/dist/server/send-mail.d.ts.map +1 -1
- package/dist/server/send-mail.js +17 -5
- package/dist/server/send-mail.js.map +1 -1
- package/module.json +2 -2
- package/package.json +12 -1
- package/src/contract.ts +14 -3
- package/src/index.ts +4 -3
- package/src/jobs/delivery-job.ts +129 -0
- package/src/jobs/index.ts +12 -0
- package/src/messages/en.ts +2 -0
- package/src/messages/index.ts +2 -2
- package/src/next/actions.ts +8 -24
- package/src/next/page-submit.ts +35 -0
- package/src/next/pages.tsx +9 -5
- package/src/next/route.ts +59 -4
- package/src/next/strip-link-query.tsx +30 -0
- package/src/providers/resend.ts +22 -3
- package/src/server/deliveries.ts +9 -5
- 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`,
|
|
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 (
|
|
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),
|
|
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,
|
package/dist/contract.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
/**
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,MAAM,gBAAgB;AAC1B,0GAA0G;AACxG,uBAAuB;AACzB,8EAA8E;GAC5E,kBAAkB;AACpB,+GAA+G;GAC7G,qBAAqB;AACvB,mFAAmF;GACjF,oBAAoB;AACtB;;;GAGG;GACD,0BAA0B;AAC5B,oHAAoH;GAClH,wBAAwB,CAAC;AAE7B,yGAAyG;AACzG,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,gBAAgB,CAAmE,CAAC;AAElI,kGAAkG;AAClG,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAE1D,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,iGAAiG;AACjG,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,CAAC;AAEvJ,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3G,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,SAAS,EAAE,iBAAiB,EAAE,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAExH;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,QAAQ,EAAE;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxD,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,KAC1B,QAAQ,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,MAAM,EAAE,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE1I;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,CACf,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACxC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,EAC7B,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,KAC9C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC7B;AAED,mGAAmG;AACnG,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,oCAAoC;AACpC,MAAM,WAAW,QAAQ;IACvB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
|
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.
|
|
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;
|
|
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 @@
|
|
|
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"}
|
package/dist/messages/en.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4Bd,CAAC"}
|
package/dist/messages/en.js
CHANGED
|
@@ -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: {
|
package/dist/messages/en.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/messages/index.d.ts
CHANGED
|
@@ -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;
|
|
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,
|
|
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":"
|
|
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"}
|