@nxgt/janus-mail 0.3.1 → 0.4.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 (44) hide show
  1. package/README.md +55 -11
  2. package/dist/generated/mail.d.ts +5 -0
  3. package/dist/generated/mail.d.ts.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +19 -15
  6. package/dist/index.js.map +6 -6
  7. package/dist/janus-mail.d.ts +1 -0
  8. package/dist/janus-mail.d.ts.map +1 -1
  9. package/dist/notice-mails.d.ts +7 -5
  10. package/dist/notice-mails.d.ts.map +1 -1
  11. package/dist/options.d.ts.map +1 -1
  12. package/dist/template-types.d.ts +79 -0
  13. package/dist/template-types.d.ts.map +1 -0
  14. package/dist/templates.d.ts +1 -1
  15. package/dist/templates.d.ts.map +1 -1
  16. package/dist/types.d.ts +17 -67
  17. package/dist/types.d.ts.map +1 -1
  18. package/docs/README.md +3 -2
  19. package/docs/guide/building.md +1 -1
  20. package/docs/guide/locales.md +4 -3
  21. package/docs/guide/sending.md +66 -27
  22. package/docs/guide/templates.md +21 -14
  23. package/docs/roadmap.md +16 -4
  24. package/docs/troubleshooting.md +62 -15
  25. package/mails/en/email-changed.html +10 -9
  26. package/mails/en/password-changed.html +10 -9
  27. package/mails/en/reset-password.html +9 -8
  28. package/mails/en/sign-in-code.html +10 -9
  29. package/mails/en/two-factor-disabled.html +10 -9
  30. package/mails/en/two-factor-enabled.html +10 -9
  31. package/mails/en/verify-email.html +9 -8
  32. package/mails/en/welcome.html +50 -0
  33. package/mails/en/welcome.txt +19 -0
  34. package/mails/fr/email-changed.html +10 -9
  35. package/mails/fr/password-changed.html +10 -9
  36. package/mails/fr/reset-password.html +9 -8
  37. package/mails/fr/sign-in-code.html +10 -9
  38. package/mails/fr/two-factor-disabled.html +10 -9
  39. package/mails/fr/two-factor-enabled.html +10 -9
  40. package/mails/fr/verify-email.html +9 -8
  41. package/mails/fr/welcome.html +50 -0
  42. package/mails/fr/welcome.txt +19 -0
  43. package/mails/mail-manifest.json +24 -0
  44. package/package.json +6 -5
package/dist/types.d.ts CHANGED
@@ -4,75 +4,11 @@
4
4
  * once, in `janusMail()`.
5
5
  */
6
6
  import type { Clock, IssuedCode, IssuedToken } from '@nxgt/janus';
7
- import type { Address, Mailer, Rendered, SentMail, WantedLocales } from '@nxgt/mail';
7
+ import type { Address, Mailer, SentMail, WantedLocales } from '@nxgt/mail';
8
8
  import type { JanusMailLocale } from './generated/locales';
9
+ import type { JanusMailTemplates } from './template-types';
9
10
  export type { JanusMailLocale } from './generated/locales';
10
- /**
11
- * What each e-mail's template is given, by template name. Every value is
12
- * text: `brand` is the name `janusMail({ brand })` was given, `name` the
13
- * recipient's, `link` an absolute URL from `links`, and `expiresIn` how long
14
- * the link or the code stays valid, in the recipient's locale — "1 hour",
15
- * "1 heure" — or the send's own `expiresIn`.
16
- *
17
- * Held equal to the build's variables by `test/types/variables.ts`: an e-mail
18
- * of the build that gains or loses a variable fails the typecheck there.
19
- */
20
- export interface JanusMailVariables {
21
- readonly verifyEmail: {
22
- readonly brand: string;
23
- readonly name: string;
24
- readonly link: string;
25
- readonly expiresIn: string;
26
- };
27
- readonly resetPassword: {
28
- readonly brand: string;
29
- readonly name: string;
30
- readonly link: string;
31
- readonly expiresIn: string;
32
- };
33
- /** No `challenge`, ever: it is the visitor's secret, and never goes in an e-mail. */
34
- readonly signInCode: {
35
- readonly brand: string;
36
- readonly code: string;
37
- readonly expiresIn: string;
38
- };
39
- readonly passwordChanged: {
40
- readonly brand: string;
41
- readonly name: string;
42
- readonly link: string;
43
- };
44
- readonly emailChanged: {
45
- readonly brand: string;
46
- readonly name: string;
47
- readonly link: string;
48
- readonly newEmail: string;
49
- };
50
- readonly twoFactorEnabled: {
51
- readonly brand: string;
52
- readonly name: string;
53
- readonly link: string;
54
- };
55
- readonly twoFactorDisabled: {
56
- readonly brand: string;
57
- readonly name: string;
58
- readonly link: string;
59
- };
60
- }
61
- /** The name of one of the seven templates: `verifyEmail`, `resetPassword`, … */
62
- export type JanusMailTemplateName = keyof JanusMailVariables;
63
- /**
64
- * One e-mail: its variables and the locale picked for the recipient, in; its
65
- * `subject`, `html` and `text`, out — escaping is then the function's job. A
66
- * default, from the prebuilt e-mails, or yours: React Email, a string, or
67
- * another Maizzle build.
68
- */
69
- export type JanusMailTemplate<V, L extends string = JanusMailLocale> = (variables: V & {
70
- readonly locale: L;
71
- }) => Rendered | PromiseLike<Rendered>;
72
- /** The seven templates, each for the locales `L`. */
73
- export type JanusMailTemplates<L extends string = JanusMailLocale> = {
74
- readonly [K in JanusMailTemplateName]: JanusMailTemplate<JanusMailVariables[K], L>;
75
- };
11
+ export type { JanusMailTemplate, JanusMailTemplateName, JanusMailTemplates, JanusMailVariables, } from './template-types';
76
12
  /**
77
13
  * Who an e-mail is for, besides the address: the name it greets, and the
78
14
  * locales they want, most wanted first — their stored locale, then their
@@ -105,6 +41,12 @@ export interface JanusMailLinks {
105
41
  * and `twoFactorDisabled` link to it.
106
42
  */
107
43
  readonly secureAccount: () => string;
44
+ /**
45
+ * Where a new user starts — your application's home, or its sign-in page
46
+ * for an account someone else created. `welcome` links to it, from its
47
+ * "Get started" button.
48
+ */
49
+ readonly getStarted: () => string;
108
50
  }
109
51
  /** The options every locale set shares. */
110
52
  interface JanusMailBaseOptions<L extends string> {
@@ -196,6 +138,14 @@ export interface JanusMail<L extends string = JanusMailLocale> {
196
138
  twoFactorDisabled(to: Recipient & {
197
139
  readonly email: string;
198
140
  }): Promise<SentMail>;
141
+ /**
142
+ * Welcomes `to.email` to the brand, with a link to get started — on the
143
+ * `user.created` event, which `auth.create` and `auth.signUp` send once
144
+ * the user is inserted.
145
+ */
146
+ welcome(to: Recipient & {
147
+ readonly email: string;
148
+ }): Promise<SentMail>;
199
149
  /** The templates in use: the defaults, with `templates` over them. */
200
150
  readonly templates: JanusMailTemplates<L>;
201
151
  /** The locales sent in. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/docs/README.md CHANGED
@@ -5,7 +5,7 @@ the detail.
5
5
 
6
6
  | Page | Read it when |
7
7
  | --- | --- |
8
- | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options — `clock` among them — each method, the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
8
+ | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the welcome on `user.created` among them; the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
9
9
  | [Templates](guide/templates.md) | You want an e-mail of your own: what a template takes and answers, a partial override, `janusTemplates()`, and what the defaults say |
10
10
  | [Locales](guide/locales.md) | You send in more than one language: how the locale is picked, `fallbackLocale`, fewer locales, and adding one |
11
11
  | [Building](guide/building.md) | You work on this package: the Maizzle project in `mail/`, `mails/`, the generated files, and why none of it reaches a consumer's build |
@@ -23,13 +23,14 @@ They mean the same here. These pages add:
23
23
 
24
24
  | Word | Means | Not |
25
25
  | --- | --- | --- |
26
- | **e-mail** | One of the seven this package sends: `verify-email`, `reset-password`, `sign-in-code`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
26
+ | **e-mail** | One of the eight this package sends: `verify-email`, `reset-password`, `sign-in-code`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
27
27
  | **template** | The function that renders one e-mail: `(variables & { locale }) => Rendered`. Named in camelCase, after the method that sends it: `verifyEmail`, `signInCode` | "renderer" — the renderer is `@nxgt/mail`'s, and reads the build |
28
28
  | **default template** | A template of `janusTemplates()`: the prebuilt e-mail, filled by the renderer | "preset" — a preset is `@nxgt/mail-presets`' source, which the build compiles |
29
29
  | **override** | A template of yours, passed in `templates`, used instead of the default | "custom template" |
30
30
  | **issued** | What a flow answered: an `IssuedToken` from `verifyEmail.send` or `resetPassword.request`, an `IssuedCode` from `signInCode.request` | "result", "payload" |
31
31
  | **recipient** | Who an e-mail is for besides the address: a `Recipient`, `{ name, locale? }` | "user" — a user is a record of `@nxgt/janus`; "to" is the address |
32
32
  | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`. It links to `links.secureAccount()` | "alert", "notification" |
33
+ | **welcome** | The e-mail sent once a user was created, on `@nxgt/janus`'s `user.created`: `welcome`. It links to `links.getStarted()` — not a notice, since it warns of nothing | "onboarding", "greeting" |
33
34
  | **expiry** | How long a link or a code lasts, as the e-mail says it: `expiresIn`, derived from the flow's `expiresAt` in the recipient's locale — "1 hour", "1 heure" — or passed per send | "TTL", "lifetime" — the `tokens` TTL is `@nxgt/janus`'s setting, which sets `expiresAt` |
34
35
  | **brand** | The name the e-mails show, `janusMail({ brand })`: text, filled at send time | "logo", "company" |
35
36
  | **the build** | The e-mails as `scripts/build-mail.ts` writes them to `mails/`: HTML and text per locale, and the manifest | "dist" — `mails/` is not `dist/` |
@@ -43,7 +43,7 @@ packages/janus-mail/
43
43
  `src/generated/mail.ts` (`rendererTypes`), from the build.
44
44
  3. `scripts/build-mail.ts` reads the manifest and **fails unless its
45
45
  format is one every `@nxgt/mail` the peer admits reads** — see below —
46
- and **unless exactly the seven e-mails were built**: an eighth from a new
46
+ and **unless exactly the eight e-mails were built**: a ninth from a new
47
47
  `@nxgt/mail-presets`, or one missing, stops the build.
48
48
  4. It writes `src/generated/locales.ts` from the manifest's locales, only
49
49
  when its content changed.
@@ -85,6 +85,7 @@ const templates: JanusMailTemplates<Locale> = {
85
85
  emailChanged: ({ name, link, newEmail, locale }) => render('email-changed', locale, { name, link, newEmail }),
86
86
  twoFactorEnabled: ({ name, link, locale }) => render('two-factor-enabled', locale, { name, link }),
87
87
  twoFactorDisabled: ({ name, link, locale }) => render('two-factor-disabled', locale, { name, link }),
88
+ welcome: ({ name, link, locale }) => render('welcome', locale, { name, link }),
88
89
  };
89
90
 
90
91
  const mail = janusMail({ mailer, from, brand: 'Acme', links, locales: ['en', 'fr', 'de'], templates });
@@ -99,13 +100,13 @@ With only some templates, the call does not compile — the error lands on
99
100
  `templates`:
100
101
 
101
102
  ```text
102
- TS2739: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled
103
+ TS2739: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, welcome
103
104
  ```
104
105
 
105
106
  and in JavaScript it throws when `janusMail()` is called:
106
107
 
107
108
  ```text
108
- TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled missing
109
+ TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, welcome missing
109
110
  ```
110
111
 
111
112
  A default template is typed for `'en' | 'fr'`, so reusing one in a wider set
@@ -118,4 +119,4 @@ Built-in locales for more languages are on the [roadmap](../roadmap.md).
118
119
 
119
120
  - `@nxgt/mail`'s [locales guide](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail/docs/guide/locales.md)
120
121
  — `pickLocale` and `parseAcceptLanguage` in full.
121
- - [Templates](templates.md) — writing the seven for a new locale.
122
+ - [Templates](templates.md) — writing the eight for a new locale.
@@ -26,6 +26,7 @@ export const mail = janusMail({
26
26
  verifyEmail: (token) => `https://acme.example/verify?token=${encodeURIComponent(token)}`,
27
27
  resetPassword: (token) => `https://acme.example/reset?token=${encodeURIComponent(token)}`,
28
28
  secureAccount: () => 'https://acme.example/account/security',
29
+ getStarted: () => 'https://acme.example/',
29
30
  },
30
31
  });
31
32
  ```
@@ -45,6 +46,7 @@ time one is sent, and kept.
45
46
  | `links.verifyEmail` | `(token) => string` | required | The page that confirms an address, given the one-time token |
46
47
  | `links.resetPassword` | `(token) => string` | required | The page that sets a new password, given the one-time token |
47
48
  | `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it |
49
+ | `links.getStarted` | `() => string` | required | Where a new user starts — your home page, or your sign-in page for an account someone else created: the welcome's **Get started** button links to it |
48
50
  | `locales` | `readonly L[]` | `['en', 'fr']` | The locales sent in — see [Locales](locales.md) |
49
51
  | `fallbackLocale` | one of `locales` | `'en'`, else the first of `locales` | The locale when the recipient wants none of `locales` |
50
52
  | `templates` | `Partial<JanusMailTemplates<L>>` | none | Your own templates, over the defaults — see [Templates](templates.md) |
@@ -134,6 +136,68 @@ since whoever changed it already controls the new one. It names `newEmail`,
134
136
  and links to `links.secureAccount()`. Read the former address before the
135
137
  update: the user `update` answers already holds the new one.
136
138
 
139
+ ### `twoFactorEnabled(to)` and `twoFactorDisabled(to)`
140
+
141
+ ```ts
142
+ // On the user events @nxgt/janus sends once the write landed:
143
+ const auth = janus({
144
+ ...config,
145
+ async events(event) {
146
+ if (event.type !== 'user.secondFactorEnabled' && event.type !== 'user.secondFactorDisabled') return;
147
+ const user = await auth.get(event.userId);
148
+ const to = { name: user.name, locale: user.locale, email: user.email };
149
+ await (event.type === 'user.secondFactorEnabled' ? mail.twoFactorEnabled(to) : mail.twoFactorDisabled(to));
150
+ },
151
+ });
152
+ ```
153
+
154
+ Two notices: two-factor authentication was turned on, or off, for the
155
+ account at `to.email`, each with a link to `links.secureAccount()` — the
156
+ user's security settings — for a user who did not make the change. Send
157
+ `twoFactorEnabled` on the `user.secondFactorEnabled` event, which
158
+ `auth.secondFactor.activate` sends once the factor is active, and
159
+ `twoFactorDisabled` on `user.secondFactorDisabled`, which
160
+ `auth.secondFactor.disable` sends only when it removed an active factor: a
161
+ `disable` on a user who had none tells nobody anything. The event names the
162
+ user by id alone, so read the name, the locale and the address from the
163
+ user. Calling them right after `activate` or `disable` answered works as
164
+ well — without the events, check `hasSecondFactor` before the `disable`.
165
+
166
+ ### `welcome(to)`
167
+
168
+ ```ts
169
+ // On the user event @nxgt/janus sends once the user is inserted:
170
+ const auth = janus({
171
+ ...config,
172
+ async events(event) {
173
+ if (event.type !== 'user.created') return;
174
+ const user = await auth.get(event.userId);
175
+ await mail.welcome({ name: user.name, locale: user.locale, email: user.email });
176
+ },
177
+ });
178
+ ```
179
+
180
+ Welcomes the new user at `to.email` — "Welcome, Ada", "Bienvenue, Ada", the
181
+ name in the subject too — with a **Get started** button that links to
182
+ `links.getStarted()`: your home page, or your sign-in page when the account
183
+ was created for the user rather than by them. Send it on the `user.created`
184
+ event, which `auth.signUp` and `auth.create` send once the user is inserted —
185
+ even when `signUp`'s session then fails to open — and never for a sign-up
186
+ refused. The event names the user by id alone, so read the name, the locale
187
+ and the address from the user. With several user types, check
188
+ `event.userType` first: a type with no e-mail has nobody to welcome.
189
+
190
+ - **It goes out before the address is verified.** A sign-up with a mistyped
191
+ address welcomes whoever holds it, and with open sign-up anyone can make
192
+ your brand send "Welcome, <any name>" to any address. To welcome proven addresses only, send
193
+ it on `user.emailVerified` instead, sent once the address is proven —
194
+ and again after an address changed and was proven anew, so welcome only
195
+ a user you have not welcomed before.
196
+ - **A failed welcome fails no sign-up.** `janus()` awaits the listener, but a
197
+ listener that throws is a `JANUS_EVENT_FAILED` warning: the user exists,
198
+ and `signUp` answers as it would have. For a welcome that must arrive, put
199
+ the event in a queue that retries a `MAIL_FAILED`, and send from there.
200
+
137
201
  ## The expiry
138
202
 
139
203
  `verifyEmail`, `resetPassword` and `signInCode` say how long the link or the
@@ -178,33 +242,6 @@ through JSON on its way to a queue holds a string — or is already past, is
178
242
  a `TypeError`, and nothing reaches the mailer: revive the date with
179
243
  `new Date(issued.expiresAt)` when the job runs, or pass `expiresIn`.
180
244
 
181
- ### `twoFactorEnabled(to)` and `twoFactorDisabled(to)`
182
-
183
- ```ts
184
- // On the user events @nxgt/janus sends once the write landed:
185
- const auth = janus({
186
- ...config,
187
- async events(event) {
188
- if (event.type !== 'user.secondFactorEnabled' && event.type !== 'user.secondFactorDisabled') return;
189
- const user = await auth.get(event.userId);
190
- const to = { name: user.name, locale: user.locale, email: user.email };
191
- await (event.type === 'user.secondFactorEnabled' ? mail.twoFactorEnabled(to) : mail.twoFactorDisabled(to));
192
- },
193
- });
194
- ```
195
-
196
- Two notices: two-factor authentication was turned on, or off, for the
197
- account at `to.email`, each with a link to `links.secureAccount()` — the
198
- user's security settings — for a user who did not make the change. Send
199
- `twoFactorEnabled` on the `user.secondFactorEnabled` event, which
200
- `auth.secondFactor.activate` sends once the factor is active, and
201
- `twoFactorDisabled` on `user.secondFactorDisabled`, which
202
- `auth.secondFactor.disable` sends only when it removed an active factor: a
203
- `disable` on a user who had none tells nobody anything. The event names the
204
- user by id alone, so read the name, the locale and the address from the
205
- user. Calling them right after `activate` or `disable` answered works as
206
- well — without the events, check `hasSecondFactor` before the `disable`.
207
-
208
245
  ## Where each e-mail goes
209
246
 
210
247
  | Method | To | Never to |
@@ -215,6 +252,7 @@ well — without the events, check `hasSecondFactor` before the `disable`.
215
252
  | `passwordChanged` | `to.email` | — |
216
253
  | `emailChanged` | `to.formerEmail` | `to.newEmail` |
217
254
  | `twoFactorEnabled`, `twoFactorDisabled` | `to.email` | — |
255
+ | `welcome` | `to.email` | — |
218
256
 
219
257
  `issued.email` is the address the user's record holds, as they registered
220
258
  it; what the visitor typed may differ in case or spacing. The recipient is a
@@ -322,6 +360,7 @@ const links = {
322
360
  verifyEmail: (token: string) => `https://acme.example/verify?token=${token}`,
323
361
  resetPassword: (token: string) => `https://acme.example/reset?token=${token}`,
324
362
  secureAccount: () => 'https://acme.example/account/security',
363
+ getStarted: () => 'https://acme.example/',
325
364
  };
326
365
 
327
366
  it('sends the code, and never the challenge', async () => {
@@ -1,7 +1,7 @@
1
1
  # Templates
2
2
 
3
3
  This page is for changing what an e-mail says or looks like: what a template
4
- is given and answers, replacing some of the seven, using the defaults on their
4
+ is given and answers, replacing some of the eight, using the defaults on their
5
5
  own, and what the defaults say.
6
6
 
7
7
  ```ts
@@ -22,7 +22,7 @@ const mail = janusMail({
22
22
  });
23
23
  ```
24
24
 
25
- The sign-in code e-mail is now yours; the six others stay the defaults.
25
+ The sign-in code e-mail is now yours; the seven others stay the defaults.
26
26
 
27
27
  ## What a template is
28
28
 
@@ -66,10 +66,12 @@ e-mail.
66
66
  | `emailChanged` | `brand`, `name`, `link`, `newEmail` | `mail.emailChanged(to)` |
67
67
  | `twoFactorEnabled` | `brand`, `name`, `link` | `mail.twoFactorEnabled(to)` |
68
68
  | `twoFactorDisabled` | `brand`, `name`, `link` | `mail.twoFactorDisabled(to)` |
69
+ | `welcome` | `brand`, `name`, `link` | `mail.welcome(to)` |
69
70
 
70
71
  Every one also gets `locale`, one of `locales`. Every value is a string:
71
72
  `brand` from the options, `name` from the recipient, `link` from `links` —
72
- `links.secureAccount()` for the four notices — `code` and `newEmail` from the
73
+ `links.secureAccount()` for the four notices, `links.getStarted()` for
74
+ `welcome` — `code` and `newEmail` from the
73
75
  call, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
74
76
  the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
75
77
 
@@ -103,11 +105,11 @@ methods live on its prototype and are refused — see
103
105
  [troubleshooting](../troubleshooting.md#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-).
104
106
 
105
107
  `templates` is `Partial` while `locales` stays within `en` and `fr`: give
106
- any of the seven, and the defaults render the rest. Once `locales` holds
107
- another locale, it takes **all seven** — see
108
+ any of the eight, and the defaults render the rest. Once `locales` holds
109
+ another locale, it takes **all eight** — see
108
110
  [Adding a locale](locales.md#adding-a-locale).
109
111
 
110
- `mail.templates` answers the seven in use — yours, and the defaults for the
112
+ `mail.templates` answers the eight in use — yours, and the defaults for the
111
113
  rest — frozen:
112
114
 
113
115
  ```ts
@@ -129,7 +131,7 @@ const { subject, html, text } = await defaults.resetPassword({
129
131
  });
130
132
  ```
131
133
 
132
- It answers the seven defaults, typed for `'en' | 'fr'`, without a mailer:
134
+ It answers the eight defaults, typed for `'en' | 'fr'`, without a mailer:
133
135
  to preview an e-mail, to wrap one — add a line to the default text, say — or
134
136
  to send one through something other than `janusMail()`. A default reused for
135
137
  a wider set of locales is a compile error: it could not render the one it
@@ -155,7 +157,7 @@ them renders: that is when the package's `mails/` folder is read, once.
155
157
  ## What the defaults say
156
158
 
157
159
  The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail-presets)
158
- 0.4.1, built with a neutral grey theme and no logo:
160
+ 0.4.2, built with a neutral grey theme and no logo:
159
161
 
160
162
  | Template | Built from | Subject (`en`) | Subject (`fr`) |
161
163
  | --- | --- | --- | --- |
@@ -166,11 +168,14 @@ The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail
166
168
  | `emailChanged` | `email-changed` | Your e-mail address was changed | Votre adresse e-mail a été modifiée |
167
169
  | `twoFactorEnabled` | `two-factor-enabled` | Two-factor authentication was turned on | L'authentification à deux facteurs a été activée |
168
170
  | `twoFactorDisabled` | `two-factor-disabled` | Two-factor authentication was turned off | L'authentification à deux facteurs a été désactivée |
171
+ | `welcome` | `welcome` | Welcome, `Ada` | Bienvenue, `Ada` |
169
172
 
170
173
  Each has a header and a footer with the brand, a heading, a greeting by name
171
174
  (but `signInCode`), a button with its link and the link again in text for a
172
175
  client that shows no button, and a text part. The four notices add a warning:
173
- *if this was not you, secure your account now*.
176
+ *if this was not you, secure your account now*. `welcome` has the brand in
177
+ its heading — "Welcome to Acme" — and the recipient's name in its subject,
178
+ filled at send time like the body; its button says **Get started**.
174
179
 
175
180
  `verifyEmail`, `resetPassword` and `signInCode` also say how long the link
176
181
  or the code lasts: "This link expires in 1 hour.", "Ce code expire dans
@@ -184,11 +189,13 @@ is `@nxgt/mail-ui`'s default, a deep navy, but for the primary: the button
184
189
  is near-black (`#27272a`) with light text in light mode, and near-white
185
190
  (`#fafafa`) with near-black text (`#18181b`) in dark mode — 17:1 against the
186
191
  dark card, where the light primary would be 1.2:1. The page behind the card
187
- follows it, 5% of the dark primary over the dark background, so it sits
188
- close to the card, which its border outlines. The sign-in code's box is
189
- `@nxgt/mail-ui`'s muted grey, which has no dark value: it stays a light box
190
- with dark text in both modes. Nothing is passed for any of it, the text part
191
- has no colours, and a template of your own gets none of it. `@nxgt/mail-ui`'s
192
+ is 5% of the light primary over the dark background, a shade darker than the
193
+ card, which its border outlines. The sign-in code's box is a light grey
194
+ (`#f1f5f9`) in light mode and a mid slate (`#94a3b8`) in dark mode, 6.95:1
195
+ against the dark card; the code on it stays near-black (`#020918`) in both,
196
+ 7.76:1 over the dark box. The footer's muted text keeps its grey (`#62748e`)
197
+ in both modes. Nothing is passed for any of it, the text part has no
198
+ colours, and a template of your own gets none of it. `@nxgt/mail-ui`'s
192
199
  [Dark mode](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-ui/docs/guide/dark-mode.md)
193
200
  guide has the technique, client by client.
194
201
 
package/docs/roadmap.md CHANGED
@@ -9,10 +9,9 @@ Nothing between releases.
9
9
 
10
10
  ## Next
11
11
 
12
- - **The other presets** — `magic-link`, `new-sign-in`, `welcome` and
13
- `invitation` from `@nxgt/mail-presets`, each once `@nxgt/janus` has a flow
14
- that sends it: a sign-in link, a sign-in from a new device, a user created,
15
- an invitation to a user type.
12
+ - **The other presets** — `magic-link`, `new-sign-in` and `invitation` from
13
+ `@nxgt/mail-presets`, each once `@nxgt/janus` has a flow that sends it: a
14
+ sign-in link, a sign-in from a new device, an invitation to a user type.
16
15
  - **The other presets of `@nxgt/mail-presets` 0.4.0** — `account-deleted`,
17
16
  after a user is deleted, once `@nxgt/janus` has a deletion a link can
18
17
  undo; and `invitation-accepted`, once it has invitations.
@@ -48,6 +47,19 @@ Nothing between releases.
48
47
 
49
48
  Newest first; from the first release on, the package's CHANGELOG holds every one.
50
49
 
50
+ - **The welcome e-mail, v0.4.0.** `welcome(to)`, built from
51
+ `@nxgt/mail-presets` 0.4.2's `welcome` in English and French — "Welcome,
52
+ Ada" — with a **Get started** button linking to the new
53
+ `links.getStarted()`: send it on `@nxgt/janus`'s `user.created` event.
54
+ `links` now requires `getStarted`, and a locale beyond `en` and `fr` takes
55
+ eight templates.
56
+
57
+ - **A sign-in code box that is not a light slab in dark mode, v0.3.2.** Under
58
+ dark mode the code's box is a mid slate, 6.95:1 against the dark card, with
59
+ the near-black code at 7.76:1 on it, and the page behind the card is darker
60
+ again; light mode, the text parts, subjects and variables are unchanged.
61
+ Built from `@nxgt/mail-ui` 0.6.0 and `@nxgt/mail-presets` 0.4.2; the
62
+ manifest is still format 1, and the `@nxgt/mail` peer stays `>=0.1.0 <1`.
51
63
  - **A button that shows in dark mode, v0.3.1.** Under dark mode the button
52
64
  is near-white with near-black text, no longer a near-black one that all but
53
65
  vanished on the dark card; light mode, the text parts, subjects and
@@ -25,14 +25,14 @@ How the messages are shaped:
25
25
  - [`janusMail: from must be an address, as 'noreply@example.com' or { name, address }`](#janusmail-from-must-be-an-address-as-noreplyexamplecom-or--name-address-)
26
26
  - [`janusMail: replyTo must be an address, as 'support@example.com' or { name, address }`](#janusmail-replyto-must-be-an-address-as-supportexamplecom-or--name-address-)
27
27
  - [`janusMail: brand must be the name the e-mails show, as 'Acme'`](#janusmail-brand-must-be-the-name-the-e-mails-show-as-acme)
28
- - [`janusMail: links must be an object, as { verifyEmail, resetPassword, secureAccount }`](#janusmail-links-must-be-an-object-as--verifyemail-resetpassword-secureaccount-)
28
+ - [`janusMail: links must be an object, as { verifyEmail, resetPassword, secureAccount, getStarted }`](#janusmail-links-must-be-an-object-as--verifyemail-resetpassword-secureaccount-getstarted-)
29
29
  - [`janusMail: links.<name> must be a function`](#janusmail-linksname-must-be-a-function)
30
30
  - [`janusMail: locales must list at least one locale, as ['en', 'fr']`](#janusmail-locales-must-list-at-least-one-locale-as-en-fr)
31
31
  - [`janusMail: locales must be BCP 47 language tags, as 'fr-CA'`](#janusmail-locales-must-be-bcp-47-language-tags-as-fr-ca)
32
32
  - [`janusMail: locales holds the same locale twice`](#janusmail-locales-holds-the-same-locale-twice)
33
33
  - [`janusMail: fallbackLocale must be one of locales`](#janusmail-fallbacklocale-must-be-one-of-locales)
34
34
  - [`janusMail: templates must be an object of functions, as { verifyEmail: (variables) => rendered }`](#janusmail-templates-must-be-an-object-of-functions-as--verifyemail-variables--rendered-)
35
- - [`janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled`](#janusmail-templates-has-no-template-name--name-one-of-verifyemail-resetpassword-signincode-passwordchanged-emailchanged-twofactorenabled-twofactordisabled)
35
+ - [`janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, welcome`](#janusmail-templates-has-no-template-name--name-one-of-verifyemail-resetpassword-signincode-passwordchanged-emailchanged-twofactorenabled-twofactordisabled-welcome)
36
36
  - [`janusMail: templates.<name> must be a function`](#janusmail-templatesname-must-be-a-function)
37
37
  - [`janusMail: templates.<name> is not an own enumerable property — pass a plain object, as { <name>: (variables) => rendered }`](#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-)
38
38
  - [`janusMail: clock must be a Clock — an object with a now function`](#janusmail-clock-must-be-a-clock--an-object-with-a-now-function)
@@ -53,12 +53,14 @@ How the messages are shaped:
53
53
  - [`Could not resolve "node:fs"` on an edge runtime](#could-not-resolve-nodefs-on-an-edge-runtime)
54
54
  - [An e-mail stays light in dark mode](#an-e-mail-stays-light-in-dark-mode)
55
55
  - [The button all but vanishes in dark mode](#the-button-all-but-vanishes-in-dark-mode)
56
+ - [The sign-in code's box stays a light slab in dark mode](#the-sign-in-codes-box-stays-a-light-slab-in-dark-mode)
56
57
 
57
58
  **Compile errors**
58
59
  - [`TS2345: Argument of type '(IssuedToken & { user: … }) | null' is not assignable to parameter of type 'IssuedToken'.`](#ts2345-argument-of-type-issuedtoken---user----null-is-not-assignable-to-parameter-of-type-issuedtoken)
59
60
  - [`TS2345: … Types of property 'expiresAt' are incompatible. Type 'string' is not assignable to type 'Date'.`](#ts2345--types-of-property-expiresat-are-incompatible-type-string-is-not-assignable-to-type-date)
60
61
  - [`TS2739: Type '{ … }' is missing the following properties from type 'JanusMailTemplates<…>'`](#ts2739-type----is-missing-the-following-properties-from-type-janusmailtemplates)
61
62
  - [`TS2322: Type '"de"' is not assignable to type '"en" | "fr"'.`](#ts2322-type-de-is-not-assignable-to-type-en--fr)
63
+ - [`TS2741: Property 'getStarted' is missing in type '{ … }' but required in type 'JanusMailLinks'.`](#ts2741-property-getstarted-is-missing-in-type----but-required-in-type-janusmaillinks)
62
64
 
63
65
  ---
64
66
 
@@ -101,24 +103,32 @@ No `brand`, a blank one, or not a string — `{ name: 'Acme' }`, the shape of
101
103
  janusMail({ mailer, from, brand: 'Acme', links });
102
104
  ```
103
105
 
104
- ### `janusMail: links must be an object, as { verifyEmail, resetPassword, secureAccount }`
106
+ ### `janusMail: links must be an object, as { verifyEmail, resetPassword, secureAccount, getStarted }`
105
107
 
106
- No `links`. All three are required: the notices link to `secureAccount`.
108
+ No `links`. All four are required: the notices link to `secureAccount`, and
109
+ the welcome to `getStarted`.
107
110
 
108
111
  ### `janusMail: links.<name> must be a function`
109
112
 
110
- `links.verifyEmail`, `links.resetPassword` or `links.secureAccount` missing,
111
- or given as a URL. The first two take the one-time token; the third takes
112
- nothing:
113
+ `links.verifyEmail`, `links.resetPassword`, `links.secureAccount` or
114
+ `links.getStarted` missing, or given as a URL. The first two take the
115
+ one-time token; the other two take nothing:
113
116
 
114
117
  ```ts
115
118
  links: {
116
119
  verifyEmail: (token) => `https://acme.example/verify?token=${encodeURIComponent(token)}`,
117
120
  resetPassword: (token) => `https://acme.example/reset?token=${encodeURIComponent(token)}`,
118
121
  secureAccount: () => 'https://acme.example/account/security',
122
+ getStarted: () => 'https://acme.example/',
119
123
  },
120
124
  ```
121
125
 
126
+ **`links.getStarted must be a function` right after an upgrade** is the
127
+ link the welcome e-mail added in 0.4.0: `links` written for 0.3 has no
128
+ `getStarted`. Add it — where a new user starts, your home page or your
129
+ sign-in page — even if you never send the welcome. In TypeScript the same
130
+ `links` is a compile error on `links`, `Property 'getStarted' is missing`.
131
+
122
132
  ### `janusMail: locales must list at least one locale, as ['en', 'fr']`
123
133
 
124
134
  `locales: []`, `locales: 'en'`, or a list holding something that is not a
@@ -151,9 +161,9 @@ A `fallbackLocale` the list does not hold — `'de'` with the default
151
161
 
152
162
  `templates` given as a list or a function. Key each template by its name.
153
163
 
154
- ### `janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled`
164
+ ### `janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, welcome`
155
165
 
156
- A key that is not one of the seven — `welcome`, or the e-mail's file name
166
+ A key that is not one of the eight — `magicLink`, or the e-mail's file name
157
167
  `'verify-email'` rather than the template's, `verifyEmail`. Other e-mails are
158
168
  on the [roadmap](roadmap.md); send them with `@nxgt/mail` directly meanwhile.
159
169
 
@@ -187,7 +197,7 @@ as it is: fields are own properties.
187
197
  ### `janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; <names> missing`
188
198
 
189
199
  `locales` holds a locale beyond `en` and `fr`, and `templates` leaves some
190
- out: the defaults could not render them in that locale. Pass all seven — see
200
+ out: the defaults could not render them in that locale. Pass all eight — see
191
201
  [Adding a locale](guide/locales.md#adding-a-locale) — or drop the locale.
192
202
  The message lists the templates missing.
193
203
 
@@ -225,8 +235,8 @@ string` is the third argument's `expiresIn` given as something else — a
225
235
  number of seconds: pass the text to show, `{ expiresIn: '1 hour' }`, or
226
236
  leave it out.
227
237
 
228
- `janusMail.twoFactorEnabled: email must be a string` (or `twoFactorDisabled`)
229
- is the user event itself given to the notice: an event names the user by id
238
+ `janusMail.twoFactorEnabled: email must be a string` (or `twoFactorDisabled`,
239
+ or `welcome`) is the user event itself given to the e-mail: an event names the user by id
230
240
  alone. Read the user first:
231
241
 
232
242
  ```ts
@@ -307,7 +317,8 @@ sends a user who did not make the change to your support desk.
307
317
  A `TypeError` from a call, before anything is rendered or sent
308
318
  (`mailer.attempts` stays 0) — `janusMail.verifyEmail: links.verifyEmail(token)
309
319
  must answer a string`, `janusMail.emailChanged: links.secureAccount() must
310
- answer a string`. The `links` function answered something other than a
320
+ answer a string`, `janusMail.welcome: links.getStarted() must answer a
321
+ string`. The `links` function answered something other than a
311
322
  string: a `URL` object, `undefined`, or a promise — an `async` function.
312
323
  Answer `url.href`, and compute the link synchronously:
313
324
 
@@ -383,7 +394,7 @@ avoids the read altogether. (In this repository: run `bun run build`.)
383
394
 
384
395
  The default templates read `mails/` with `node:fs`, through
385
396
  `@nxgt/mail/renderer`: Node, Bun or Deno only. On an edge runtime, pass all
386
- seven templates yourself; an inlined build is on the [roadmap](roadmap.md).
397
+ eight templates yourself; an inlined build is on the [roadmap](roadmap.md).
387
398
 
388
399
  ### An e-mail stays light in dark mode
389
400
 
@@ -421,6 +432,26 @@ ui({
421
432
  });
422
433
  ```
423
434
 
435
+ ### The sign-in code's box stays a light slab in dark mode
436
+
437
+ A `signInCode` template of your own, built with `@nxgt/mail-ui`'s `NxCode`,
438
+ keeps its light box (`#f1f5f9`) on the dark card for a reader in dark mode;
439
+ the defaults turn theirs a mid slate there since 0.3.2. The box is muted,
440
+ which keeps its light value under dark mode unless the build gives it a dark
441
+ one. Build the override with `@nxgt/mail-ui` 0.6.0 or later and set
442
+ `color-muted-dark`. The code's text stays the light foreground (`#020918`) in
443
+ both modes, so a dark muted such as `#1e293b` leaves it at 1.36:1: pick one
444
+ light enough for that text, as the defaults do:
445
+
446
+ ```ts
447
+ ui({
448
+ brand: { name: 'Acme' },
449
+ theme: {
450
+ 'color-muted-dark': '#94a3b8', // the code at 7.76:1 on it, 6.95:1 against the dark card
451
+ },
452
+ });
453
+ ```
454
+
424
455
  ## Compile errors
425
456
 
426
457
  ### `TS2345: Argument of type '(IssuedToken & { user: … }) | null' is not assignable to parameter of type 'IssuedToken'.`
@@ -449,9 +480,25 @@ whole.
449
480
  ### `TS2739: Type '{ … }' is missing the following properties from type 'JanusMailTemplates<…>'`
450
481
 
451
482
  `locales` holds a locale beyond `en` and `fr`, and `templates` does not give
452
- all seven. See [Adding a locale](guide/locales.md#adding-a-locale).
483
+ all eight. See [Adding a locale](guide/locales.md#adding-a-locale).
453
484
 
454
485
  ### `TS2322: Type '"de"' is not assignable to type '"en" | "fr"'.`
455
486
 
456
487
  A `fallbackLocale` outside `locales`. Add it to `locales` — with every
457
488
  template, if it is not `en` or `fr` — or pick one of them.
489
+
490
+ ### `TS2741: Property 'getStarted' is missing in type '{ … }' but required in type 'JanusMailLinks'.`
491
+
492
+ `links` written for 0.3, before the welcome e-mail: since 0.4.0 it takes
493
+ `getStarted` too, where the welcome's **Get started** button leads. Add it,
494
+ even if you never send the welcome — in JavaScript the same `links` is
495
+ [`janusMail: links.<name> must be a function`](#janusmail-linksname-must-be-a-function):
496
+
497
+ ```ts
498
+ links: {
499
+ verifyEmail: (token) => `https://acme.example/verify?token=${encodeURIComponent(token)}`,
500
+ resetPassword: (token) => `https://acme.example/reset?token=${encodeURIComponent(token)}`,
501
+ secureAccount: () => 'https://acme.example/account/security',
502
+ getStarted: () => 'https://acme.example/',
503
+ },
504
+ ```