@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.
- package/README.md +55 -11
- package/dist/generated/mail.d.ts +5 -0
- package/dist/generated/mail.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -15
- package/dist/index.js.map +6 -6
- package/dist/janus-mail.d.ts +1 -0
- package/dist/janus-mail.d.ts.map +1 -1
- package/dist/notice-mails.d.ts +7 -5
- package/dist/notice-mails.d.ts.map +1 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/template-types.d.ts +79 -0
- package/dist/template-types.d.ts.map +1 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/types.d.ts +17 -67
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -2
- package/docs/guide/building.md +1 -1
- package/docs/guide/locales.md +4 -3
- package/docs/guide/sending.md +66 -27
- package/docs/guide/templates.md +21 -14
- package/docs/roadmap.md +16 -4
- package/docs/troubleshooting.md +62 -15
- package/mails/en/email-changed.html +10 -9
- package/mails/en/password-changed.html +10 -9
- package/mails/en/reset-password.html +9 -8
- package/mails/en/sign-in-code.html +10 -9
- package/mails/en/two-factor-disabled.html +10 -9
- package/mails/en/two-factor-enabled.html +10 -9
- package/mails/en/verify-email.html +9 -8
- package/mails/en/welcome.html +50 -0
- package/mails/en/welcome.txt +19 -0
- package/mails/fr/email-changed.html +10 -9
- package/mails/fr/password-changed.html +10 -9
- package/mails/fr/reset-password.html +9 -8
- package/mails/fr/sign-in-code.html +10 -9
- package/mails/fr/two-factor-disabled.html +10 -9
- package/mails/fr/two-factor-enabled.html +10 -9
- package/mails/fr/verify-email.html +9 -8
- package/mails/fr/welcome.html +50 -0
- package/mails/fr/welcome.txt +19 -0
- package/mails/mail-manifest.json +24 -0
- 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,
|
|
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. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,
|
|
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
|
|
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
|
|
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/` |
|
package/docs/guide/building.md
CHANGED
|
@@ -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
|
|
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.
|
package/docs/guide/locales.md
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
|
|
122
|
+
- [Templates](templates.md) — writing the eight for a new locale.
|
package/docs/guide/sending.md
CHANGED
|
@@ -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 () => {
|
package/docs/guide/templates.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
107
|
-
another locale, it takes **all
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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
|
|
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
|
|
111
|
-
or given as a URL. The first two take the
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
+
```
|