@ultimat3/mail 22.5.1 → 22.6.0
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/README.md +1 -1
- package/package.json +6 -6
- package/src/driver.ts +5 -3
- package/src/mail.ts +4 -2
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ setMailTransform(async (rendered, meta) =>
|
|
|
58
58
|
| Every colour is a token | `MAIL_TOKENS` in `layout.ts` holds light + dark hexes; templates never see a hex |
|
|
59
59
|
| Every date takes an IANA zone | `options.tz`, else `ctx.tz`, else `UTC` |
|
|
60
60
|
| No CR/LF in a header-bound field | checked in `renderMessage` and again in `sendMailJob`, so every driver refuses the same message (`X_MAIL_HEADER_INVALID`). `mime.ts` keeps its own gate for the headers the SMTP transport mints itself |
|
|
61
|
-
| `unsubscribeUrl` is one-click
|
|
61
|
+
| `unsubscribeUrl` is one-click, and one-click is the supported path | it emits `List-Unsubscribe: <url>` plus `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058), and the footer links the SAME url. Make that url one page that answers both: `GET` renders a confirm button and never unsubscribes (scanners prefetch), and `POST` — the mail client's one-click, or the button's own form — runs the action: `defineRoute({ render: 'ssr', …, post: 'unsubscribe' })` (`@ultimat3/render`) binds it, with the url's query (`?t=<token>`) merged into the input. `unsubscribeOneClick: false` drops the `-Post` line for a url that still cannot take a POST; it is the fallback, not the path |
|
|
62
62
|
| Sending is a job | `retry: { attempts: 5, backoff: 'exponential' }`, idempotency key `mail:<mailId>:<hash(recipients + rendered)>` — 128 bits, ASCII, under Resend's 256-character limit at any recipient count — or `(mailId, your key)` when you pass one (digested if it is not a short ASCII token) — a caller's key is scoped to its mail so two templates cannot dedupe each other away |
|
|
63
63
|
|
|
64
64
|
## Drivers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/mail",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.6.0",
|
|
4
4
|
"description": "Transactional email as data: one template renders HTML and text, sent through a job.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"test": "bun test"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@ultimat3/core": "22.
|
|
35
|
-
"@ultimat3/i18n": "22.
|
|
36
|
-
"@ultimat3/jobs": "22.
|
|
37
|
-
"@ultimat3/schema": "22.
|
|
38
|
-
"@ultimat3/time": "22.
|
|
34
|
+
"@ultimat3/core": "22.6.0",
|
|
35
|
+
"@ultimat3/i18n": "22.6.0",
|
|
36
|
+
"@ultimat3/jobs": "22.6.0",
|
|
37
|
+
"@ultimat3/schema": "22.6.0",
|
|
38
|
+
"@ultimat3/time": "22.6.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/driver.ts
CHANGED
|
@@ -28,9 +28,11 @@ export interface MailMessage {
|
|
|
28
28
|
readonly bcc?: readonly string[] | undefined;
|
|
29
29
|
readonly unsubscribeUrl?: string | undefined;
|
|
30
30
|
/**
|
|
31
|
-
* `
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Absent means `true` — one-click, what Gmail and Yahoo require of bulk senders, and the
|
|
32
|
+
* supported path: the url is a page whose `GET` confirms and whose `POST` unsubscribes
|
|
33
|
+
* (`defineRoute({ post: '<action>' })` in `@ultimat3/render`). `false` drops
|
|
34
|
+
* `List-Unsubscribe-Post` and keeps the GET-only `List-Unsubscribe`, for a url that cannot yet
|
|
35
|
+
* honour RFC 8058's promise that a POST to it unsubscribes.
|
|
34
36
|
*/
|
|
35
37
|
readonly unsubscribeOneClick?: boolean | undefined;
|
|
36
38
|
readonly idempotencyKey?: string | undefined;
|
package/src/mail.ts
CHANGED
|
@@ -46,8 +46,10 @@ export interface SendOptions {
|
|
|
46
46
|
readonly unsubscribeUrl?: string | undefined;
|
|
47
47
|
/**
|
|
48
48
|
* Default `true`: `List-Unsubscribe-Post: List-Unsubscribe=One-Click` (RFC 8058) rides with
|
|
49
|
-
* `List-Unsubscribe
|
|
50
|
-
*
|
|
49
|
+
* `List-Unsubscribe`, and it is the supported path — one url for the header and the footer, a
|
|
50
|
+
* page whose `GET` confirms and whose `POST` unsubscribes (`defineRoute({ post: '<action>' })`).
|
|
51
|
+
* `false` is the fallback for a url that cannot take the POST: the header and the footer link
|
|
52
|
+
* stay, the POST promise goes.
|
|
51
53
|
*/
|
|
52
54
|
readonly unsubscribeOneClick?: boolean | undefined;
|
|
53
55
|
readonly idempotencyKey?: string | undefined;
|