@nxgt/janus-mail 0.2.1 → 0.3.1
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 +46 -12
- package/dist/generated/mail.d.ts +10 -0
- package/dist/generated/mail.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +5 -5
- package/dist/notice-mails.d.ts +5 -5
- package/dist/notice-mails.d.ts.map +1 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/types.d.ts +33 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -3
- package/docs/guide/building.md +7 -5
- package/docs/guide/locales.md +5 -3
- package/docs/guide/sending.md +65 -0
- package/docs/guide/templates.md +29 -9
- package/docs/roadmap.md +24 -1
- package/docs/troubleshooting.md +57 -7
- package/mails/en/email-changed.html +23 -13
- package/mails/en/password-changed.html +23 -13
- package/mails/en/reset-password.html +21 -12
- package/mails/en/sign-in-code.html +21 -10
- package/mails/en/two-factor-disabled.html +51 -0
- package/mails/en/two-factor-disabled.txt +21 -0
- package/mails/en/two-factor-enabled.html +51 -0
- package/mails/en/two-factor-enabled.txt +21 -0
- package/mails/en/verify-email.html +21 -12
- package/mails/fr/email-changed.html +24 -14
- package/mails/fr/password-changed.html +23 -13
- package/mails/fr/reset-password.html +22 -12
- package/mails/fr/sign-in-code.html +19 -10
- package/mails/fr/two-factor-disabled.html +51 -0
- package/mails/fr/two-factor-disabled.txt +21 -0
- package/mails/fr/two-factor-enabled.html +51 -0
- package/mails/fr/two-factor-enabled.txt +21 -0
- package/mails/fr/verify-email.html +22 -12
- package/mails/mail-manifest.json +48 -0
- package/package.json +8 -8
package/docs/guide/sending.md
CHANGED
|
@@ -178,6 +178,33 @@ through JSON on its way to a queue holds a string — or is already past, is
|
|
|
178
178
|
a `TypeError`, and nothing reaches the mailer: revive the date with
|
|
179
179
|
`new Date(issued.expiresAt)` when the job runs, or pass `expiresIn`.
|
|
180
180
|
|
|
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
|
+
|
|
181
208
|
## Where each e-mail goes
|
|
182
209
|
|
|
183
210
|
| Method | To | Never to |
|
|
@@ -187,6 +214,7 @@ a `TypeError`, and nothing reaches the mailer: revive the date with
|
|
|
187
214
|
| `signInCode` | `issued.email` | the `email` passed to `request` |
|
|
188
215
|
| `passwordChanged` | `to.email` | — |
|
|
189
216
|
| `emailChanged` | `to.formerEmail` | `to.newEmail` |
|
|
217
|
+
| `twoFactorEnabled`, `twoFactorDisabled` | `to.email` | — |
|
|
190
218
|
|
|
191
219
|
`issued.email` is the address the user's record holds, as they registered
|
|
192
220
|
it; what the visitor typed may differ in case or spacing. The recipient is a
|
|
@@ -238,6 +266,43 @@ advises sending *after* answering, so the time an answer takes does not tell
|
|
|
238
266
|
whether the address exists: hand the send to a queue that awaits it and
|
|
239
267
|
retries a `MAIL_FAILED`, rather than to a promise nobody awaits.
|
|
240
268
|
|
|
269
|
+
### Retrying and tracing the mailer
|
|
270
|
+
|
|
271
|
+
From `@nxgt/mail` 0.8, wrap the mailer before you hand it to `janusMail()`:
|
|
272
|
+
`withRetry` retries a `MailFailure` with backoff, reusing one idempotency
|
|
273
|
+
key for every attempt, and never retries a `MailRefused`; `withTelemetry`
|
|
274
|
+
from `@nxgt/mail/telemetry` opens a `mail.send` span per send. That entry
|
|
275
|
+
needs `@opentelemetry/api` installed (an optional peer, loaded by it alone);
|
|
276
|
+
with no OpenTelemetry SDK registered, its spans are no-ops. Put telemetry on the outside, so one
|
|
277
|
+
send is one span:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { janusMail } from '@nxgt/janus-mail';
|
|
281
|
+
import { withRetry } from '@nxgt/mail';
|
|
282
|
+
import { withTelemetry as withMailTelemetry } from '@nxgt/mail/telemetry';
|
|
283
|
+
import { createSmtpMailer } from '@nxgt/mail-smtp';
|
|
284
|
+
|
|
285
|
+
export const mail = janusMail({
|
|
286
|
+
mailer: withMailTelemetry(withRetry(createSmtpMailer({ transporter })), { transport: 'smtp' }),
|
|
287
|
+
from: 'noreply@acme.example',
|
|
288
|
+
brand: 'Acme',
|
|
289
|
+
links, // transporter and links: as in the example at the top
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Alias it.** `@nxgt/telemetry` — which `@nxgt/janus-telemetry` peers — also
|
|
294
|
+
exports a `withTelemetry`, `withTelemetry(telemetry, fn)`, which scopes a
|
|
295
|
+
telemetry to a block. The two are unrelated: `mail.send` is recorded
|
|
296
|
+
through OpenTelemetry, not `@nxgt/telemetry`, so it does not nest under
|
|
297
|
+
`@nxgt/janus-telemetry`'s spans. Import `@nxgt/mail`'s under another name,
|
|
298
|
+
as above, when a module uses both.
|
|
299
|
+
|
|
300
|
+
**SMTP ignores the idempotency key**, so a retry after an ambiguous SMTP
|
|
301
|
+
timeout can deliver an e-mail twice. If a duplicate sign-in code or reset
|
|
302
|
+
link is not acceptable, leave an SMTP mailer unretried (`{ attempts: 1 }`,
|
|
303
|
+
or no `withRetry`). See `@nxgt/mail`'s
|
|
304
|
+
[sending guide](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail/docs/guide/sending.md#retrying--withretry).
|
|
305
|
+
|
|
241
306
|
## Testing
|
|
242
307
|
|
|
243
308
|
`createMemoryMailer()` from `@nxgt/mail` keeps an outbox, and can be told to
|
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 seven, 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 six others stay the defaults.
|
|
26
26
|
|
|
27
27
|
## What a template is
|
|
28
28
|
|
|
@@ -64,10 +64,12 @@ e-mail.
|
|
|
64
64
|
| `signInCode` | `brand`, `code`, `expiresIn` | `mail.signInCode(issued, to?, options?)` |
|
|
65
65
|
| `passwordChanged` | `brand`, `name`, `link` | `mail.passwordChanged(to)` |
|
|
66
66
|
| `emailChanged` | `brand`, `name`, `link`, `newEmail` | `mail.emailChanged(to)` |
|
|
67
|
+
| `twoFactorEnabled` | `brand`, `name`, `link` | `mail.twoFactorEnabled(to)` |
|
|
68
|
+
| `twoFactorDisabled` | `brand`, `name`, `link` | `mail.twoFactorDisabled(to)` |
|
|
67
69
|
|
|
68
70
|
Every one also gets `locale`, one of `locales`. Every value is a string:
|
|
69
71
|
`brand` from the options, `name` from the recipient, `link` from `links` —
|
|
70
|
-
`links.secureAccount()` for the
|
|
72
|
+
`links.secureAccount()` for the four notices — `code` and `newEmail` from the
|
|
71
73
|
call, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
|
|
72
74
|
the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
|
|
73
75
|
|
|
@@ -101,11 +103,11 @@ methods live on its prototype and are refused — see
|
|
|
101
103
|
[troubleshooting](../troubleshooting.md#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-).
|
|
102
104
|
|
|
103
105
|
`templates` is `Partial` while `locales` stays within `en` and `fr`: give
|
|
104
|
-
any of the
|
|
105
|
-
another locale, it takes **all
|
|
106
|
+
any of the seven, and the defaults render the rest. Once `locales` holds
|
|
107
|
+
another locale, it takes **all seven** — see
|
|
106
108
|
[Adding a locale](locales.md#adding-a-locale).
|
|
107
109
|
|
|
108
|
-
`mail.templates` answers the
|
|
110
|
+
`mail.templates` answers the seven in use — yours, and the defaults for the
|
|
109
111
|
rest — frozen:
|
|
110
112
|
|
|
111
113
|
```ts
|
|
@@ -127,7 +129,7 @@ const { subject, html, text } = await defaults.resetPassword({
|
|
|
127
129
|
});
|
|
128
130
|
```
|
|
129
131
|
|
|
130
|
-
It answers the
|
|
132
|
+
It answers the seven defaults, typed for `'en' | 'fr'`, without a mailer:
|
|
131
133
|
to preview an e-mail, to wrap one — add a line to the default text, say — or
|
|
132
134
|
to send one through something other than `janusMail()`. A default reused for
|
|
133
135
|
a wider set of locales is a compile error: it could not render the one it
|
|
@@ -153,7 +155,7 @@ them renders: that is when the package's `mails/` folder is read, once.
|
|
|
153
155
|
## What the defaults say
|
|
154
156
|
|
|
155
157
|
The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail-presets)
|
|
156
|
-
0.
|
|
158
|
+
0.4.1, built with a neutral grey theme and no logo:
|
|
157
159
|
|
|
158
160
|
| Template | Built from | Subject (`en`) | Subject (`fr`) |
|
|
159
161
|
| --- | --- | --- | --- |
|
|
@@ -162,16 +164,34 @@ The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail
|
|
|
162
164
|
| `signInCode` | `sign-in-code` | Your sign-in code: `042817` | Votre code de connexion : `042817` |
|
|
163
165
|
| `passwordChanged` | `password-changed` | Your password was changed | Votre mot de passe a été modifié |
|
|
164
166
|
| `emailChanged` | `email-changed` | Your e-mail address was changed | Votre adresse e-mail a été modifiée |
|
|
167
|
+
| `twoFactorEnabled` | `two-factor-enabled` | Two-factor authentication was turned on | L'authentification à deux facteurs a été activée |
|
|
168
|
+
| `twoFactorDisabled` | `two-factor-disabled` | Two-factor authentication was turned off | L'authentification à deux facteurs a été désactivée |
|
|
165
169
|
|
|
166
170
|
Each has a header and a footer with the brand, a heading, a greeting by name
|
|
167
171
|
(but `signInCode`), a button with its link and the link again in text for a
|
|
168
|
-
client that shows no button, and a text part. The
|
|
172
|
+
client that shows no button, and a text part. The four notices add a warning:
|
|
169
173
|
*if this was not you, secure your account now*.
|
|
170
174
|
|
|
171
175
|
`verifyEmail`, `resetPassword` and `signInCode` also say how long the link
|
|
172
176
|
or the code lasts: "This link expires in 1 hour.", "Ce code expire dans
|
|
173
177
|
10 minutes." The text part keeps each paragraph on one line.
|
|
174
178
|
|
|
179
|
+
**The HTML follows dark mode.** Each HTML part declares
|
|
180
|
+
`color-scheme: light dark`. A client that reads `prefers-color-scheme`, or
|
|
181
|
+
Outlook's `[data-ogsc]`, shows a dark page, a dark card and light text to a
|
|
182
|
+
reader in dark mode; Gmail always shows the light e-mail. The dark palette
|
|
183
|
+
is `@nxgt/mail-ui`'s default, a deep navy, but for the primary: the button
|
|
184
|
+
is near-black (`#27272a`) with light text in light mode, and near-white
|
|
185
|
+
(`#fafafa`) with near-black text (`#18181b`) in dark mode — 17:1 against the
|
|
186
|
+
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
|
+
[Dark mode](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-ui/docs/guide/dark-mode.md)
|
|
193
|
+
guide has the technique, client by client.
|
|
194
|
+
|
|
175
195
|
**The brand is text.** It is written in the header, the body and the footer,
|
|
176
196
|
escaped — no logo and no link, which would need absolute URLs known when the
|
|
177
197
|
package is built. A logo takes a template of your own; the
|
package/docs/roadmap.md
CHANGED
|
@@ -13,6 +13,9 @@ Nothing between releases.
|
|
|
13
13
|
`invitation` from `@nxgt/mail-presets`, each once `@nxgt/janus` has a flow
|
|
14
14
|
that sends it: a sign-in link, a sign-in from a new device, a user created,
|
|
15
15
|
an invitation to a user type.
|
|
16
|
+
- **The other presets of `@nxgt/mail-presets` 0.4.0** — `account-deleted`,
|
|
17
|
+
after a user is deleted, once `@nxgt/janus` has a deletion a link can
|
|
18
|
+
undo; and `invitation-accepted`, once it has invitations.
|
|
16
19
|
|
|
17
20
|
## Later
|
|
18
21
|
|
|
@@ -32,7 +35,8 @@ Nothing between releases.
|
|
|
32
35
|
`MailFailure` or `MailRefused`, untouched, so one `instanceof` against
|
|
33
36
|
`@nxgt/mail` answers every failure, whichever package sent the e-mail.
|
|
34
37
|
- **Retries or a queue** — a retry is the application's decision, made where
|
|
35
|
-
it can see it; hand a send to a queue that retries a `MAIL_FAILED
|
|
38
|
+
it can see it; hand a send to a queue that retries a `MAIL_FAILED`, or pass
|
|
39
|
+
a mailer wrapped in `@nxgt/mail` 0.8's `withRetry`.
|
|
36
40
|
- **Sending from inside `janus()`** — `@nxgt/janus` stays free of e-mail:
|
|
37
41
|
its flows answer what to send, and this package is called with that answer.
|
|
38
42
|
An application that sends its own way needs neither this package nor a
|
|
@@ -44,6 +48,25 @@ Nothing between releases.
|
|
|
44
48
|
|
|
45
49
|
Newest first; from the first release on, the package's CHANGELOG holds every one.
|
|
46
50
|
|
|
51
|
+
- **A button that shows in dark mode, v0.3.1.** Under dark mode the button
|
|
52
|
+
is near-white with near-black text, no longer a near-black one that all but
|
|
53
|
+
vanished on the dark card; light mode, the text parts, subjects and
|
|
54
|
+
variables are unchanged. Built from `@nxgt/mail-ui` 0.5.0,
|
|
55
|
+
`@nxgt/mail-presets` 0.4.1 and `@nxgt/mail-i18n` 0.6.0; the manifest is
|
|
56
|
+
still format 1, and the `@nxgt/mail` peer stays `>=0.1.0 <1`.
|
|
57
|
+
- **The two-factor notices, v0.3.0.** `twoFactorEnabled(to)` and
|
|
58
|
+
`twoFactorDisabled(to)`, built from `@nxgt/mail-presets` 0.4.0's
|
|
59
|
+
`two-factor-enabled` and `two-factor-disabled` in English and French, each
|
|
60
|
+
linking to `links.secureAccount()`: send them on `@nxgt/janus` 0.9's
|
|
61
|
+
`user.secondFactorEnabled` and `user.secondFactorDisabled` events. Seven
|
|
62
|
+
e-mails now, and seven templates to pass for a locale beyond `en` and `fr`.
|
|
63
|
+
- **Dark mode, v0.3.0.** The e-mails follow the reader's dark mode in
|
|
64
|
+
every client that supports it, Gmail excepted: a dark page, a dark card and
|
|
65
|
+
light text. Built from `@nxgt/mail-ui` 0.4.0, `@nxgt/mail-presets` 0.4.0
|
|
66
|
+
and `@nxgt/mail-i18n` 0.5.0; the text parts, subjects and variables are
|
|
67
|
+
unchanged, the manifest is still format 1, and the `@nxgt/mail` peer stays
|
|
68
|
+
`>=0.1.0 <1`.
|
|
69
|
+
|
|
47
70
|
- **The expiry in the e-mail, v0.2.0.** The verification, reset and sign-in
|
|
48
71
|
code e-mails say how long the link or code lasts — "1 hour", "1 heure" —
|
|
49
72
|
derived from the flow's `expiresAt` in the recipient's locale, measured
|
package/docs/troubleshooting.md
CHANGED
|
@@ -32,7 +32,7 @@ How the messages are shaped:
|
|
|
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`](#janusmail-templates-has-no-template-name--name-one-of-verifyemail-resetpassword-signincode-passwordchanged-emailchanged)
|
|
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)
|
|
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)
|
|
@@ -51,6 +51,8 @@ How the messages are shaped:
|
|
|
51
51
|
- [`MAIL_FAILED` — `MailFailure`](#mail_failed--mailfailure)
|
|
52
52
|
- [`createMailRenderer: …/mails/mail-manifest.json cannot be read — run maizzle build, and deploy its output folder`](#createmailrenderer-mailsmail-manifestjson-cannot-be-read--run-maizzle-build-and-deploy-its-output-folder)
|
|
53
53
|
- [`Could not resolve "node:fs"` on an edge runtime](#could-not-resolve-nodefs-on-an-edge-runtime)
|
|
54
|
+
- [An e-mail stays light in dark mode](#an-e-mail-stays-light-in-dark-mode)
|
|
55
|
+
- [The button all but vanishes in dark mode](#the-button-all-but-vanishes-in-dark-mode)
|
|
54
56
|
|
|
55
57
|
**Compile errors**
|
|
56
58
|
- [`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)
|
|
@@ -149,9 +151,9 @@ A `fallbackLocale` the list does not hold — `'de'` with the default
|
|
|
149
151
|
|
|
150
152
|
`templates` given as a list or a function. Key each template by its name.
|
|
151
153
|
|
|
152
|
-
### `janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged`
|
|
154
|
+
### `janusMail: templates has no template <name> — name one of verifyEmail, resetPassword, signInCode, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled`
|
|
153
155
|
|
|
154
|
-
A key that is not one of the
|
|
156
|
+
A key that is not one of the seven — `welcome`, or the e-mail's file name
|
|
155
157
|
`'verify-email'` rather than the template's, `verifyEmail`. Other e-mails are
|
|
156
158
|
on the [roadmap](roadmap.md); send them with `@nxgt/mail` directly meanwhile.
|
|
157
159
|
|
|
@@ -185,7 +187,7 @@ as it is: fields are own properties.
|
|
|
185
187
|
### `janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; <names> missing`
|
|
186
188
|
|
|
187
189
|
`locales` holds a locale beyond `en` and `fr`, and `templates` leaves some
|
|
188
|
-
out: the defaults could not render them in that locale. Pass all
|
|
190
|
+
out: the defaults could not render them in that locale. Pass all seven — see
|
|
189
191
|
[Adding a locale](guide/locales.md#adding-a-locale) — or drop the locale.
|
|
190
192
|
The message lists the templates missing.
|
|
191
193
|
|
|
@@ -223,6 +225,15 @@ string` is the third argument's `expiresIn` given as something else — a
|
|
|
223
225
|
number of seconds: pass the text to show, `{ expiresIn: '1 hour' }`, or
|
|
224
226
|
leave it out.
|
|
225
227
|
|
|
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
|
|
230
|
+
alone. Read the user first:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
const user = await auth.get(event.userId);
|
|
234
|
+
await mail.twoFactorDisabled({ name: user.name, locale: user.locale, email: user.email });
|
|
235
|
+
```
|
|
236
|
+
|
|
226
237
|
### `janusMail.<method>: expiresAt must be a Date`
|
|
227
238
|
|
|
228
239
|
A `TypeError` from `verifyEmail`, `resetPassword` or `signInCode`: the e-mail
|
|
@@ -354,7 +365,10 @@ try {
|
|
|
354
365
|
}
|
|
355
366
|
```
|
|
356
367
|
|
|
357
|
-
This package retries nothing: a retry is your decision.
|
|
368
|
+
This package retries nothing: a retry is your decision. To retry in
|
|
369
|
+
process, pass a mailer wrapped in `@nxgt/mail` 0.8's `withRetry`; once its
|
|
370
|
+
attempts run out it rejects with the last `MailFailure`, `attempts` on it —
|
|
371
|
+
see [Retrying and tracing the mailer](guide/sending.md#retrying-and-tracing-the-mailer).
|
|
358
372
|
|
|
359
373
|
### `createMailRenderer: …/mails/mail-manifest.json cannot be read — run maizzle build, and deploy its output folder`
|
|
360
374
|
|
|
@@ -369,7 +383,43 @@ avoids the read altogether. (In this repository: run `bun run build`.)
|
|
|
369
383
|
|
|
370
384
|
The default templates read `mails/` with `node:fs`, through
|
|
371
385
|
`@nxgt/mail/renderer`: Node, Bun or Deno only. On an edge runtime, pass all
|
|
372
|
-
|
|
386
|
+
seven templates yourself; an inlined build is on the [roadmap](roadmap.md).
|
|
387
|
+
|
|
388
|
+
### An e-mail stays light in dark mode
|
|
389
|
+
|
|
390
|
+
Two causes, neither an error:
|
|
391
|
+
|
|
392
|
+
- **Gmail.** Its web and mobile clients cannot be targeted from CSS and
|
|
393
|
+
always show the light e-mail. Every other client that reads
|
|
394
|
+
`prefers-color-scheme`, or Outlook's `[data-ogsc]`, shows the dark one.
|
|
395
|
+
- **A template of your own.** The dark rules are in the default HTML, built
|
|
396
|
+
by `@nxgt/mail-ui`. An override that returns its own `html` has none of
|
|
397
|
+
them; build it with `@nxgt/mail-ui` 0.4.0 or later to follow dark mode:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
janusMail({ mailer, from, brand, links, templates: { verifyEmail: myVerifyEmail } });
|
|
401
|
+
// myVerifyEmail's html needs its own color-scheme and dark rules
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### The button all but vanishes in dark mode
|
|
405
|
+
|
|
406
|
+
A template of your own, built with a near-black primary, shows a button
|
|
407
|
+
that melts into the dark card for a reader in dark mode; the defaults turn
|
|
408
|
+
theirs near-white there since 0.3.1. `@nxgt/mail-ui`'s primary keeps its
|
|
409
|
+
light value under dark mode unless the build gives it a dark one — `#27272a`
|
|
410
|
+
on the dark card is 1.2:1. Build the override with `@nxgt/mail-ui` 0.5.0 or
|
|
411
|
+
later and set the dark pair in its `theme`, as the defaults do:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
ui({
|
|
415
|
+
brand: { name: 'Acme' },
|
|
416
|
+
theme: {
|
|
417
|
+
'color-primary': '#27272a',
|
|
418
|
+
'color-primary-dark': '#fafafa', // 17:1 on the dark card
|
|
419
|
+
'color-primary-foreground-dark': '#18181b', // the button's text, in dark mode
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
```
|
|
373
423
|
|
|
374
424
|
## Compile errors
|
|
375
425
|
|
|
@@ -399,7 +449,7 @@ whole.
|
|
|
399
449
|
### `TS2739: Type '{ … }' is missing the following properties from type 'JanusMailTemplates<…>'`
|
|
400
450
|
|
|
401
451
|
`locales` holds a locale beyond `en` and `fr`, and `templates` does not give
|
|
402
|
-
all
|
|
452
|
+
all seven. See [Adding a locale](guide/locales.md#adding-a-locale).
|
|
403
453
|
|
|
404
454
|
### `TS2322: Type '"de"' is not assignable to type '"en" | "fr"'.`
|
|
405
455
|
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" dir="ltr" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
2
|
+
<html lang="en" dir="ltr" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" style="color-scheme:light dark;">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="x-apple-disable-message-reformatting">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<meta name="color-scheme" content="light">
|
|
8
|
-
<meta name="supported-color-schemes" content="light">
|
|
7
|
+
<meta name="color-scheme" content="light dark">
|
|
8
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
9
|
+
<style>[data-ogsc] .nx-dark-bg-paper{background-color:#0f1523!important;}[data-ogsb] .nx-dark-bg-paper{background-color:#0f1523!important;}[data-ogsc] .nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}[data-ogsb] .nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}[data-ogsc] .nx-dark-bg-background{background-color:#020918!important;background-color:lab(2.35721% .367686
|
|
10
|
+
-8.51797)!important;}[data-ogsb] .nx-dark-bg-background{background-color:#020918!important;background-color:lab(2.35721% .367686 -8.51797)!important;}[data-ogsc] .nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsb] .nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsc] .nx-dark-text-foreground-25{color:#404551!important;}[data-ogsb] .nx-dark-text-foreground-25{color:#404551!important;}
|
|
11
|
+
[data-ogsc] .nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsb] .nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsc] .nx-dark-border-border{border-color:#1c212f!important;}[data-ogsb] .nx-dark-border-border{border-color:#1c212f!important;}[data-ogsc] .nx-dark-bg-primary{background-color:#fafafa!important;}[data-ogsb] .nx-dark-bg-primary{background-color:#fafafa!important;}
|
|
12
|
+
[data-ogsc] .nx-dark-text-primary{color:#fafafa!important;}[data-ogsb] .nx-dark-text-primary{color:#fafafa!important;}[data-ogsc] .nx-dark-text-primary-foreground{color:#18181b!important;}[data-ogsb] .nx-dark-text-primary-foreground{color:#18181b!important;}[data-ogsc] .nx-dark-border-primary{border-color:#fafafa!important;}[data-ogsb] .nx-dark-border-primary{border-color:#fafafa!important;}[data-ogsc] .nx-dark-bg-primary-15{background-color:#dfdfdf!important;}[data-ogsb] .nx-dark-bg-primary-15{
|
|
13
|
+
background-color:#dfdfdf!important;}[data-ogsc] .nx-dark-bg-primary-20{background-color:#d4d4d4!important;}[data-ogsb] .nx-dark-bg-primary-20{background-color:#d4d4d4!important;}[data-ogsc] .nx-dark-border-primary-40{border-color:#a9a9aa!important;}[data-ogsb] .nx-dark-border-primary-40{border-color:#a9a9aa!important;}[data-ogsc] .nx-dark-border-primary-50{border-color:#939395!important;}[data-ogsb] .nx-dark-border-primary-50{border-color:#939395!important;}[data-ogsc] .nx-light-only{display:
|
|
14
|
+
none!important;}[data-ogsb] .nx-light-only{display:none!important;}[data-ogsc] .nx-dark-only{display:inline-block!important;}[data-ogsb] .nx-dark-only{display:inline-block!important;}
|
|
15
|
+
@media (prefers-color-scheme:dark){html{--lightningcss-light:;--lightningcss-dark:initial;}.nx-dark-bg-paper{background-color:#0f1523!important;}.nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}.nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}.nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}.nx-dark-border-border{border-color:#1c212f
|
|
16
|
+
!important;}.nx-dark-bg-primary{background-color:#fafafa!important;}.nx-dark-text-primary-foreground{color:#18181b!important;} }
|
|
17
|
+
</style>
|
|
9
18
|
</head>
|
|
10
|
-
<body xml:lang="en" dir="ltr" style="margin:0;padding:0;width:100%;height:100%;word-break:break-word;background-color:#f4f4f4;"><div style="display:none;">Your account now signs in with a new address. ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
19
|
+
<body xml:lang="en" dir="ltr" style="margin:0;padding:0;width:100%;height:100%;word-break:break-word;background-color:#f4f4f4;" class="nx-dark-bg-paper"><div style="display:none;">Your account now signs in with a new address. ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
11
20
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
12
21
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
13
22
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
14
23
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
15
24
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
16
25
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
17
|
-
 ͏  ͏  ͏ </div><span style="display:none">
|
|
26
|
+
 ͏  ͏  ͏  ͏ </div><span style="display:none">
|
|
18
27
|
<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings><w:WordDocument><w:DontUseAdvancedTypographyReadingMail/></w:WordDocument></xml>
|
|
19
28
|
<![endif]--></span><div role="article" aria-roledescription="email" lang="en" dir="ltr" style="font-size:medium;font-size:max(16px,1rem)">
|
|
20
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;background-color:#f4f4f4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;"><tr><td align="center" style="padding:32px 16px;">
|
|
29
|
+
<table class="nx-dark-bg-paper" role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;background-color:#f4f4f4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;"><tr><td align="center" style="padding:32px 16px;">
|
|
21
30
|
<!--[if mso]>
|
|
22
31
|
<table role="none" cellpadding="0" cellspacing="0" style="width:600px" align="center"><tr><td>
|
|
23
32
|
<![endif]--><div style="margin:0 auto;max-width:600px;">
|
|
24
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;"><tr><td style="padding-bottom:24px;text-align:center;"><span style="font-size:18px;line-height:28px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ brand }}</span>
|
|
25
|
-
</td></tr><tr><td style="border-radius:14px;border-color:#e2e8f0;border-style:solid;border-width:1px;border-color:lab(91.7353% -.998765 -4.76968);background-color:#fff;background-color:lab(100% 0 0);padding:32px;font-size:14px;line-height:20px;color:#020618;color:lab(1.76974% 1.32743 -9.28855);box-shadow:0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 1px 3px rgba(0,0,0,.1),0 1px 2px -1px rgba(0,
|
|
26
|
-
font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Your e-mail address was changed</h1><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Hello {{ name }},</p><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686
|
|
27
|
-
|
|
28
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;"><tr><td style="
|
|
33
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;"><tr><td style="padding-bottom:24px;text-align:center;"><span class="nx-dark-text-foreground" style="font-size:18px;line-height:28px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ brand }}</span>
|
|
34
|
+
</td></tr><tr><td class="nx-dark-border-border nx-dark-bg-card nx-dark-text-card-foreground" style="border-radius:14px;border-color:#e2e8f0;border-style:solid;border-width:1px;border-color:lab(91.7353% -.998765 -4.76968);background-color:#fff;background-color:lab(100% 0 0);padding:32px;font-size:14px;line-height:20px;color:#020618;color:lab(1.76974% 1.32743 -9.28855);box-shadow:0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 1px 3px rgba(0,0,0,.1),0 1px 2px -1px rgba(0,
|
|
35
|
+
0,0,.1);"><h1 class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:24px;line-height:32px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Your e-mail address was changed</h1><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Hello {{ name }},</p><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686
|
|
36
|
+
-8.51797);">Your {{ brand }} account now uses {{ newEmail }}. We send this notice to your former address.</p>
|
|
37
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="margin-bottom:16px;width:100%;"><tr><td style="border-width:0;border-left-style:solid;border-left-width:8px;border-style:solid;border-color:#f05100;border-color:lab(57.1026% 64.2584 89.8886);background-color:#fef6f2;padding:16px;">
|
|
38
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;"><tr><td style="vertical-align:top;"><p style="margin:0;font-size:14px;line-height:20px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If this was not you, secure your account now.</p>
|
|
29
39
|
</td></tr></table>
|
|
30
|
-
</td></tr></table><div><a style="display:inline-block;border-radius:9999px;background-color:#27272a;padding:10px 16px;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;font-size:14px;line-height:16px;font-weight:500;color:#f8fafc;color:lab(98.1434% -.369519 -1.05966);text-decoration:none;" href="{{ link }}">
|
|
40
|
+
</td></tr></table><div><a style="display:inline-block;border-radius:9999px;background-color:#27272a;padding:10px 16px;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;font-size:14px;line-height:16px;font-weight:500;color:#f8fafc;color:lab(98.1434% -.369519 -1.05966);text-decoration:none;" href="{{ link }}" class="nx-dark-bg-primary nx-dark-text-primary-foreground">
|
|
31
41
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
32
42
|
<![endif]--> <span style="mso-text-raise:10px;">Undo this change</span>
|
|
33
43
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
34
|
-
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If the button does not work, open this link: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:underline;">{{ link }}</a></p>
|
|
44
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If the button does not work, open this link: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:underline;">{{ link }}</a></p>
|
|
35
45
|
</td></tr><tr><td style="padding-left:24px;padding-right:24px;padding-top:24px;text-align:center;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);"><p style="margin:0 0 8px;">You received this e-mail because you have an account with {{ brand }}.</p><p style="margin:0;">{{ brand }}</p>
|
|
36
46
|
</td></tr></table></div>
|
|
37
47
|
<!--[if mso]>
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" dir="ltr" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
2
|
+
<html lang="en" dir="ltr" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" style="color-scheme:light dark;">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="x-apple-disable-message-reformatting">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
-
<meta name="color-scheme" content="light">
|
|
8
|
-
<meta name="supported-color-schemes" content="light">
|
|
7
|
+
<meta name="color-scheme" content="light dark">
|
|
8
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
9
|
+
<style>[data-ogsc] .nx-dark-bg-paper{background-color:#0f1523!important;}[data-ogsb] .nx-dark-bg-paper{background-color:#0f1523!important;}[data-ogsc] .nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}[data-ogsb] .nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}[data-ogsc] .nx-dark-bg-background{background-color:#020918!important;background-color:lab(2.35721% .367686
|
|
10
|
+
-8.51797)!important;}[data-ogsb] .nx-dark-bg-background{background-color:#020918!important;background-color:lab(2.35721% .367686 -8.51797)!important;}[data-ogsc] .nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsb] .nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsc] .nx-dark-text-foreground-25{color:#404551!important;}[data-ogsb] .nx-dark-text-foreground-25{color:#404551!important;}
|
|
11
|
+
[data-ogsc] .nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsb] .nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}[data-ogsc] .nx-dark-border-border{border-color:#1c212f!important;}[data-ogsb] .nx-dark-border-border{border-color:#1c212f!important;}[data-ogsc] .nx-dark-bg-primary{background-color:#fafafa!important;}[data-ogsb] .nx-dark-bg-primary{background-color:#fafafa!important;}
|
|
12
|
+
[data-ogsc] .nx-dark-text-primary{color:#fafafa!important;}[data-ogsb] .nx-dark-text-primary{color:#fafafa!important;}[data-ogsc] .nx-dark-text-primary-foreground{color:#18181b!important;}[data-ogsb] .nx-dark-text-primary-foreground{color:#18181b!important;}[data-ogsc] .nx-dark-border-primary{border-color:#fafafa!important;}[data-ogsb] .nx-dark-border-primary{border-color:#fafafa!important;}[data-ogsc] .nx-dark-bg-primary-15{background-color:#dfdfdf!important;}[data-ogsb] .nx-dark-bg-primary-15{
|
|
13
|
+
background-color:#dfdfdf!important;}[data-ogsc] .nx-dark-bg-primary-20{background-color:#d4d4d4!important;}[data-ogsb] .nx-dark-bg-primary-20{background-color:#d4d4d4!important;}[data-ogsc] .nx-dark-border-primary-40{border-color:#a9a9aa!important;}[data-ogsb] .nx-dark-border-primary-40{border-color:#a9a9aa!important;}[data-ogsc] .nx-dark-border-primary-50{border-color:#939395!important;}[data-ogsb] .nx-dark-border-primary-50{border-color:#939395!important;}[data-ogsc] .nx-light-only{display:
|
|
14
|
+
none!important;}[data-ogsb] .nx-light-only{display:none!important;}[data-ogsc] .nx-dark-only{display:inline-block!important;}[data-ogsb] .nx-dark-only{display:inline-block!important;}
|
|
15
|
+
@media (prefers-color-scheme:dark){html{--lightningcss-light:;--lightningcss-dark:initial;}.nx-dark-bg-paper{background-color:#0f1523!important;}.nx-dark-bg-card{background-color:#0f172b!important;background-color:lab(7.78673% 1.82345 -15.0537)!important;}.nx-dark-text-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}.nx-dark-text-card-foreground{color:#f8fafc!important;color:lab(98.1434% -.369519 -1.05966)!important;}.nx-dark-border-border{border-color:#1c212f
|
|
16
|
+
!important;}.nx-dark-bg-primary{background-color:#fafafa!important;}.nx-dark-text-primary-foreground{color:#18181b!important;} }
|
|
17
|
+
</style>
|
|
9
18
|
</head>
|
|
10
|
-
<body xml:lang="en" dir="ltr" style="margin:0;padding:0;width:100%;height:100%;word-break:break-word;background-color:#f4f4f4;"><div style="display:none;">The password of your account was just changed. ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
19
|
+
<body xml:lang="en" dir="ltr" style="margin:0;padding:0;width:100%;height:100%;word-break:break-word;background-color:#f4f4f4;" class="nx-dark-bg-paper"><div style="display:none;">The password of your account was just changed. ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
11
20
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
12
21
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
13
22
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
14
23
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
15
24
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
16
25
|
 ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏  ͏
|
|
17
|
-
 ͏  ͏ </div><span style="display:none">
|
|
26
|
+
 ͏  ͏  ͏ </div><span style="display:none">
|
|
18
27
|
<!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings><w:WordDocument><w:DontUseAdvancedTypographyReadingMail/></w:WordDocument></xml>
|
|
19
28
|
<![endif]--></span><div role="article" aria-roledescription="email" lang="en" dir="ltr" style="font-size:medium;font-size:max(16px,1rem)">
|
|
20
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;background-color:#f4f4f4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;"><tr><td align="center" style="padding:32px 16px;">
|
|
29
|
+
<table class="nx-dark-bg-paper" role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;background-color:#f4f4f4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;"><tr><td align="center" style="padding:32px 16px;">
|
|
21
30
|
<!--[if mso]>
|
|
22
31
|
<table role="none" cellpadding="0" cellspacing="0" style="width:600px" align="center"><tr><td>
|
|
23
32
|
<![endif]--><div style="margin:0 auto;max-width:600px;">
|
|
24
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;"><tr><td style="padding-bottom:24px;text-align:center;"><span style="font-size:18px;line-height:28px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ brand }}</span>
|
|
25
|
-
</td></tr><tr><td style="border-radius:14px;border-color:#e2e8f0;border-style:solid;border-width:1px;border-color:lab(91.7353% -.998765 -4.76968);background-color:#fff;background-color:lab(100% 0 0);padding:32px;font-size:14px;line-height:20px;color:#020618;color:lab(1.76974% 1.32743 -9.28855);box-shadow:0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 1px 3px rgba(0,0,0,.1),0 1px 2px -1px rgba(0,
|
|
26
|
-
font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Your password was changed</h1><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Hello {{ name }},</p><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686
|
|
27
|
-
|
|
28
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;"><tr><td style="
|
|
33
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;"><tr><td style="padding-bottom:24px;text-align:center;"><span class="nx-dark-text-foreground" style="font-size:18px;line-height:28px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ brand }}</span>
|
|
34
|
+
</td></tr><tr><td class="nx-dark-border-border nx-dark-bg-card nx-dark-text-card-foreground" style="border-radius:14px;border-color:#e2e8f0;border-style:solid;border-width:1px;border-color:lab(91.7353% -.998765 -4.76968);background-color:#fff;background-color:lab(100% 0 0);padding:32px;font-size:14px;line-height:20px;color:#020618;color:lab(1.76974% 1.32743 -9.28855);box-shadow:0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 0 rgba(0,0,0,0),0 1px 3px rgba(0,0,0,.1),0 1px 2px -1px rgba(0,
|
|
35
|
+
0,0,.1);"><h1 class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:24px;line-height:32px;font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Your password was changed</h1><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Hello {{ name }},</p><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686
|
|
36
|
+
-8.51797);">The password of your {{ brand }} account was just changed.</p>
|
|
37
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="margin-bottom:16px;width:100%;"><tr><td style="border-width:0;border-left-style:solid;border-left-width:8px;border-style:solid;border-color:#f05100;border-color:lab(57.1026% 64.2584 89.8886);background-color:#fef6f2;padding:16px;">
|
|
38
|
+
<table role="presentation" cellpadding="0" cellspacing="0" dir="ltr" style="width:100%;"><tr><td style="vertical-align:top;"><p style="margin:0;font-size:14px;line-height:20px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If this was not you, secure your account now.</p>
|
|
29
39
|
</td></tr></table>
|
|
30
|
-
</td></tr></table><div><a style="display:inline-block;border-radius:9999px;background-color:#27272a;padding:10px 16px;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;font-size:14px;line-height:16px;font-weight:500;color:#f8fafc;color:lab(98.1434% -.369519 -1.05966);text-decoration:none;" href="{{ link }}">
|
|
40
|
+
</td></tr></table><div><a style="display:inline-block;border-radius:9999px;background-color:#27272a;padding:10px 16px;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont, 'Segoe UI',Roboto, 'Noto Sans',Ubuntu,Cantarell, 'Helvetica Neue',sans-serif;font-size:14px;line-height:16px;font-weight:500;color:#f8fafc;color:lab(98.1434% -.369519 -1.05966);text-decoration:none;" href="{{ link }}" class="nx-dark-bg-primary nx-dark-text-primary-foreground">
|
|
31
41
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
32
42
|
<![endif]--> <span style="mso-text-raise:10px;">Secure my account</span>
|
|
33
43
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
34
|
-
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If the button does not work, open this link: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:underline;">{{ link }}</a></p>
|
|
44
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p class="nx-dark-text-foreground" style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If the button does not work, open this link: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:underline;">{{ link }}</a></p>
|
|
35
45
|
</td></tr><tr><td style="padding-left:24px;padding-right:24px;padding-top:24px;text-align:center;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);"><p style="margin:0 0 8px;">You received this e-mail because you have an account with {{ brand }}.</p><p style="margin:0;">{{ brand }}</p>
|
|
36
46
|
</td></tr></table></div>
|
|
37
47
|
<!--[if mso]>
|