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