@ultimat3/mail 1.0.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/package.json +39 -0
  4. package/src/base64.ts +9 -0
  5. package/src/blocks.d.ts +47 -0
  6. package/src/blocks.d.ts.map +1 -0
  7. package/src/blocks.js +23 -0
  8. package/src/blocks.js.map +1 -0
  9. package/src/blocks.ts +56 -0
  10. package/src/catalog.d.ts +6 -0
  11. package/src/catalog.d.ts.map +1 -0
  12. package/src/catalog.js +78 -0
  13. package/src/catalog.js.map +1 -0
  14. package/src/catalog.ts +88 -0
  15. package/src/driver-env.ts +105 -0
  16. package/src/driver-resend.ts +184 -0
  17. package/src/driver-smtp.ts +209 -0
  18. package/src/driver.d.ts +79 -0
  19. package/src/driver.d.ts.map +1 -0
  20. package/src/driver.js +115 -0
  21. package/src/driver.js.map +1 -0
  22. package/src/driver.ts +174 -0
  23. package/src/errors.d.ts +23 -0
  24. package/src/errors.d.ts.map +1 -0
  25. package/src/errors.js +82 -0
  26. package/src/errors.js.map +1 -0
  27. package/src/errors.ts +177 -0
  28. package/src/html.d.ts +10 -0
  29. package/src/html.d.ts.map +1 -0
  30. package/src/html.js +34 -0
  31. package/src/html.js.map +1 -0
  32. package/src/html.ts +37 -0
  33. package/src/idempotency.ts +61 -0
  34. package/src/index.d.ts +17 -0
  35. package/src/index.d.ts.map +1 -0
  36. package/src/index.js +13 -0
  37. package/src/index.js.map +1 -0
  38. package/src/index.ts +110 -0
  39. package/src/job.d.ts +17 -0
  40. package/src/job.d.ts.map +1 -0
  41. package/src/job.js +79 -0
  42. package/src/job.js.map +1 -0
  43. package/src/job.ts +36 -0
  44. package/src/layout.d.ts +41 -0
  45. package/src/layout.d.ts.map +1 -0
  46. package/src/layout.js +160 -0
  47. package/src/layout.js.map +1 -0
  48. package/src/layout.ts +221 -0
  49. package/src/mail.d.ts +67 -0
  50. package/src/mail.d.ts.map +1 -0
  51. package/src/mail.js +119 -0
  52. package/src/mail.js.map +1 -0
  53. package/src/mail.ts +195 -0
  54. package/src/mime.ts +233 -0
  55. package/src/render.d.ts +26 -0
  56. package/src/render.d.ts.map +1 -0
  57. package/src/render.js +149 -0
  58. package/src/render.js.map +1 -0
  59. package/src/render.ts +190 -0
  60. package/src/smtp-client.ts +246 -0
  61. package/src/smtp-protocol.ts +184 -0
  62. package/src/smtp-socket.ts +266 -0
  63. package/src/templates/index.d.ts +9 -0
  64. package/src/templates/index.d.ts.map +1 -0
  65. package/src/templates/index.js +23 -0
  66. package/src/templates/index.js.map +1 -0
  67. package/src/templates/index.ts +39 -0
  68. package/src/templates/invite.d.ts +15 -0
  69. package/src/templates/invite.d.ts.map +1 -0
  70. package/src/templates/invite.js +25 -0
  71. package/src/templates/invite.js.map +1 -0
  72. package/src/templates/invite.ts +29 -0
  73. package/src/templates/mfa-enrolled.d.ts +14 -0
  74. package/src/templates/mfa-enrolled.d.ts.map +1 -0
  75. package/src/templates/mfa-enrolled.js +26 -0
  76. package/src/templates/mfa-enrolled.js.map +1 -0
  77. package/src/templates/mfa-enrolled.ts +37 -0
  78. package/src/templates/reset-password.d.ts +13 -0
  79. package/src/templates/reset-password.d.ts.map +1 -0
  80. package/src/templates/reset-password.js +23 -0
  81. package/src/templates/reset-password.js.map +1 -0
  82. package/src/templates/reset-password.ts +27 -0
  83. package/src/templates/security-alert.d.ts +16 -0
  84. package/src/templates/security-alert.d.ts.map +1 -0
  85. package/src/templates/security-alert.js +29 -0
  86. package/src/templates/security-alert.js.map +1 -0
  87. package/src/templates/security-alert.ts +36 -0
  88. package/src/templates/verify-email.d.ts +13 -0
  89. package/src/templates/verify-email.d.ts.map +1 -0
  90. package/src/templates/verify-email.js +23 -0
  91. package/src/templates/verify-email.js.map +1 -0
  92. package/src/templates/verify-email.ts +27 -0
  93. package/src/templates/welcome.d.ts +13 -0
  94. package/src/templates/welcome.d.ts.map +1 -0
  95. package/src/templates/welcome.js +20 -0
  96. package/src/templates/welcome.js.map +1 -0
  97. package/src/templates/welcome.ts +24 -0
@@ -0,0 +1,9 @@
1
+ import type { AnyMailDefinition } from '../mail';
2
+ export { type InviteInput, inviteInput, inviteMail } from './invite';
3
+ export { MFA_METHODS, type MfaEnrolledInput, mfaEnrolledInput, mfaEnrolledMail, } from './mfa-enrolled';
4
+ export { type ResetPasswordInput, resetPasswordInput, resetPasswordMail, } from './reset-password';
5
+ export { type SecurityAlertInput, securityAlertInput, securityAlertMail, } from './security-alert';
6
+ export { type VerifyEmailInput, verifyEmailInput, verifyEmailMail } from './verify-email';
7
+ export { type WelcomeInput, welcomeInput, welcomeMail } from './welcome';
8
+ export declare const FRAMEWORK_MAILS: readonly AnyMailDefinition[];
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAQjD,OAAO,EAAE,KAAK,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,kBAAkB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzE,eAAO,MAAM,eAAe,EAAE,SAAS,iBAAiB,EAOvD,CAAC"}
@@ -0,0 +1,23 @@
1
+ // Single responsibility: the framework's own transactional mails, gathered. `FRAMEWORK_MAILS`
2
+ // is what `x mail list` prints and what the i18n audit walks for missing keys.
3
+ import { inviteMail } from './invite';
4
+ import { mfaEnrolledMail } from './mfa-enrolled';
5
+ import { resetPasswordMail } from './reset-password';
6
+ import { securityAlertMail } from './security-alert';
7
+ import { verifyEmailMail } from './verify-email';
8
+ import { welcomeMail } from './welcome';
9
+ export { inviteInput, inviteMail } from './invite';
10
+ export { MFA_METHODS, mfaEnrolledInput, mfaEnrolledMail, } from './mfa-enrolled';
11
+ export { resetPasswordInput, resetPasswordMail, } from './reset-password';
12
+ export { securityAlertInput, securityAlertMail, } from './security-alert';
13
+ export { verifyEmailInput, verifyEmailMail } from './verify-email';
14
+ export { welcomeInput, welcomeMail } from './welcome';
15
+ export const FRAMEWORK_MAILS = [
16
+ welcomeMail,
17
+ verifyEmailMail,
18
+ resetPasswordMail,
19
+ inviteMail,
20
+ mfaEnrolledMail,
21
+ securityAlertMail,
22
+ ];
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,+EAA+E;AAG/E,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAoB,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,WAAW,EAEX,gBAAgB,EAChB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAEL,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAyB,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EAAqB,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzE,MAAM,CAAC,MAAM,eAAe,GAAiC;IAC3D,WAAW;IACX,eAAe;IACf,iBAAiB;IACjB,UAAU;IACV,eAAe;IACf,iBAAiB;CAClB,CAAC"}
@@ -0,0 +1,39 @@
1
+ // Single responsibility: the framework's own transactional mails, gathered. `FRAMEWORK_MAILS`
2
+ // is what `x mail list` prints and what the i18n audit walks for missing keys.
3
+
4
+ import type { AnyMailDefinition } from '../mail';
5
+ import { inviteMail } from './invite';
6
+ import { mfaEnrolledMail } from './mfa-enrolled';
7
+ import { resetPasswordMail } from './reset-password';
8
+ import { securityAlertMail } from './security-alert';
9
+ import { verifyEmailMail } from './verify-email';
10
+ import { welcomeMail } from './welcome';
11
+
12
+ export { type InviteInput, inviteInput, inviteMail } from './invite';
13
+ export {
14
+ MFA_METHODS,
15
+ type MfaEnrolledInput,
16
+ mfaEnrolledInput,
17
+ mfaEnrolledMail,
18
+ } from './mfa-enrolled';
19
+ export {
20
+ type ResetPasswordInput,
21
+ resetPasswordInput,
22
+ resetPasswordMail,
23
+ } from './reset-password';
24
+ export {
25
+ type SecurityAlertInput,
26
+ securityAlertInput,
27
+ securityAlertMail,
28
+ } from './security-alert';
29
+ export { type VerifyEmailInput, verifyEmailInput, verifyEmailMail } from './verify-email';
30
+ export { type WelcomeInput, welcomeInput, welcomeMail } from './welcome';
31
+
32
+ export const FRAMEWORK_MAILS: readonly AnyMailDefinition[] = [
33
+ welcomeMail,
34
+ verifyEmailMail,
35
+ resetPasswordMail,
36
+ inviteMail,
37
+ mfaEnrolledMail,
38
+ securityAlertMail,
39
+ ];
@@ -0,0 +1,15 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const inviteInput: import("@ultimat3/schema").ObjectSchema<{
3
+ inviterName: import("@ultimat3/schema").StringSchema;
4
+ orgName: import("@ultimat3/schema").StringSchema;
5
+ url: import("@ultimat3/schema").StringSchema;
6
+ expiresHours: import("@ultimat3/schema").NumberSchema;
7
+ }>;
8
+ export type InviteInput = Infer<typeof inviteInput>;
9
+ export declare const inviteMail: import("..").MailDefinition<{
10
+ expiresHours: number;
11
+ inviterName: string;
12
+ orgName: string;
13
+ url: string;
14
+ }>;
15
+ //# sourceMappingURL=invite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invite.d.ts","sourceRoot":"","sources":["invite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAIjD,eAAO,MAAM,WAAW;;;;;EAKtB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEpD,eAAO,MAAM,UAAU;;;;;EAarB,CAAC"}
@@ -0,0 +1,25 @@
1
+ // Single responsibility: the org-invitation mail.
2
+ import { t } from '@ultimat3/schema';
3
+ import { blocks } from '../blocks';
4
+ import { defineMail } from '../mail';
5
+ export const inviteInput = t.object({
6
+ inviterName: t.string,
7
+ orgName: t.string,
8
+ url: t.url,
9
+ expiresHours: t.number.int().min(1),
10
+ });
11
+ export const inviteMail = defineMail({
12
+ id: 'invite',
13
+ subject: 'mail.invite.subject',
14
+ input: inviteInput,
15
+ template: ({ data }) => [
16
+ blocks.heading('mail.invite.heading', { orgName: data.orgName }),
17
+ blocks.paragraph('mail.invite.body', {
18
+ inviterName: data.inviterName,
19
+ orgName: data.orgName,
20
+ }),
21
+ blocks.button('mail.invite.cta', data.url),
22
+ blocks.paragraph('mail.invite.expiry', { count: data.expiresHours }),
23
+ ],
24
+ });
25
+ //# sourceMappingURL=invite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invite.js","sourceRoot":"","sources":["invite.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAElD,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,WAAW,EAAE,CAAC,CAAC,MAAM;IACrB,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,GAAG,EAAE,CAAC,CAAC,GAAG;IACV,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACpC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAc;IAChD,EAAE,EAAE,QAAQ;IACZ,OAAO,EAAE,qBAAqB;IAC9B,KAAK,EAAE,WAAW;IAClB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAChE,MAAM,CAAC,SAAS,CAAC,kBAAkB,EAAE;YACnC,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC;QAC1C,MAAM,CAAC,SAAS,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;KACrE;CACF,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ // Single responsibility: the org-invitation mail.
2
+
3
+ import { type Infer, t } from '@ultimat3/schema';
4
+ import { blocks } from '../blocks';
5
+ import { defineMail } from '../mail';
6
+
7
+ export const inviteInput = t.object({
8
+ inviterName: t.string,
9
+ orgName: t.string,
10
+ url: t.url,
11
+ expiresHours: t.number.int().min(1),
12
+ });
13
+
14
+ export type InviteInput = Infer<typeof inviteInput>;
15
+
16
+ export const inviteMail = defineMail<InviteInput>({
17
+ id: 'invite',
18
+ subject: 'mail.invite.subject',
19
+ input: inviteInput,
20
+ template: ({ data }) => [
21
+ blocks.heading('mail.invite.heading', { orgName: data.orgName }),
22
+ blocks.paragraph('mail.invite.body', {
23
+ inviterName: data.inviterName,
24
+ orgName: data.orgName,
25
+ }),
26
+ blocks.button('mail.invite.cta', data.url),
27
+ blocks.paragraph('mail.invite.expiry', { count: data.expiresHours }),
28
+ ],
29
+ });
@@ -0,0 +1,14 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const MFA_METHODS: readonly ['totp', 'webauthn', 'sms'];
3
+ export declare const mfaEnrolledInput: import("@ultimat3/schema").ObjectSchema<{
4
+ name: import("@ultimat3/schema").StringSchema;
5
+ method: import("@ultimat3/schema").Schema<"sms" | "totp" | "webauthn", "sms" | "totp" | "webauthn">;
6
+ at: import("@ultimat3/schema").Schema<string | number | Date, Date>;
7
+ }>;
8
+ export type MfaEnrolledInput = Infer<typeof mfaEnrolledInput>;
9
+ export declare const mfaEnrolledMail: import("..").MailDefinition<{
10
+ at: Date;
11
+ method: "sms" | "totp" | "webauthn";
12
+ name: string;
13
+ }>;
14
+ //# sourceMappingURL=mfa-enrolled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa-enrolled.d.ts","sourceRoot":"","sources":["mfa-enrolled.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAKjD,eAAO,MAAM,WAAW,YAAI,MAAM,EAAE,UAAU,EAAE,KAAK,CAAU,CAAC;AAEhE,eAAO,MAAM,gBAAgB;;;;EAI3B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE9D,eAAO,MAAM,eAAe;;;;EAkB1B,CAAC"}
@@ -0,0 +1,26 @@
1
+ // Single responsibility: the "a second factor was added" notice. The enrolment time is
2
+ // formatted in the recipient's zone — "at 03:12" is only useful if it is their 03:12.
3
+ import { t } from '@ultimat3/schema';
4
+ import { formatDateTime, instant } from '@ultimat3/time';
5
+ import { blocks } from '../blocks';
6
+ import { defineMail } from '../mail';
7
+ export const MFA_METHODS = ['totp', 'webauthn', 'sms'];
8
+ export const mfaEnrolledInput = t.object({
9
+ name: t.string,
10
+ method: t.enum(MFA_METHODS),
11
+ at: t.date,
12
+ });
13
+ export const mfaEnrolledMail = defineMail({
14
+ id: 'mfa-enrolled',
15
+ subject: 'mail.mfa-enrolled.subject',
16
+ input: mfaEnrolledInput,
17
+ template: ({ data, t: translate, locale, tz }) => [
18
+ blocks.heading('mail.mfa-enrolled.heading', { name: data.name }),
19
+ blocks.paragraph('mail.mfa-enrolled.body'),
20
+ blocks.detail('mail.mfa-enrolled.method-label', translate(`mail.mfa-enrolled.method-${data.method}`)),
21
+ blocks.detail('mail.mfa-enrolled.at-label', formatDateTime(instant(data.at), { locale, zone: tz, style: 'medium' })),
22
+ blocks.divider(),
23
+ blocks.paragraph('mail.mfa-enrolled.help'),
24
+ ],
25
+ });
26
+ //# sourceMappingURL=mfa-enrolled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa-enrolled.js","sourceRoot":"","sources":["mfa-enrolled.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,sFAAsF;AAEtF,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAU,CAAC;AAEhE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM;IACd,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;IAC3B,EAAE,EAAE,CAAC,CAAC,IAAI;CACX,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAmB;IAC1D,EAAE,EAAE,cAAc;IAClB,OAAO,EAAE,2BAA2B;IACpC,KAAK,EAAE,gBAAgB;IACvB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;QAChD,MAAM,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAChE,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC;QAC1C,MAAM,CAAC,MAAM,CACX,gCAAgC,EAChC,SAAS,CAAC,4BAA4B,IAAI,CAAC,MAAM,EAAE,CAAC,CACrD;QACD,MAAM,CAAC,MAAM,CACX,4BAA4B,EAC5B,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CACxE;QACD,MAAM,CAAC,OAAO,EAAE;QAChB,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC;KAC3C;CACF,CAAC,CAAC"}
@@ -0,0 +1,37 @@
1
+ // Single responsibility: the "a second factor was added" notice. The enrolment time is
2
+ // formatted in the recipient's zone — "at 03:12" is only useful if it is their 03:12.
3
+
4
+ import { type Infer, t } from '@ultimat3/schema';
5
+ import { formatDateTime, instant } from '@ultimat3/time';
6
+ import { blocks } from '../blocks';
7
+ import { defineMail } from '../mail';
8
+
9
+ export const MFA_METHODS = ['totp', 'webauthn', 'sms'] as const;
10
+
11
+ export const mfaEnrolledInput = t.object({
12
+ name: t.string,
13
+ method: t.enum(MFA_METHODS),
14
+ at: t.date,
15
+ });
16
+
17
+ export type MfaEnrolledInput = Infer<typeof mfaEnrolledInput>;
18
+
19
+ export const mfaEnrolledMail = defineMail<MfaEnrolledInput>({
20
+ id: 'mfa-enrolled',
21
+ subject: 'mail.mfa-enrolled.subject',
22
+ input: mfaEnrolledInput,
23
+ template: ({ data, t: translate, locale, tz }) => [
24
+ blocks.heading('mail.mfa-enrolled.heading', { name: data.name }),
25
+ blocks.paragraph('mail.mfa-enrolled.body'),
26
+ blocks.detail(
27
+ 'mail.mfa-enrolled.method-label',
28
+ translate(`mail.mfa-enrolled.method-${data.method}`),
29
+ ),
30
+ blocks.detail(
31
+ 'mail.mfa-enrolled.at-label',
32
+ formatDateTime(instant(data.at), { locale, zone: tz, style: 'medium' }),
33
+ ),
34
+ blocks.divider(),
35
+ blocks.paragraph('mail.mfa-enrolled.help'),
36
+ ],
37
+ });
@@ -0,0 +1,13 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const resetPasswordInput: import("@ultimat3/schema").ObjectSchema<{
3
+ name: import("@ultimat3/schema").StringSchema;
4
+ url: import("@ultimat3/schema").StringSchema;
5
+ expiresMinutes: import("@ultimat3/schema").NumberSchema;
6
+ }>;
7
+ export type ResetPasswordInput = Infer<typeof resetPasswordInput>;
8
+ export declare const resetPasswordMail: import("..").MailDefinition<{
9
+ expiresMinutes: number;
10
+ name: string;
11
+ url: string;
12
+ }>;
13
+ //# sourceMappingURL=reset-password.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["reset-password.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAIjD,eAAO,MAAM,kBAAkB;;;;EAI7B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAElE,eAAO,MAAM,iBAAiB;;;;EAW5B,CAAC"}
@@ -0,0 +1,23 @@
1
+ // Single responsibility: the password-reset mail. The "you did not ask for this" line is not
2
+ // optional politeness — it is how a targeted reset attempt gets reported.
3
+ import { t } from '@ultimat3/schema';
4
+ import { blocks } from '../blocks';
5
+ import { defineMail } from '../mail';
6
+ export const resetPasswordInput = t.object({
7
+ name: t.string,
8
+ url: t.url,
9
+ expiresMinutes: t.number.int().min(1),
10
+ });
11
+ export const resetPasswordMail = defineMail({
12
+ id: 'reset-password',
13
+ subject: 'mail.reset-password.subject',
14
+ input: resetPasswordInput,
15
+ template: ({ data }) => [
16
+ blocks.heading('mail.reset-password.heading', { name: data.name }),
17
+ blocks.paragraph('mail.reset-password.body'),
18
+ blocks.button('mail.reset-password.cta', data.url),
19
+ blocks.paragraph('mail.reset-password.expiry', { count: data.expiresMinutes }),
20
+ blocks.paragraph('mail.reset-password.ignore'),
21
+ ],
22
+ });
23
+ //# sourceMappingURL=reset-password.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-password.js","sourceRoot":"","sources":["reset-password.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,0EAA0E;AAE1E,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM;IACd,GAAG,EAAE,CAAC,CAAC,GAAG;IACV,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAqB;IAC9D,EAAE,EAAE,gBAAgB;IACpB,OAAO,EAAE,6BAA6B;IACtC,KAAK,EAAE,kBAAkB;IACzB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,6BAA6B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAClE,MAAM,CAAC,SAAS,CAAC,0BAA0B,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,yBAAyB,EAAE,IAAI,CAAC,GAAG,CAAC;QAClD,MAAM,CAAC,SAAS,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;QAC9E,MAAM,CAAC,SAAS,CAAC,4BAA4B,CAAC;KAC/C;CACF,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ // Single responsibility: the password-reset mail. The "you did not ask for this" line is not
2
+ // optional politeness — it is how a targeted reset attempt gets reported.
3
+
4
+ import { type Infer, t } from '@ultimat3/schema';
5
+ import { blocks } from '../blocks';
6
+ import { defineMail } from '../mail';
7
+
8
+ export const resetPasswordInput = t.object({
9
+ name: t.string,
10
+ url: t.url,
11
+ expiresMinutes: t.number.int().min(1),
12
+ });
13
+
14
+ export type ResetPasswordInput = Infer<typeof resetPasswordInput>;
15
+
16
+ export const resetPasswordMail = defineMail<ResetPasswordInput>({
17
+ id: 'reset-password',
18
+ subject: 'mail.reset-password.subject',
19
+ input: resetPasswordInput,
20
+ template: ({ data }) => [
21
+ blocks.heading('mail.reset-password.heading', { name: data.name }),
22
+ blocks.paragraph('mail.reset-password.body'),
23
+ blocks.button('mail.reset-password.cta', data.url),
24
+ blocks.paragraph('mail.reset-password.expiry', { count: data.expiresMinutes }),
25
+ blocks.paragraph('mail.reset-password.ignore'),
26
+ ],
27
+ });
@@ -0,0 +1,16 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const securityAlertInput: import("@ultimat3/schema").ObjectSchema<{
3
+ name: import("@ultimat3/schema").StringSchema;
4
+ /** A machine event id (`session.new-device`), not prose. */
5
+ event: import("@ultimat3/schema").StringSchema;
6
+ ip: import("@ultimat3/schema").StringSchema;
7
+ at: import("@ultimat3/schema").Schema<string | number | Date, Date>;
8
+ }>;
9
+ export type SecurityAlertInput = Infer<typeof securityAlertInput>;
10
+ export declare const securityAlertMail: import("..").MailDefinition<{
11
+ at: Date;
12
+ event: string;
13
+ ip: string;
14
+ name: string;
15
+ }>;
16
+ //# sourceMappingURL=security-alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-alert.d.ts","sourceRoot":"","sources":["security-alert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAKjD,eAAO,MAAM,kBAAkB;;IAE7B,4DAA4D;;;;EAI5D,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAElE,eAAO,MAAM,iBAAiB;;;;;EAgB5B,CAAC"}
@@ -0,0 +1,29 @@
1
+ // Single responsibility: the security-alert mail — the one transactional mail that is allowed
2
+ // to alarm. Event, IP and time are rendered as details, never woven into a sentence, so they
3
+ // survive translation intact.
4
+ import { t } from '@ultimat3/schema';
5
+ import { formatDateTime, instant } from '@ultimat3/time';
6
+ import { blocks } from '../blocks';
7
+ import { defineMail } from '../mail';
8
+ export const securityAlertInput = t.object({
9
+ name: t.string,
10
+ /** A machine event id (`session.new-device`), not prose. */
11
+ event: t.string,
12
+ ip: t.string,
13
+ at: t.date,
14
+ });
15
+ export const securityAlertMail = defineMail({
16
+ id: 'security-alert',
17
+ subject: 'mail.security-alert.subject',
18
+ input: securityAlertInput,
19
+ template: ({ data, locale, tz }) => [
20
+ blocks.heading('mail.security-alert.heading', { name: data.name }),
21
+ blocks.callout('mail.security-alert.alert', 'danger'),
22
+ blocks.detail('mail.security-alert.event-label', data.event),
23
+ blocks.detail('mail.security-alert.ip-label', data.ip),
24
+ blocks.detail('mail.security-alert.at-label', formatDateTime(instant(data.at), { locale, zone: tz, style: 'medium' })),
25
+ blocks.divider(),
26
+ blocks.paragraph('mail.security-alert.help'),
27
+ ],
28
+ });
29
+ //# sourceMappingURL=security-alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-alert.js","sourceRoot":"","sources":["security-alert.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,6FAA6F;AAC7F,8BAA8B;AAE9B,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM;IACd,4DAA4D;IAC5D,KAAK,EAAE,CAAC,CAAC,MAAM;IACf,EAAE,EAAE,CAAC,CAAC,MAAM;IACZ,EAAE,EAAE,CAAC,CAAC,IAAI;CACX,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAqB;IAC9D,EAAE,EAAE,gBAAgB;IACpB,OAAO,EAAE,6BAA6B;IACtC,KAAK,EAAE,kBAAkB;IACzB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,6BAA6B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAClE,MAAM,CAAC,OAAO,CAAC,2BAA2B,EAAE,QAAQ,CAAC;QACrD,MAAM,CAAC,MAAM,CAAC,iCAAiC,EAAE,IAAI,CAAC,KAAK,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,8BAA8B,EAAE,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,CAAC,MAAM,CACX,8BAA8B,EAC9B,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CACxE;QACD,MAAM,CAAC,OAAO,EAAE;QAChB,MAAM,CAAC,SAAS,CAAC,0BAA0B,CAAC;KAC7C;CACF,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ // Single responsibility: the security-alert mail — the one transactional mail that is allowed
2
+ // to alarm. Event, IP and time are rendered as details, never woven into a sentence, so they
3
+ // survive translation intact.
4
+
5
+ import { type Infer, t } from '@ultimat3/schema';
6
+ import { formatDateTime, instant } from '@ultimat3/time';
7
+ import { blocks } from '../blocks';
8
+ import { defineMail } from '../mail';
9
+
10
+ export const securityAlertInput = t.object({
11
+ name: t.string,
12
+ /** A machine event id (`session.new-device`), not prose. */
13
+ event: t.string,
14
+ ip: t.string,
15
+ at: t.date,
16
+ });
17
+
18
+ export type SecurityAlertInput = Infer<typeof securityAlertInput>;
19
+
20
+ export const securityAlertMail = defineMail<SecurityAlertInput>({
21
+ id: 'security-alert',
22
+ subject: 'mail.security-alert.subject',
23
+ input: securityAlertInput,
24
+ template: ({ data, locale, tz }) => [
25
+ blocks.heading('mail.security-alert.heading', { name: data.name }),
26
+ blocks.callout('mail.security-alert.alert', 'danger'),
27
+ blocks.detail('mail.security-alert.event-label', data.event),
28
+ blocks.detail('mail.security-alert.ip-label', data.ip),
29
+ blocks.detail(
30
+ 'mail.security-alert.at-label',
31
+ formatDateTime(instant(data.at), { locale, zone: tz, style: 'medium' }),
32
+ ),
33
+ blocks.divider(),
34
+ blocks.paragraph('mail.security-alert.help'),
35
+ ],
36
+ });
@@ -0,0 +1,13 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const verifyEmailInput: import("@ultimat3/schema").ObjectSchema<{
3
+ name: import("@ultimat3/schema").StringSchema;
4
+ url: import("@ultimat3/schema").StringSchema;
5
+ expiresMinutes: import("@ultimat3/schema").NumberSchema;
6
+ }>;
7
+ export type VerifyEmailInput = Infer<typeof verifyEmailInput>;
8
+ export declare const verifyEmailMail: import("..").MailDefinition<{
9
+ expiresMinutes: number;
10
+ name: string;
11
+ url: string;
12
+ }>;
13
+ //# sourceMappingURL=verify-email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-email.d.ts","sourceRoot":"","sources":["verify-email.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAIjD,eAAO,MAAM,gBAAgB;;;;EAI3B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE9D,eAAO,MAAM,eAAe;;;;EAW1B,CAAC"}
@@ -0,0 +1,23 @@
1
+ // Single responsibility: the email-verification mail. The expiry line is a plural key, so a
2
+ // 1-minute link never reads "1 minutes".
3
+ import { t } from '@ultimat3/schema';
4
+ import { blocks } from '../blocks';
5
+ import { defineMail } from '../mail';
6
+ export const verifyEmailInput = t.object({
7
+ name: t.string,
8
+ url: t.url,
9
+ expiresMinutes: t.number.int().min(1),
10
+ });
11
+ export const verifyEmailMail = defineMail({
12
+ id: 'verify-email',
13
+ subject: 'mail.verify-email.subject',
14
+ input: verifyEmailInput,
15
+ template: ({ data }) => [
16
+ blocks.heading('mail.verify-email.heading', { name: data.name }),
17
+ blocks.paragraph('mail.verify-email.body'),
18
+ blocks.button('mail.verify-email.cta', data.url),
19
+ blocks.paragraph('mail.verify-email.expiry', { count: data.expiresMinutes }),
20
+ blocks.paragraph('mail.verify-email.ignore'),
21
+ ],
22
+ });
23
+ //# sourceMappingURL=verify-email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-email.js","sourceRoot":"","sources":["verify-email.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,yCAAyC;AAEzC,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM;IACd,GAAG,EAAE,CAAC,CAAC,GAAG;IACV,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAmB;IAC1D,EAAE,EAAE,cAAc;IAClB,OAAO,EAAE,2BAA2B;IACpC,KAAK,EAAE,gBAAgB;IACvB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAChE,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,uBAAuB,EAAE,IAAI,CAAC,GAAG,CAAC;QAChD,MAAM,CAAC,SAAS,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;QAC5E,MAAM,CAAC,SAAS,CAAC,0BAA0B,CAAC;KAC7C;CACF,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ // Single responsibility: the email-verification mail. The expiry line is a plural key, so a
2
+ // 1-minute link never reads "1 minutes".
3
+
4
+ import { type Infer, t } from '@ultimat3/schema';
5
+ import { blocks } from '../blocks';
6
+ import { defineMail } from '../mail';
7
+
8
+ export const verifyEmailInput = t.object({
9
+ name: t.string,
10
+ url: t.url,
11
+ expiresMinutes: t.number.int().min(1),
12
+ });
13
+
14
+ export type VerifyEmailInput = Infer<typeof verifyEmailInput>;
15
+
16
+ export const verifyEmailMail = defineMail<VerifyEmailInput>({
17
+ id: 'verify-email',
18
+ subject: 'mail.verify-email.subject',
19
+ input: verifyEmailInput,
20
+ template: ({ data }) => [
21
+ blocks.heading('mail.verify-email.heading', { name: data.name }),
22
+ blocks.paragraph('mail.verify-email.body'),
23
+ blocks.button('mail.verify-email.cta', data.url),
24
+ blocks.paragraph('mail.verify-email.expiry', { count: data.expiresMinutes }),
25
+ blocks.paragraph('mail.verify-email.ignore'),
26
+ ],
27
+ });
@@ -0,0 +1,13 @@
1
+ import { type Infer } from '@ultimat3/schema';
2
+ export declare const welcomeInput: import("@ultimat3/schema").ObjectSchema<{
3
+ name: import("@ultimat3/schema").StringSchema;
4
+ appName: import("@ultimat3/schema").StringSchema;
5
+ url: import("@ultimat3/schema").StringSchema;
6
+ }>;
7
+ export type WelcomeInput = Infer<typeof welcomeInput>;
8
+ export declare const welcomeMail: import("..").MailDefinition<{
9
+ appName: string;
10
+ name: string;
11
+ url: string;
12
+ }>;
13
+ //# sourceMappingURL=welcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["welcome.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAK,MAAM,kBAAkB,CAAC;AAIjD,eAAO,MAAM,YAAY;;;;EAIvB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEtD,eAAO,MAAM,WAAW;;;;EAStB,CAAC"}
@@ -0,0 +1,20 @@
1
+ // Single responsibility: the welcome mail, as data. Keys only — the words live in a catalog.
2
+ import { t } from '@ultimat3/schema';
3
+ import { blocks } from '../blocks';
4
+ import { defineMail } from '../mail';
5
+ export const welcomeInput = t.object({
6
+ name: t.string,
7
+ appName: t.string,
8
+ url: t.url,
9
+ });
10
+ export const welcomeMail = defineMail({
11
+ id: 'welcome',
12
+ subject: 'mail.welcome.subject',
13
+ input: welcomeInput,
14
+ template: ({ data }) => [
15
+ blocks.heading('mail.welcome.heading', { name: data.name }),
16
+ blocks.paragraph('mail.welcome.body', { appName: data.appName }),
17
+ blocks.button('mail.welcome.cta', data.url, { appName: data.appName }),
18
+ ],
19
+ });
20
+ //# sourceMappingURL=welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["welcome.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAE7F,OAAO,EAAc,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,MAAM;IACd,OAAO,EAAE,CAAC,CAAC,MAAM;IACjB,GAAG,EAAE,CAAC,CAAC,GAAG;CACX,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAe;IAClD,EAAE,EAAE,SAAS;IACb,OAAO,EAAE,sBAAsB;IAC/B,KAAK,EAAE,YAAY;IACnB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtB,MAAM,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3D,MAAM,CAAC,SAAS,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;KACvE;CACF,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ // Single responsibility: the welcome mail, as data. Keys only — the words live in a catalog.
2
+
3
+ import { type Infer, t } from '@ultimat3/schema';
4
+ import { blocks } from '../blocks';
5
+ import { defineMail } from '../mail';
6
+
7
+ export const welcomeInput = t.object({
8
+ name: t.string,
9
+ appName: t.string,
10
+ url: t.url,
11
+ });
12
+
13
+ export type WelcomeInput = Infer<typeof welcomeInput>;
14
+
15
+ export const welcomeMail = defineMail<WelcomeInput>({
16
+ id: 'welcome',
17
+ subject: 'mail.welcome.subject',
18
+ input: welcomeInput,
19
+ template: ({ data }) => [
20
+ blocks.heading('mail.welcome.heading', { name: data.name }),
21
+ blocks.paragraph('mail.welcome.body', { appName: data.appName }),
22
+ blocks.button('mail.welcome.cta', data.url, { appName: data.appName }),
23
+ ],
24
+ });