@ultimat3/mail 1.0.0 → 1.2.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.
Files changed (69) hide show
  1. package/package.json +6 -6
  2. package/src/blocks.d.ts +0 -47
  3. package/src/blocks.d.ts.map +0 -1
  4. package/src/blocks.js +0 -23
  5. package/src/blocks.js.map +0 -1
  6. package/src/catalog.d.ts +0 -6
  7. package/src/catalog.d.ts.map +0 -1
  8. package/src/catalog.js +0 -78
  9. package/src/catalog.js.map +0 -1
  10. package/src/driver.d.ts +0 -79
  11. package/src/driver.d.ts.map +0 -1
  12. package/src/driver.js +0 -115
  13. package/src/driver.js.map +0 -1
  14. package/src/errors.d.ts +0 -23
  15. package/src/errors.d.ts.map +0 -1
  16. package/src/errors.js +0 -82
  17. package/src/errors.js.map +0 -1
  18. package/src/html.d.ts +0 -10
  19. package/src/html.d.ts.map +0 -1
  20. package/src/html.js +0 -34
  21. package/src/html.js.map +0 -1
  22. package/src/index.d.ts +0 -17
  23. package/src/index.d.ts.map +0 -1
  24. package/src/index.js +0 -13
  25. package/src/index.js.map +0 -1
  26. package/src/job.d.ts +0 -17
  27. package/src/job.d.ts.map +0 -1
  28. package/src/job.js +0 -79
  29. package/src/job.js.map +0 -1
  30. package/src/layout.d.ts +0 -41
  31. package/src/layout.d.ts.map +0 -1
  32. package/src/layout.js +0 -160
  33. package/src/layout.js.map +0 -1
  34. package/src/mail.d.ts +0 -67
  35. package/src/mail.d.ts.map +0 -1
  36. package/src/mail.js +0 -119
  37. package/src/mail.js.map +0 -1
  38. package/src/render.d.ts +0 -26
  39. package/src/render.d.ts.map +0 -1
  40. package/src/render.js +0 -149
  41. package/src/render.js.map +0 -1
  42. package/src/templates/index.d.ts +0 -9
  43. package/src/templates/index.d.ts.map +0 -1
  44. package/src/templates/index.js +0 -23
  45. package/src/templates/index.js.map +0 -1
  46. package/src/templates/invite.d.ts +0 -15
  47. package/src/templates/invite.d.ts.map +0 -1
  48. package/src/templates/invite.js +0 -25
  49. package/src/templates/invite.js.map +0 -1
  50. package/src/templates/mfa-enrolled.d.ts +0 -14
  51. package/src/templates/mfa-enrolled.d.ts.map +0 -1
  52. package/src/templates/mfa-enrolled.js +0 -26
  53. package/src/templates/mfa-enrolled.js.map +0 -1
  54. package/src/templates/reset-password.d.ts +0 -13
  55. package/src/templates/reset-password.d.ts.map +0 -1
  56. package/src/templates/reset-password.js +0 -23
  57. package/src/templates/reset-password.js.map +0 -1
  58. package/src/templates/security-alert.d.ts +0 -16
  59. package/src/templates/security-alert.d.ts.map +0 -1
  60. package/src/templates/security-alert.js +0 -29
  61. package/src/templates/security-alert.js.map +0 -1
  62. package/src/templates/verify-email.d.ts +0 -13
  63. package/src/templates/verify-email.d.ts.map +0 -1
  64. package/src/templates/verify-email.js +0 -23
  65. package/src/templates/verify-email.js.map +0 -1
  66. package/src/templates/welcome.d.ts +0 -13
  67. package/src/templates/welcome.d.ts.map +0 -1
  68. package/src/templates/welcome.js +0 -20
  69. package/src/templates/welcome.js.map +0 -1
package/src/job.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { type JobHandle } from '@ultimat3/jobs';
2
- import { type StandardSchemaV1 } from '@ultimat3/schema';
3
- import { type MailMessage } from './driver';
4
- /** The queue payload is the already-rendered envelope: rendering happens once, at send time. */
5
- export declare const mailMessageSchema: StandardSchemaV1<unknown, MailMessage>;
6
- /**
7
- * `(mailId, recipients, hash(rendered payload))`, or the caller's key when supplied.
8
- * Content-derived on purpose: a retry of the same request produces the same key, while an
9
- * intentional resend with different content produces a different one.
10
- */
11
- export declare function mailIdempotencyKey(message: MailMessage): string;
12
- /**
13
- * Five attempts with exponential backoff: transient 4xx/greylisting clears in minutes, and a
14
- * hard bounce still lands in the dead-letter queue where `x jobs dead --json` can show it.
15
- */
16
- export declare const sendMailJob: JobHandle<MailMessage>;
17
- //# sourceMappingURL=job.d.ts.map
package/src/job.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"job.d.ts","sourceRoot":"","sources":["job.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAO,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,KAAK,gBAAgB,EAAK,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,UAAU,CAAC;AAEzE,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,EAAE,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAanE,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAiB/D;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,CAAC,WAAW,CAM7C,CAAC"}
package/src/job.js DELETED
@@ -1,79 +0,0 @@
1
- // Single responsibility: delivering a rendered message is background work, never request work.
2
- // A slow SMTP host must not hold a request open, and at-least-once delivery must not mean two
3
- // welcome emails — so the send is a `job` with a content-derived idempotency key.
4
- import { job } from '@ultimat3/jobs';
5
- import { t } from '@ultimat3/schema';
6
- import { mailDriver } from './driver';
7
- /** The queue payload is the already-rendered envelope: rendering happens once, at send time. */
8
- export const mailMessageSchema = t.object({
9
- mailId: t.string,
10
- to: t.array(t.email),
11
- subject: t.string,
12
- html: t.string,
13
- text: t.string,
14
- locale: t.locale,
15
- tz: t.timezone,
16
- replyTo: t.email.optional(),
17
- cc: t.array(t.email).optional(),
18
- bcc: t.array(t.email).optional(),
19
- unsubscribeUrl: t.url.optional(),
20
- idempotencyKey: t.string.optional(),
21
- });
22
- /**
23
- * `(mailId, recipients, hash(rendered payload))`, or the caller's key when supplied.
24
- * Content-derived on purpose: a retry of the same request produces the same key, while an
25
- * intentional resend with different content produces a different one.
26
- */
27
- export function mailIdempotencyKey(message) {
28
- const explicit = message.idempotencyKey;
29
- if (explicit !== undefined && explicit !== '')
30
- return `mail:${explicit}`;
31
- const recipients = [...message.to].map((address) => address.toLowerCase()).sort();
32
- const digest = fnv1a32(stableStringify({
33
- subject: message.subject,
34
- html: message.html,
35
- text: message.text,
36
- cc: message.cc ?? [],
37
- bcc: message.bcc ?? [],
38
- locale: message.locale,
39
- tz: message.tz,
40
- unsubscribeUrl: message.unsubscribeUrl ?? '',
41
- }));
42
- return `mail:${message.mailId}:${recipients.join(',')}:${digest}`;
43
- }
44
- /**
45
- * Five attempts with exponential backoff: transient 4xx/greylisting clears in minutes, and a
46
- * hard bounce still lands in the dead-letter queue where `x jobs dead --json` can show it.
47
- */
48
- export const sendMailJob = job({
49
- name: 'mail.send',
50
- input: mailMessageSchema,
51
- idempotencyKey: mailIdempotencyKey,
52
- retry: { attempts: 5, backoff: 'exponential' },
53
- run: ({ input }) => mailDriver().send(input),
54
- });
55
- /** Key order is normalised so two structurally equal payloads hash identically. */
56
- function stableStringify(value) {
57
- if (Array.isArray(value))
58
- return `[${value.map(stableStringify).join(',')}]`;
59
- if (value !== null && typeof value === 'object') {
60
- const entries = Object.entries(value)
61
- .filter(([, entry]) => entry !== undefined)
62
- .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
63
- .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`);
64
- return `{${entries.join(',')}}`;
65
- }
66
- if (value === undefined)
67
- return 'null';
68
- return JSON.stringify(value);
69
- }
70
- /** FNV-1a, 32-bit. Not a security hash — a short, dependency-free, stable content id. */
71
- function fnv1a32(input) {
72
- let hash = 0x811c9dc5;
73
- for (let index = 0; index < input.length; index += 1) {
74
- hash ^= input.charCodeAt(index);
75
- hash = Math.imul(hash, 0x01000193) >>> 0;
76
- }
77
- return hash.toString(16).padStart(8, '0');
78
- }
79
- //# sourceMappingURL=job.js.map
package/src/job.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"job.js","sourceRoot":"","sources":["job.ts"],"names":[],"mappings":"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"}
package/src/layout.d.ts DELETED
@@ -1,41 +0,0 @@
1
- export type ColorScheme = 'light' | 'dark';
2
- export type MailToken = 'pageBg' | 'surfaceBg' | 'textPrimary' | 'textMuted' | 'borderSubtle' | 'accentBg' | 'accentText' | 'linkText' | 'calloutInfoBg' | 'calloutInfoText' | 'calloutDangerBg' | 'calloutDangerText';
3
- export declare const MAIL_TOKENS: Readonly<Record<MailToken, Readonly<Record<ColorScheme, string>>>>;
4
- /** Resolve a token to a hex value. The only function in the package that returns a colour. */
5
- export declare function token(name: MailToken, scheme?: ColorScheme): string;
6
- export declare const MAIL_FONT_STACK = "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif";
7
- export declare const MAIL_WIDTH_PX = 600;
8
- /** A node's dark-mode role: which CSS property takes which token when the client is dark. */
9
- export type DarkRule = readonly [role: string, declarations: readonly [string, MailToken][]];
10
- /**
11
- * Dark overrides key off short `data-x` role codes rather than class names: base styling
12
- * stays fully inlined for the clients that strip <style>, and the codes are deliberately
13
- * not token names, so nothing leaks the design vocabulary into delivered HTML.
14
- */
15
- export declare const DARK_RULES: readonly DarkRule[];
16
- /** The one `prefers-color-scheme` block. Clients that ignore it keep the inlined light theme. */
17
- export declare function darkModeCss(): string;
18
- export interface UnsubscribeSlot {
19
- /** Already translated — the layout never touches a catalog. */
20
- readonly label: string;
21
- readonly url: string;
22
- }
23
- export interface LayoutInput {
24
- readonly subject: string;
25
- /** Hidden preview text. Clients show it next to the subject in the inbox list. */
26
- readonly preheader: string;
27
- /** Rendered block HTML. */
28
- readonly content: string;
29
- readonly footer: readonly string[];
30
- readonly unsubscribe?: UnsubscribeSlot | undefined;
31
- readonly locale: string;
32
- readonly direction: 'ltr' | 'rtl';
33
- }
34
- export type MailLayout = (input: LayoutInput) => string;
35
- export declare const BASE_LAYOUT = "base";
36
- /** The framework's only layout. Apps register their own with `registerLayout`. */
37
- export declare const baseLayout: MailLayout;
38
- export declare function registerLayout(name: string, layout: MailLayout): void;
39
- export declare function layoutFor(name: string): MailLayout | undefined;
40
- export declare function registeredLayouts(): readonly string[];
41
- //# sourceMappingURL=layout.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["layout.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3C,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,WAAW,GACX,aAAa,GACb,WAAW,GACX,cAAc,GACd,UAAU,GACV,YAAY,GACZ,UAAU,GACV,eAAe,GACf,iBAAiB,GACjB,iBAAiB,GACjB,mBAAmB,CAAC;AAExB,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAcvF,CAAC;AAEL,8FAA8F;AAC9F,wBAAgB,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,GAAE,WAAqB,GAAG,MAAM,CAE5E;AAED,eAAO,MAAM,eAAe,gFACmD,CAAC;AAEhF,eAAO,MAAM,aAAa,MAAM,CAAC;AAEjC,6FAA6F;AAC7F,MAAM,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;AAE7F;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAmCzC,CAAC;AAEF,iGAAiG;AACjG,wBAAgB,WAAW,IAAI,MAAM,CAQpC;AAED,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,KAAK,GAAG,KAAK,CAAC;CACnC;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC;AAExD,eAAO,MAAM,WAAW,SAAS,CAAC;AA6ClC,kFAAkF;AAClF,eAAO,MAAM,UAAU,EAAE,UAwBjB,CAAC;AAeT,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAErE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAE9D;AAED,wBAAgB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAErD"}
package/src/layout.js DELETED
@@ -1,160 +0,0 @@
1
- // Single responsibility: the base email layout and the semantic colour tokens every block
2
- // resolves through. Colours are declared once here (light + dark) and inlined by the
3
- // renderer, because email clients drop <style> and templates must never carry a raw hex.
4
- // Table-based and 600px wide: Outlook still renders with Word's HTML engine.
5
- import { escapeHtml, safeUrl, styleAttr } from './html';
6
- export const MAIL_TOKENS = Object.freeze({
7
- pageBg: { light: '#f4f5f7', dark: '#0b0d10' },
8
- surfaceBg: { light: '#ffffff', dark: '#14181d' },
9
- textPrimary: { light: '#16191d', dark: '#e7eaee' },
10
- textMuted: { light: '#5c6470', dark: '#9aa4b1' },
11
- borderSubtle: { light: '#e2e6ea', dark: '#262c33' },
12
- accentBg: { light: '#2f6df6', dark: '#4f86ff' },
13
- accentText: { light: '#ffffff', dark: '#0b0d10' },
14
- linkText: { light: '#2f6df6', dark: '#7aa7ff' },
15
- calloutInfoBg: { light: '#eef3ff', dark: '#141c2c' },
16
- calloutInfoText: { light: '#1f3f8f', dark: '#b9cdfb' },
17
- calloutDangerBg: { light: '#fdeceb', dark: '#2a1416' },
18
- calloutDangerText: { light: '#8c2118', dark: '#f5b3ad' },
19
- });
20
- /** Resolve a token to a hex value. The only function in the package that returns a colour. */
21
- export function token(name, scheme = 'light') {
22
- return MAIL_TOKENS[name][scheme];
23
- }
24
- export const MAIL_FONT_STACK = '-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif';
25
- export const MAIL_WIDTH_PX = 600;
26
- /**
27
- * Dark overrides key off short `data-x` role codes rather than class names: base styling
28
- * stays fully inlined for the clients that strip <style>, and the codes are deliberately
29
- * not token names, so nothing leaks the design vocabulary into delivered HTML.
30
- */
31
- export const DARK_RULES = [
32
- ['page', [['background-color', 'pageBg']]],
33
- [
34
- 'card',
35
- [
36
- ['background-color', 'surfaceBg'],
37
- ['border-color', 'borderSubtle'],
38
- ],
39
- ],
40
- ['h', [['color', 'textPrimary']]],
41
- ['p', [['color', 'textPrimary']]],
42
- ['mut', [['color', 'textMuted']]],
43
- [
44
- 'btn',
45
- [
46
- ['background-color', 'accentBg'],
47
- ['color', 'accentText'],
48
- ],
49
- ],
50
- ['hr', [['border-top-color', 'borderSubtle']]],
51
- ['a', [['color', 'linkText']]],
52
- [
53
- 'ci',
54
- [
55
- ['background-color', 'calloutInfoBg'],
56
- ['color', 'calloutInfoText'],
57
- ],
58
- ],
59
- [
60
- 'cd',
61
- [
62
- ['background-color', 'calloutDangerBg'],
63
- ['color', 'calloutDangerText'],
64
- ],
65
- ],
66
- ];
67
- /** The one `prefers-color-scheme` block. Clients that ignore it keep the inlined light theme. */
68
- export function darkModeCss() {
69
- const rules = DARK_RULES.map(([role, declarations]) => {
70
- const body = declarations
71
- .map(([property, name]) => `${property}:${token(name, 'dark')}!important`)
72
- .join(';');
73
- return `[data-x="${role}"]{${body}}`;
74
- });
75
- return `@media (prefers-color-scheme:dark){${rules.join('')}}`;
76
- }
77
- export const BASE_LAYOUT = 'base';
78
- const BODY_STYLE = styleAttr([
79
- 'margin:0',
80
- 'padding:0',
81
- 'width:100%',
82
- `background-color:${token('pageBg')}`,
83
- `font-family:${MAIL_FONT_STACK}`,
84
- `color:${token('textPrimary')}`,
85
- '-webkit-font-smoothing:antialiased',
86
- ]);
87
- const PREHEADER_STYLE = styleAttr([
88
- 'display:none',
89
- 'font-size:1px',
90
- 'line-height:1px',
91
- 'max-height:0',
92
- 'max-width:0',
93
- 'opacity:0',
94
- 'overflow:hidden',
95
- 'mso-hide:all',
96
- ]);
97
- const PAGE_STYLE = styleAttr([`background-color:${token('pageBg')}`, 'width:100%']);
98
- const GUTTER_STYLE = styleAttr(['padding:24px 12px']);
99
- const SHELL_STYLE = styleAttr([`width:${MAIL_WIDTH_PX}px`, 'max-width:100%']);
100
- const CARD_STYLE = styleAttr([
101
- `background-color:${token('surfaceBg')}`,
102
- `border:1px solid ${token('borderSubtle')}`,
103
- 'border-radius:12px',
104
- 'padding:32px',
105
- ]);
106
- const FOOTER_STYLE = styleAttr([
107
- `color:${token('textMuted')}`,
108
- 'font-size:12px',
109
- 'line-height:18px',
110
- 'padding:16px 8px 32px 8px',
111
- ]);
112
- const UNSUBSCRIBE_STYLE = styleAttr([`color:${token('textMuted')}`, 'text-decoration:underline']);
113
- const TABLE_ATTRS = 'role="presentation" cellpadding="0" cellspacing="0" border="0"';
114
- /** The framework's only layout. Apps register their own with `registerLayout`. */
115
- export const baseLayout = (input) => `<!doctype html>
116
- <html lang="${escapeHtml(input.locale)}" dir="${input.direction}">
117
- <head>
118
- <meta charset="utf-8">
119
- <meta name="viewport" content="width=device-width,initial-scale=1">
120
- <meta http-equiv="x-ua-compatible" content="ie=edge">
121
- <meta name="color-scheme" content="light dark">
122
- <meta name="supported-color-schemes" content="light dark">
123
- <meta name="format-detection" content="telephone=no,date=no,address=no,email=no">
124
- <meta name="x-apple-disable-message-reformatting">
125
- <title>${escapeHtml(input.subject)}</title>
126
- <style>${darkModeCss()}</style>
127
- </head>
128
- <body ${BODY_STYLE} data-x="page">
129
- <div ${PREHEADER_STYLE}>${escapeHtml(input.preheader)}</div>
130
- <table ${TABLE_ATTRS} width="100%" data-x="page" ${PAGE_STYLE}>
131
- <tr><td align="center" ${GUTTER_STYLE}>
132
- <table ${TABLE_ATTRS} width="${MAIL_WIDTH_PX}" ${SHELL_STYLE}>
133
- <tr><td data-x="card" ${CARD_STYLE}>${input.content}</td></tr>
134
- <tr><td data-x="mut" ${FOOTER_STYLE}>${footerHtml(input)}</td></tr>
135
- </table>
136
- </td></tr>
137
- </table>
138
- </body>
139
- </html>`;
140
- function footerHtml(input) {
141
- const lines = input.footer.map((line) => `<div>${escapeHtml(line)}</div>`);
142
- const slot = input.unsubscribe;
143
- if (slot !== undefined) {
144
- const href = escapeHtml(safeUrl(slot.url));
145
- const label = escapeHtml(slot.label);
146
- lines.push(`<div><a data-x="a" href="${href}" ${UNSUBSCRIBE_STYLE}>${label}</a></div>`);
147
- }
148
- return lines.join('');
149
- }
150
- const layouts = new Map([[BASE_LAYOUT, baseLayout]]);
151
- export function registerLayout(name, layout) {
152
- layouts.set(name, layout);
153
- }
154
- export function layoutFor(name) {
155
- return layouts.get(name);
156
- }
157
- export function registeredLayouts() {
158
- return [...layouts.keys()].sort();
159
- }
160
- //# sourceMappingURL=layout.js.map
package/src/layout.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"layout.js","sourceRoot":"","sources":["layout.ts"],"names":[],"mappings":"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"}
package/src/mail.d.ts DELETED
@@ -1,67 +0,0 @@
1
- import { type StandardSchemaV1 } from '@ultimat3/schema';
2
- import type { MailTemplate } from './blocks';
3
- import { type MailMessage, type SendResult } from './driver';
4
- import { type RenderableMail } from './render';
5
- export interface MailDefinition<I> extends RenderableMail<I> {
6
- readonly id: string;
7
- /** An i18n key, never a literal subject line. */
8
- readonly subject: string;
9
- readonly template: MailTemplate<I>;
10
- readonly input: StandardSchemaV1<unknown, I>;
11
- readonly layout: string;
12
- }
13
- export interface MailInit<I> {
14
- readonly id: string;
15
- readonly subject: string;
16
- readonly template: MailTemplate<I>;
17
- readonly input: StandardSchemaV1<unknown, I>;
18
- /** Defaults to the framework's `base` layout. */
19
- readonly layout?: string | undefined;
20
- }
21
- export interface SendOptions {
22
- readonly to: string | readonly string[];
23
- /** BCP-47. Required — there is no ambient fallback for a message the user reads later. */
24
- readonly locale: string;
25
- /** IANA zone for every date in the body. Defaults to the request context, then UTC. */
26
- readonly tz?: string | undefined;
27
- readonly replyTo?: string | undefined;
28
- readonly cc?: readonly string[] | undefined;
29
- readonly bcc?: readonly string[] | undefined;
30
- readonly unsubscribeUrl?: string | undefined;
31
- readonly idempotencyKey?: string | undefined;
32
- /** Deliver inline instead of through the queue. Tests and CLI one-shots only. */
33
- readonly sync?: boolean | undefined;
34
- }
35
- /**
36
- * The registry erases the payload type so heterogeneous mails share one map. `template`
37
- * stays assignable because `never` flows into any `TemplateArgs<I>`; `input` widens to
38
- * `unknown` on the output side, which is exactly what `parse` re-establishes at send time.
39
- */
40
- export interface AnyMailDefinition {
41
- readonly id: string;
42
- readonly subject: string;
43
- readonly layout: string;
44
- readonly input: StandardSchemaV1<unknown, unknown>;
45
- readonly template: MailTemplate<never>;
46
- }
47
- export declare function defineMail<I>(init: MailInit<I>): MailDefinition<I>;
48
- export declare function mailFor(id: string): AnyMailDefinition | undefined;
49
- export declare function registeredMails(): readonly AnyMailDefinition[];
50
- export declare function registeredMailIds(): readonly string[];
51
- /** Test/CLI seam: drop every registered mail. */
52
- export declare function resetMails(): void;
53
- /**
54
- * Validate, render, and build the envelope — everything `send` does before it decides
55
- * between the queue and the transport. Exported so `x mail preview` and tests can render
56
- * without delivering anything.
57
- */
58
- export declare function renderMessage<I>(mail: MailDefinition<I>, data: I, options: SendOptions): MailMessage;
59
- /**
60
- * Enqueues by default so a slow SMTP host never blocks a request. Falls back to an inline
61
- * send when no job driver is configured (a script, a test, `x` one-shots) — the alternative
62
- * would be silently dropping mail in exactly the setups where it is easiest to miss.
63
- */
64
- export declare function send<I>(mail: MailDefinition<I>, data: I, options: SendOptions): Promise<SendResult>;
65
- /** Send by id — what the CLI, MCP tools and generated code use. `data` is parsed, not trusted. */
66
- export declare function sendById(id: string, data: unknown, options: SendOptions): Promise<SendResult>;
67
- //# sourceMappingURL=mail.d.ts.map
package/src/mail.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"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"}
package/src/mail.js DELETED
@@ -1,119 +0,0 @@
1
- // Single responsibility: the `defineMail` primitive, the mail registry, and `send`.
2
- // `locale` is required by the TYPE, not by a runtime check alone: an email in the wrong
3
- // language is a real, unnoticed production bug, and the ambient request locale is the one
4
- // piece of context that a background send cannot recover after the fact.
5
- import { assert, DEFAULT_TIME_ZONE, tryUseContext } from '@ultimat3/core';
6
- import { jobDriver } from '@ultimat3/jobs';
7
- import { parse } from '@ultimat3/schema';
8
- import { mailDriver } from './driver';
9
- import { localeMissing, mailDuplicate, templateUnknown } from './errors';
10
- import { mailIdempotencyKey, sendMailJob } from './job';
11
- import { BASE_LAYOUT } from './layout';
12
- import { renderMail } from './render';
13
- const registry = new Map();
14
- export function defineMail(init) {
15
- if (registry.has(init.id))
16
- throw mailDuplicate(init.id);
17
- const definition = {
18
- id: init.id,
19
- subject: init.subject,
20
- template: init.template,
21
- input: init.input,
22
- layout: init.layout ?? BASE_LAYOUT,
23
- };
24
- registry.set(init.id, definition);
25
- return definition;
26
- }
27
- export function mailFor(id) {
28
- return registry.get(id);
29
- }
30
- export function registeredMails() {
31
- return [...registry.values()].sort((a, b) => a.id.localeCompare(b.id));
32
- }
33
- export function registeredMailIds() {
34
- return [...registry.keys()].sort();
35
- }
36
- /** Test/CLI seam: drop every registered mail. */
37
- export function resetMails() {
38
- registry.clear();
39
- }
40
- /**
41
- * Validate, render, and build the envelope — everything `send` does before it decides
42
- * between the queue and the transport. Exported so `x mail preview` and tests can render
43
- * without delivering anything.
44
- */
45
- export function renderMessage(mail, data, options) {
46
- assertLocale(mail.id, options.locale);
47
- if (registry.get(mail.id) === undefined)
48
- throw templateUnknown(mail.id, registeredMailIds());
49
- const to = typeof options.to === 'string' ? [options.to] : [...options.to];
50
- assert(to.length > 0, `send(${mail.id}, ...) was called with an empty recipient list`, 'pass { to: user.email } — one address, or a non-empty array of addresses');
51
- const input = parse(mail.input, data, `${mail.id}.input`);
52
- const tz = options.tz ?? tryUseContext()?.tz ?? DEFAULT_TIME_ZONE;
53
- const rendered = renderMail(mail, input, {
54
- locale: options.locale,
55
- tz,
56
- unsubscribeUrl: options.unsubscribeUrl,
57
- });
58
- return {
59
- mailId: mail.id,
60
- to,
61
- subject: rendered.subject,
62
- html: rendered.html,
63
- text: rendered.text,
64
- locale: options.locale,
65
- tz,
66
- replyTo: options.replyTo,
67
- cc: options.cc,
68
- bcc: options.bcc,
69
- unsubscribeUrl: options.unsubscribeUrl,
70
- idempotencyKey: options.idempotencyKey,
71
- };
72
- }
73
- /**
74
- * Enqueues by default so a slow SMTP host never blocks a request. Falls back to an inline
75
- * send when no job driver is configured (a script, a test, `x` one-shots) — the alternative
76
- * would be silently dropping mail in exactly the setups where it is easiest to miss.
77
- */
78
- export async function send(mail, data, options) {
79
- const message = renderMessage(mail, data, options);
80
- const key = mailIdempotencyKey(message);
81
- const queue = jobDriver();
82
- if (options.sync === true || queue === undefined) {
83
- // The key is reported even inline, so a caller can dedupe its own retries either way.
84
- return { ...(await mailDriver().send(message)), idempotencyKey: key };
85
- }
86
- const enqueued = await queue.enqueue({
87
- name: sendMailJob.name,
88
- queue: sendMailJob.queue,
89
- input: message,
90
- idempotencyKey: key,
91
- maxAttempts: sendMailJob.retry.attempts,
92
- onConflict: 'dedupe',
93
- });
94
- return {
95
- id: enqueued.id,
96
- driver: 'queue',
97
- accepted: [...message.to, ...(message.cc ?? []), ...(message.bcc ?? [])],
98
- queued: true,
99
- idempotencyKey: key,
100
- };
101
- }
102
- /** Send by id — what the CLI, MCP tools and generated code use. `data` is parsed, not trusted. */
103
- export async function sendById(id, data, options) {
104
- const mail = registry.get(id);
105
- if (mail === undefined)
106
- throw templateUnknown(id, registeredMailIds());
107
- // The one cast in the package: the registry erased `I`, and `send` re-parses `data`
108
- // through the mail's own schema before it reaches the template.
109
- return await send(mail, data, options);
110
- }
111
- /**
112
- * The runtime backstop for JS callers and generated code — TypeScript already forbids
113
- * omitting `locale`, so reaching this throw means the type was cast away somewhere.
114
- */
115
- function assertLocale(mailId, locale) {
116
- if (typeof locale !== 'string' || locale.trim() === '')
117
- throw localeMissing(mailId);
118
- }
119
- //# sourceMappingURL=mail.js.map
package/src/mail.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,wFAAwF;AACxF,0FAA0F;AAC1F,yEAAyE;AAEzE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAyB,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAoB,UAAU,EAAmB,MAAM,UAAU,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAuB,UAAU,EAAE,MAAM,UAAU,CAAC;AAgD3D,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA6B,CAAC;AAEtD,MAAM,UAAU,UAAU,CAAI,IAAiB;IAC7C,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,UAAU,GAAsB;QACpC,EAAE,EAAE,IAAI,CAAC,EAAE;QACX,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,WAAW;KACnC,CAAC;IACF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;IAClC,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,UAAU;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAuB,EACvB,IAAO,EACP,OAAoB;IAEpB,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,SAAS;QAAE,MAAM,eAAe,CAAC,IAAI,CAAC,EAAE,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAE7F,MAAM,EAAE,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC3E,MAAM,CACJ,EAAE,CAAC,MAAM,GAAG,CAAC,EACb,QAAQ,IAAI,CAAC,EAAE,gDAAgD,EAC/D,0EAA0E,CAC3E,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,aAAa,EAAE,EAAE,EAAE,IAAI,iBAAiB,CAAC;IAClE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;QACvC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,EAAE;QACF,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,EAAE;QACF,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,EAAE;QACF,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CACxB,IAAuB,EACvB,IAAO,EACP,OAAoB;IAEpB,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAE1B,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACjD,sFAAsF;QACtF,OAAO,EAAE,GAAG,CAAC,MAAM,UAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC;QACnC,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,KAAK,EAAE,WAAW,CAAC,KAAK;QACxB,KAAK,EAAE,OAAO;QACd,cAAc,EAAE,GAAG;QACnB,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,QAAQ;QACvC,UAAU,EAAE,QAAQ;KACrB,CAAC,CAAC;IAEH,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,MAAM,EAAE,OAAO;QACf,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACxE,MAAM,EAAE,IAAI;QACZ,cAAc,EAAE,GAAG;KACpB,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,EAAU,EACV,IAAa,EACb,OAAoB;IAEpB,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS;QAAE,MAAM,eAAe,CAAC,EAAE,EAAE,iBAAiB,EAAE,CAAC,CAAC;IACvE,oFAAoF;IACpF,gEAAgE;IAChE,OAAO,MAAM,IAAI,CAAC,IAA+B,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,MAAc,EAAE,MAAe;IACnD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;AACtF,CAAC"}
package/src/render.d.ts DELETED
@@ -1,26 +0,0 @@
1
- import { type Translator } from '@ultimat3/i18n';
2
- import type { MailBlock, MailTemplate } from './blocks';
3
- /** What the renderer needs from a mail. `MailDefinition` adds the input schema on top. */
4
- export interface RenderableMail<I> {
5
- readonly id: string;
6
- readonly subject: string;
7
- readonly template: MailTemplate<I>;
8
- readonly layout: string;
9
- }
10
- export interface RenderOptions {
11
- readonly locale: string;
12
- readonly tz: string;
13
- readonly unsubscribeUrl?: string | undefined;
14
- }
15
- export interface RenderedMail {
16
- readonly subject: string;
17
- readonly preheader: string;
18
- readonly html: string;
19
- readonly text: string;
20
- }
21
- export declare const FOOTER_KEYS: readonly ['mail.footer.legal', 'mail.footer.help'];
22
- export declare const UNSUBSCRIBE_KEY = "mail.footer.unsubscribe";
23
- export declare function renderMail<I>(mail: RenderableMail<I>, data: I, options: RenderOptions): RenderedMail;
24
- /** The text part, block by block. Never derived from the HTML. */
25
- export declare function textOf(list: readonly MailBlock[], t: Translator): string;
26
- //# sourceMappingURL=render.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["render.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmC,KAAK,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AACjG,OAAO,KAAK,EAAe,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAKrE,0FAA0F;AAC1F,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,WAAW,YAAI,mBAAmB,EAAE,kBAAkB,CAAU,CAAC;AAC9E,eAAO,MAAM,eAAe,4BAA4B,CAAC;AAEzD,wBAAgB,UAAU,CAAC,CAAC,EAC1B,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EACvB,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,aAAa,GACrB,YAAY,CAmCd;AAED,kEAAkE;AAClE,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,MAAM,CAoBxE"}