@nxgt/janus-mail 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -15
- package/dist/expiring-mails.d.ts +7 -0
- package/dist/expiring-mails.d.ts.map +1 -0
- package/dist/expiry.d.ts +29 -0
- package/dist/expiry.d.ts.map +1 -0
- package/dist/generated/mail.d.ts +3 -0
- package/dist/generated/mail.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +152 -84
- package/dist/index.js.map +10 -6
- package/dist/janus-mail.d.ts +6 -0
- package/dist/janus-mail.d.ts.map +1 -1
- package/dist/notice-mails.d.ts +12 -0
- package/dist/notice-mails.d.ts.map +1 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/send.d.ts +27 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/types.d.ts +36 -7
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +2 -1
- package/docs/guide/building.md +1 -1
- package/docs/guide/locales.md +6 -4
- package/docs/guide/sending.md +65 -11
- package/docs/guide/templates.md +16 -14
- package/docs/roadmap.md +9 -2
- package/docs/troubleshooting.md +97 -4
- package/mails/en/reset-password.html +2 -2
- package/mails/en/reset-password.txt +3 -2
- package/mails/en/sign-in-code.html +1 -1
- package/mails/en/sign-in-code.txt +2 -0
- package/mails/en/verify-email.html +2 -2
- package/mails/en/verify-email.txt +3 -2
- package/mails/fr/reset-password.html +2 -2
- package/mails/fr/reset-password.txt +3 -2
- package/mails/fr/sign-in-code.html +1 -1
- package/mails/fr/sign-in-code.txt +2 -0
- package/mails/fr/verify-email.html +2 -2
- package/mails/fr/verify-email.txt +3 -2
- package/mails/mail-manifest.json +4 -1
- package/package.json +6 -6
package/docs/guide/templates.md
CHANGED
|
@@ -59,30 +59,30 @@ e-mail.
|
|
|
59
59
|
|
|
60
60
|
| Template | Variables | Sent by |
|
|
61
61
|
| --- | --- | --- |
|
|
62
|
-
| `verifyEmail` | `brand`, `name`, `link` | `mail.verifyEmail(issued, to)` |
|
|
63
|
-
| `resetPassword` | `brand`, `name`, `link` | `mail.resetPassword(issued, to)` |
|
|
64
|
-
| `signInCode` | `brand`, `code` | `mail.signInCode(issued, to?)` |
|
|
62
|
+
| `verifyEmail` | `brand`, `name`, `link`, `expiresIn` | `mail.verifyEmail(issued, to, options?)` |
|
|
63
|
+
| `resetPassword` | `brand`, `name`, `link`, `expiresIn` | `mail.resetPassword(issued, to, options?)` |
|
|
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
67
|
|
|
68
68
|
Every one also gets `locale`, one of `locales`. Every value is a string:
|
|
69
69
|
`brand` from the options, `name` from the recipient, `link` from `links` —
|
|
70
70
|
`links.secureAccount()` for the two notices — `code` and `newEmail` from the
|
|
71
|
-
call
|
|
71
|
+
call, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
|
|
72
|
+
the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
|
|
72
73
|
|
|
73
74
|
`signInCode` is **never given the challenge**: it is not in its variables,
|
|
74
|
-
and the method never reads it.
|
|
75
|
-
|
|
75
|
+
and the method never reads it. An override states the expiry from
|
|
76
|
+
`expiresIn`, already formatted:
|
|
76
77
|
|
|
77
78
|
```ts
|
|
78
79
|
const mail = janusMail({
|
|
79
80
|
mailer, from, brand: 'Acme', links,
|
|
80
81
|
templates: {
|
|
81
|
-
|
|
82
|
-
signInCode: ({ code, locale }) =>
|
|
82
|
+
signInCode: ({ code, expiresIn, locale }) =>
|
|
83
83
|
locale === 'fr'
|
|
84
|
-
? { subject: `Code : ${code}`, html: `<p>${code} — valable
|
|
85
|
-
: { subject: `Code: ${code}`, html: `<p>${code} — valid for
|
|
84
|
+
? { subject: `Code : ${code}`, html: `<p>${code} — valable ${expiresIn}.</p>`, text: `${code} — valable ${expiresIn}.` }
|
|
85
|
+
: { subject: `Code: ${code}`, html: `<p>${code} — valid for ${expiresIn}.</p>`, text: `${code} — valid for ${expiresIn}.` },
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
```
|
|
@@ -109,7 +109,7 @@ another locale, it takes **all five** — see
|
|
|
109
109
|
rest — frozen:
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
|
-
const rendered = await mail.templates.verifyEmail({ brand: 'Acme', name: 'Ada', link, locale: 'fr' });
|
|
112
|
+
const rendered = await mail.templates.verifyEmail({ brand: 'Acme', name: 'Ada', link, expiresIn: '1 heure', locale: 'fr' });
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
## The defaults alone — `janusTemplates()`
|
|
@@ -122,6 +122,7 @@ const { subject, html, text } = await defaults.resetPassword({
|
|
|
122
122
|
brand: 'Acme',
|
|
123
123
|
name: 'Ada',
|
|
124
124
|
link: 'https://acme.example/reset?token=abc',
|
|
125
|
+
expiresIn: '1 heure',
|
|
125
126
|
locale: 'fr',
|
|
126
127
|
});
|
|
127
128
|
```
|
|
@@ -152,7 +153,7 @@ them renders: that is when the package's `mails/` folder is read, once.
|
|
|
152
153
|
## What the defaults say
|
|
153
154
|
|
|
154
155
|
The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail-presets)
|
|
155
|
-
0.
|
|
156
|
+
0.2.0, built with a neutral grey theme and no logo:
|
|
156
157
|
|
|
157
158
|
| Template | Built from | Subject (`en`) | Subject (`fr`) |
|
|
158
159
|
| --- | --- | --- | --- |
|
|
@@ -167,8 +168,9 @@ Each has a header and a footer with the brand, a heading, a greeting by name
|
|
|
167
168
|
client that shows no button, and a text part. The two notices add a warning:
|
|
168
169
|
*if this was not you, secure your account now*.
|
|
169
170
|
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
`verifyEmail`, `resetPassword` and `signInCode` also say how long the link
|
|
172
|
+
or the code lasts: "This link expires in 1 hour.", "Ce code expire dans
|
|
173
|
+
10 minutes." The text part keeps each paragraph on one line.
|
|
172
174
|
|
|
173
175
|
**The brand is text.** It is written in the header, the body and the footer,
|
|
174
176
|
escaped — no logo and no link, which would need absolute URLs known when the
|
package/docs/roadmap.md
CHANGED
|
@@ -13,8 +13,6 @@ 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 expiry in the e-mail** — "the link expires in 24 hours", from the
|
|
17
|
-
flow's `expiresAt`, once the presets' messages take it.
|
|
18
16
|
|
|
19
17
|
## Later
|
|
20
18
|
|
|
@@ -46,6 +44,15 @@ Nothing between releases.
|
|
|
46
44
|
|
|
47
45
|
Newest first; from the first release on, the package's CHANGELOG holds every one.
|
|
48
46
|
|
|
47
|
+
- **The expiry in the e-mail, v0.2.0.** The verification, reset and sign-in
|
|
48
|
+
code e-mails say how long the link or code lasts — "1 hour", "1 heure" —
|
|
49
|
+
derived from the flow's `expiresAt` in the recipient's locale, measured
|
|
50
|
+
against `janusMail({ clock })` (the clock given to `janus()`), or given per
|
|
51
|
+
send as `{ expiresIn }`. Two breaks: a template called directly takes
|
|
52
|
+
`expiresIn`, and `signInCode` reads `issued.expiresAt`. Built from
|
|
53
|
+
`@nxgt/mail-presets` 0.2.0, whose text parts keep each paragraph on one
|
|
54
|
+
line.
|
|
55
|
+
|
|
49
56
|
- **The first release, v0.1.0.** The five e-mails of `@nxgt/janus`'s flows,
|
|
50
57
|
in English and French, over any `@nxgt/mail` transport: `janusMail()` and
|
|
51
58
|
`janusTemplates()`. Built with Maizzle when the package is built, shipped
|
package/docs/troubleshooting.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Troubleshooting `@nxgt/janus-mail`
|
|
2
2
|
|
|
3
|
-
Each entry is headed by the text you see: a message, an error `code`,
|
|
4
|
-
compiler error. Search this page for the words of your message.
|
|
3
|
+
Each entry is headed by the text you see: a message, an error `code`, a
|
|
4
|
+
compiler error — or, where nothing is thrown, what the e-mail says. Search this page for the words of your message.
|
|
5
5
|
|
|
6
6
|
How the messages are shaped:
|
|
7
7
|
|
|
@@ -34,10 +34,16 @@ How the messages are shaped:
|
|
|
34
34
|
- [`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
35
|
- [`janusMail: templates.<name> must be a function`](#janusmail-templatesname-must-be-a-function)
|
|
36
36
|
- [`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-)
|
|
37
|
+
- [`janusMail: locales must be BCP 47 language tags, with hyphens, as 'fr-CA'`](#janusmail-locales-must-be-bcp-47-language-tags-with-hyphens-as-fr-ca)
|
|
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)
|
|
37
39
|
- [`janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; <names> missing`](#janusmail-the-default-templates-are-built-in-en-and-fr-only--with-another-locale-in-locales-pass-every-template-in-templates-names-missing)
|
|
38
40
|
|
|
39
41
|
**Sending**
|
|
40
42
|
- [`janusMail.<method>: <field> must be a string`](#janusmailmethod-field-must-be-a-string)
|
|
43
|
+
- [`janusMail.<method>: expiresAt must be a Date`](#janusmailmethod-expiresat-must-be-a-date)
|
|
44
|
+
- [`janusMail.<method>: clock.now() must answer a Date`](#janusmailmethod-clocknow-must-answer-a-date)
|
|
45
|
+
- [`janusMail.<method>: expiresAt is past — the link or code would not work`](#janusmailmethod-expiresat-is-past--the-link-or-code-would-not-work)
|
|
46
|
+
- [`This link expires in 1 hour.` — in the wrong language, or shorter than the `tokens` TTL](#this-link-expires-in-1-hour--in-the-wrong-language-or-shorter-than-the-tokens-ttl)
|
|
41
47
|
- [`MAIL_REFUSED` — `render: <email>: link must be an http:, https: or mailto: URL`](#mail_refused--render-email-link-must-be-an-http-https-or-mailto-url)
|
|
42
48
|
- [`janusMail.<method>: links.<call> must answer a string`](#janusmailmethod-linkscall-must-answer-a-string)
|
|
43
49
|
- [`render: <email>: link must be a string or a finite number`](#render-email-link-must-be-a-string-or-a-finite-number)
|
|
@@ -48,6 +54,7 @@ How the messages are shaped:
|
|
|
48
54
|
|
|
49
55
|
**Compile errors**
|
|
50
56
|
- [`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)
|
|
57
|
+
- [`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)
|
|
51
58
|
- [`TS2739: Type '{ … }' is missing the following properties from type 'JanusMailTemplates<…>'`](#ts2739-type----is-missing-the-following-properties-from-type-janusmailtemplates)
|
|
52
59
|
- [`TS2322: Type '"de"' is not assignable to type '"en" | "fr"'.`](#ts2322-type-de-is-not-assignable-to-type-en--fr)
|
|
53
60
|
|
|
@@ -169,6 +176,30 @@ out: the defaults could not render them in that locale. Pass all five — see
|
|
|
169
176
|
[Adding a locale](guide/locales.md#adding-a-locale) — or drop the locale.
|
|
170
177
|
The message lists the templates missing.
|
|
171
178
|
|
|
179
|
+
### `janusMail: locales must be BCP 47 language tags, with hyphens, as 'fr-CA'`
|
|
180
|
+
|
|
181
|
+
A `TypeError` when `janusMail()` is called: a locale in `locales` is one
|
|
182
|
+
`Intl` refuses — `de_DE` with an underscore, a grandfathered `i-klingon`, a
|
|
183
|
+
bare private-use `x-…`. The expiry is formatted by `Intl` in the locale
|
|
184
|
+
picked, so a locale it cannot parse would fail at the first send; it is
|
|
185
|
+
refused here instead. Write the tag with hyphens:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
janusMail({ mailer, from, brand: 'Acme', links, locales: ['en', 'fr', 'de-DE'], templates });
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `janusMail: clock must be a Clock — an object with a now function`
|
|
192
|
+
|
|
193
|
+
`clock` is `@nxgt/janus`'s `Clock`, `{ now(): Date }` — pass the one given to
|
|
194
|
+
`janus({ clock })`, not `Date.now` or `() => new Date()`:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { fixedClock } from '@nxgt/janus';
|
|
198
|
+
|
|
199
|
+
const clock = fixedClock(Date.UTC(2026, 0, 1));
|
|
200
|
+
janusMail({ mailer, from, brand: 'Acme', links, clock });
|
|
201
|
+
```
|
|
202
|
+
|
|
172
203
|
## Sending
|
|
173
204
|
|
|
174
205
|
### `janusMail.<method>: <field> must be a string`
|
|
@@ -186,7 +217,61 @@ if (issued !== null) await mail.resetPassword(issued, { name: issued.user.name }
|
|
|
186
217
|
|
|
187
218
|
`issued` needs `token` and `email` (`code` and `email` for `signInCode`), and
|
|
188
219
|
the recipient its `name` — a user whose schema has no name passes one
|
|
189
|
-
anyway: `{ name: user.email }`.
|
|
220
|
+
anyway: `{ name: user.email }`. `janusMail.verifyEmail: expiresIn must be a
|
|
221
|
+
string` is the third argument's `expiresIn` given as something else — a
|
|
222
|
+
number of seconds: pass the text to show, `{ expiresIn: '1 hour' }`, or
|
|
223
|
+
leave it out.
|
|
224
|
+
|
|
225
|
+
### `janusMail.<method>: expiresAt must be a Date`
|
|
226
|
+
|
|
227
|
+
A `TypeError` from `verifyEmail`, `resetPassword` or `signInCode`: the e-mail
|
|
228
|
+
says how long its link or code lasts, from `issued.expiresAt`, and that is
|
|
229
|
+
not a valid `Date`. The usual cause is a flow's answer that went through
|
|
230
|
+
JSON — a job queue, a cache — where a `Date` becomes a string. Nothing is
|
|
231
|
+
sent. Revive it when the job runs, or say the expiry yourself:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const issued = { ...job.issued, expiresAt: new Date(job.issued.expiresAt) };
|
|
235
|
+
await mail.verifyEmail(issued, { name: job.name });
|
|
236
|
+
// or
|
|
237
|
+
await mail.verifyEmail(job.issued, { name: job.name }, { expiresIn: '1 day' });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### `janusMail.<method>: clock.now() must answer a Date`
|
|
241
|
+
|
|
242
|
+
A `TypeError` from `verifyEmail`, `resetPassword` or `signInCode`: the
|
|
243
|
+
`clock` given to `janusMail()` has a `now` that answered something else — a
|
|
244
|
+
number from `Date.now()`, a string, an invalid `Date`. Nothing is sent.
|
|
245
|
+
`clock` is `@nxgt/janus`'s `Clock`: pass the one `janus({ clock })` was
|
|
246
|
+
given, or `{ now: () => new Date() }`.
|
|
247
|
+
|
|
248
|
+
### `janusMail.<method>: expiresAt is past — the link or code would not work`
|
|
249
|
+
|
|
250
|
+
A `TypeError`: the send came after the link or the code expired — a queue
|
|
251
|
+
that waited too long, an `expiresAt` from another record, or, in tests,
|
|
252
|
+
`janus({ clock: fixedClock(...) })` without the same `clock` given to
|
|
253
|
+
`janusMail()`, so a date in the clock's past is measured against today. Nothing is
|
|
254
|
+
sent: an e-mail whose link fails helps nobody. Issue a new one
|
|
255
|
+
(`auth.verifyEmail.send(user)`, `auth.signInCode.request(email)`) and send
|
|
256
|
+
that; if a queue can hold a send that long, lengthen the `tokens` TTL in
|
|
257
|
+
`janus({ tokens })`.
|
|
258
|
+
|
|
259
|
+
### `This link expires in 1 hour.` — in the wrong language, or shorter than the `tokens` TTL
|
|
260
|
+
|
|
261
|
+
`expiresIn` is formatted by the runtime's `Intl.NumberFormat`, in the locale
|
|
262
|
+
picked for the recipient. A locale the runtime has no data for — a Node
|
|
263
|
+
built with `small-icu`, a locale of your own — is formatted in the
|
|
264
|
+
runtime's default language. The time left is measured against
|
|
265
|
+
`janusMail({ clock })`, rounded to the minute, then **down** to the largest
|
|
266
|
+
whole unit: 90 minutes is "1 hour", 36 hours "1 day".
|
|
267
|
+
For another wording, pass it:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
await mail.resetPassword(issued, { name, locale: 'de' }, { expiresIn: '90 Minuten' });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
French puts a no-break space between the number and some units (`1 heure`,
|
|
274
|
+
per CLDR): compare with `\s` in a test, not a plain space.
|
|
190
275
|
|
|
191
276
|
### `MAIL_REFUSED` — `render: <email>: link must be an http:, https: or mailto: URL`
|
|
192
277
|
|
|
@@ -300,7 +385,15 @@ return new Response(null, { status: 202 });
|
|
|
300
385
|
|
|
301
386
|
The same holds for `signInCode.request`, whose answer passed unchecked reads
|
|
302
387
|
`Argument of type 'IssuedCode<…> | null' is not assignable to parameter of
|
|
303
|
-
type 'Pick<IssuedCode<unknown>, "email" | "code">'`.
|
|
388
|
+
type 'Pick<IssuedCode<unknown>, "email" | "code" | "expiresAt">'`.
|
|
389
|
+
|
|
390
|
+
### `TS2345: … Types of property 'expiresAt' are incompatible. Type 'string' is not assignable to type 'Date'.`
|
|
391
|
+
|
|
392
|
+
A flow's answer that went through JSON, typed as it came out. Revive the
|
|
393
|
+
date — `{ ...issued, expiresAt: new Date(issued.expiresAt) }` — or pass the
|
|
394
|
+
expiry as text in the third argument. A sign-in code built by hand without
|
|
395
|
+
`expiresAt` reads `Property 'expiresAt' is missing`: pass the flow's answer
|
|
396
|
+
whole.
|
|
304
397
|
|
|
305
398
|
### `TS2739: Type '{ … }' is missing the following properties from type 'JanusMailTemplates<…>'`
|
|
306
399
|
|
|
@@ -28,8 +28,8 @@ font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Reset your
|
|
|
28
28
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
29
29
|
<![endif]--> <span style="mso-text-raise:10px;">Choose a new password</span>
|
|
30
30
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
31
|
-
<![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;">{{
|
|
32
|
-
ignore this e-mail.</p>
|
|
31
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">This link expires in {{ expiresIn }}.</p><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;">{{
|
|
32
|
+
link }}</a></p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If you did not ask for this, you can ignore this e-mail.</p>
|
|
33
33
|
</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>
|
|
34
34
|
</td></tr></table></div>
|
|
35
35
|
<!--[if mso]>
|
|
@@ -12,10 +12,11 @@ Choose a new password
|
|
|
12
12
|
|
|
13
13
|
{{ link }}
|
|
14
14
|
|
|
15
|
+
This link expires in {{ expiresIn }}.
|
|
16
|
+
|
|
15
17
|
If the button does not work, open this link: {{ link }}
|
|
16
18
|
|
|
17
|
-
If you did not ask for this, you can
|
|
18
|
-
ignore this e-mail.
|
|
19
|
+
If you did not ask for this, you can ignore this e-mail.
|
|
19
20
|
|
|
20
21
|
You received this e-mail because you have an account with {{ brand }}.
|
|
21
22
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
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,0,0,.1);"><h1 style="margin:0 0 16px;font-size:24px;line-height:32px;
|
|
26
26
|
font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Your sign-in code</h1><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Enter this code to sign in to {{ brand }}. It works once.</p>
|
|
27
27
|
<table role="presentation" cellpadding="0" cellspacing="0" style="margin-bottom:16px;width:100%;"><tr><td style="border-radius:10px;background-color:#f1f5f9;background-color:lab(96.286% -.852436 -2.46847);padding:16px 24px;text-align:center;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:30px;line-height:36px;font-weight:600;letter-spacing:.3em;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ code }}
|
|
28
|
-
</td></tr></table><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If you did not try to sign in, you can ignore this e-mail: no one can sign in without the code.</p>
|
|
28
|
+
</td></tr></table><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">This code expires in {{ expiresIn }}.</p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If you did not try to sign in, you can ignore this e-mail: no one can sign in without the code.</p>
|
|
29
29
|
</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>
|
|
30
30
|
</td></tr></table></div>
|
|
31
31
|
<!--[if mso]>
|
|
@@ -8,6 +8,8 @@ Enter this code to sign in to {{ brand }}. It works once.
|
|
|
8
8
|
|
|
9
9
|
{{ code }}
|
|
10
10
|
|
|
11
|
+
This code expires in {{ expiresIn }}.
|
|
12
|
+
|
|
11
13
|
If you did not try to sign in, you can ignore this e-mail: no one can sign in without the code.
|
|
12
14
|
|
|
13
15
|
You received this e-mail because you have an account with {{ brand }}.
|
|
@@ -28,8 +28,8 @@ background-color:#27272a;padding:10px 16px;font-family:ui-sans-serif,system-ui,-
|
|
|
28
28
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
29
29
|
<![endif]--> <span style="mso-text-raise:10px;">Confirm my address</span>
|
|
30
30
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
31
|
-
<![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;">{{
|
|
32
|
-
ignore this e-mail.</p>
|
|
31
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">This link expires in {{ expiresIn }}.</p><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;">{{
|
|
32
|
+
link }}</a></p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">If you did not ask for this, you can ignore this e-mail.</p>
|
|
33
33
|
</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>
|
|
34
34
|
</td></tr></table></div>
|
|
35
35
|
<!--[if mso]>
|
|
@@ -12,10 +12,11 @@ Confirm my address
|
|
|
12
12
|
|
|
13
13
|
{{ link }}
|
|
14
14
|
|
|
15
|
+
This link expires in {{ expiresIn }}.
|
|
16
|
+
|
|
15
17
|
If the button does not work, open this link: {{ link }}
|
|
16
18
|
|
|
17
|
-
If you did not ask for this, you can
|
|
18
|
-
ignore this e-mail.
|
|
19
|
+
If you did not ask for this, you can ignore this e-mail.
|
|
19
20
|
|
|
20
21
|
You received this e-mail because you have an account with {{ brand }}.
|
|
21
22
|
|
|
@@ -27,8 +27,8 @@ style="display:inline-block;border-radius:9999px;background-color:#27272a;paddin
|
|
|
27
27
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
28
28
|
<![endif]--> <span style="mso-text-raise:10px;">Choisir un nouveau mot de passe</span>
|
|
29
29
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
30
|
-
<![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);">Si le bouton ne fonctionne pas, ouvrez ce lien : <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:
|
|
31
|
-
de cette demande, vous pouvez ignorer cet e-mail.</p>
|
|
30
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Ce lien expire dans {{ expiresIn }}.</p><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si le bouton ne fonctionne pas, ouvrez ce lien : <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:
|
|
31
|
+
underline;">{{ link }}</a></p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.</p>
|
|
32
32
|
</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;">Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.</p><p style="margin:0;">{{ brand }}</p>
|
|
33
33
|
</td></tr></table></div>
|
|
34
34
|
<!--[if mso]>
|
|
@@ -12,10 +12,11 @@ Choisir un nouveau mot de passe
|
|
|
12
12
|
|
|
13
13
|
{{ link }}
|
|
14
14
|
|
|
15
|
+
Ce lien expire dans {{ expiresIn }}.
|
|
16
|
+
|
|
15
17
|
Si le bouton ne fonctionne pas, ouvrez ce lien : {{ link }}
|
|
16
18
|
|
|
17
|
-
Si vous n'êtes pas à l'origine
|
|
18
|
-
de cette demande, vous pouvez ignorer cet e-mail.
|
|
19
|
+
Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.
|
|
19
20
|
|
|
20
21
|
Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.
|
|
21
22
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
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,0,0,.1);"><h1 style="margin:0 0 16px;font-size:24px;line-height:32px;
|
|
26
26
|
font-weight:600;color:#020918;color:lab(2.35721% .367686 -8.51797);">Votre code de connexion</h1><p style="margin:0 0 16px;font-size:14px;line-height:20px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Saisissez ce code pour vous connecter à {{ brand }}. Il ne fonctionne qu'une fois.</p>
|
|
27
27
|
<table role="presentation" cellpadding="0" cellspacing="0" style="margin-bottom:16px;width:100%;"><tr><td style="border-radius:10px;background-color:#f1f5f9;background-color:lab(96.286% -.852436 -2.46847);padding:16px 24px;text-align:center;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:30px;line-height:36px;font-weight:600;letter-spacing:.3em;color:#020918;color:lab(2.35721% .367686 -8.51797);">{{ code }}
|
|
28
|
-
</td></tr></table><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail : personne ne peut se connecter sans ce code.</p>
|
|
28
|
+
</td></tr></table><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Ce code expire dans {{ expiresIn }}.</p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail : personne ne peut se connecter sans ce code.</p>
|
|
29
29
|
</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;">Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.</p><p style="margin:0;">{{ brand }}</p>
|
|
30
30
|
</td></tr></table></div>
|
|
31
31
|
<!--[if mso]>
|
|
@@ -8,6 +8,8 @@ Saisissez ce code pour vous connecter à {{ brand }}. Il ne fonctionne qu'une fo
|
|
|
8
8
|
|
|
9
9
|
{{ code }}
|
|
10
10
|
|
|
11
|
+
Ce code expire dans {{ expiresIn }}.
|
|
12
|
+
|
|
11
13
|
Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail : personne ne peut se connecter sans ce code.
|
|
12
14
|
|
|
13
15
|
Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.
|
|
@@ -28,8 +28,8 @@ border-radius:9999px;background-color:#27272a;padding:10px 16px;font-family:ui-s
|
|
|
28
28
|
<!--[if mso]><i style="mso-font-width:114%;mso-text-raise:19px;" hidden> </i>
|
|
29
29
|
<![endif]--> <span style="mso-text-raise:10px;">Confirmer mon adresse</span>
|
|
30
30
|
<!--[if mso]><i style="mso-font-width:114%;" hidden> ​</i>
|
|
31
|
-
<![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);">Si le bouton ne fonctionne pas, ouvrez ce lien : <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:
|
|
32
|
-
de cette demande, vous pouvez ignorer cet e-mail.</p>
|
|
31
|
+
<![endif]--></a></div><div role="separator" style="line-height:24px;">‍</div><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#020918;color:lab(2.35721% .367686 -8.51797);">Ce lien expire dans {{ expiresIn }}.</p><p style="margin:0 0 16px;font-size:12px;line-height:16px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si le bouton ne fonctionne pas, ouvrez ce lien : <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration:
|
|
32
|
+
underline;">{{ link }}</a></p><p style="margin:0 0 16px;font-size:10px;color:#62748e;color:lab(48.0876% -2.03595 -16.5814);">Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.</p>
|
|
33
33
|
</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;">Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.</p><p style="margin:0;">{{ brand }}</p>
|
|
34
34
|
</td></tr></table></div>
|
|
35
35
|
<!--[if mso]>
|
|
@@ -12,10 +12,11 @@ Confirmer mon adresse
|
|
|
12
12
|
|
|
13
13
|
{{ link }}
|
|
14
14
|
|
|
15
|
+
Ce lien expire dans {{ expiresIn }}.
|
|
16
|
+
|
|
15
17
|
Si le bouton ne fonctionne pas, ouvrez ce lien : {{ link }}
|
|
16
18
|
|
|
17
|
-
Si vous n'êtes pas à l'origine
|
|
18
|
-
de cette demande, vous pouvez ignorer cet e-mail.
|
|
19
|
+
Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.
|
|
19
20
|
|
|
20
21
|
Vous recevez cet e-mail parce que vous avez un compte chez {{ brand }}.
|
|
21
22
|
|
package/mails/mail-manifest.json
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"reset-password": {
|
|
59
59
|
"variables": [
|
|
60
60
|
"brand",
|
|
61
|
+
"expiresIn",
|
|
61
62
|
"link",
|
|
62
63
|
"name"
|
|
63
64
|
],
|
|
@@ -82,7 +83,8 @@
|
|
|
82
83
|
"sign-in-code": {
|
|
83
84
|
"variables": [
|
|
84
85
|
"brand",
|
|
85
|
-
"code"
|
|
86
|
+
"code",
|
|
87
|
+
"expiresIn"
|
|
86
88
|
],
|
|
87
89
|
"urlVariables": [],
|
|
88
90
|
"subject": {
|
|
@@ -103,6 +105,7 @@
|
|
|
103
105
|
"verify-email": {
|
|
104
106
|
"variables": [
|
|
105
107
|
"brand",
|
|
108
|
+
"expiresIn",
|
|
106
109
|
"link",
|
|
107
110
|
"name"
|
|
108
111
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-mail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The e-mails of @nxgt/janus's flows — e-mail verification, password reset, sign-in code, password changed, e-mail changed — rendered in English and French from a prebuilt Maizzle build, with your brand, sent through any @nxgt/mail transport",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,11 +54,11 @@
|
|
|
54
54
|
"@maizzle/framework": "6.1.7",
|
|
55
55
|
"@maizzle/tailwindcss": "1.5.6",
|
|
56
56
|
"@nxgt/janus": "^0.8.8",
|
|
57
|
-
"@nxgt/mail": "0.
|
|
58
|
-
"@nxgt/mail-config": "0.2.
|
|
59
|
-
"@nxgt/mail-i18n": "0.3.
|
|
60
|
-
"@nxgt/mail-presets": "0.
|
|
61
|
-
"@nxgt/mail-ui": "0.
|
|
57
|
+
"@nxgt/mail": "0.6.0",
|
|
58
|
+
"@nxgt/mail-config": "0.2.1",
|
|
59
|
+
"@nxgt/mail-i18n": "0.3.1",
|
|
60
|
+
"@nxgt/mail-presets": "0.2.0",
|
|
61
|
+
"@nxgt/mail-ui": "0.2.0",
|
|
62
62
|
"@types/bun": "^1.4.2",
|
|
63
63
|
"vue": "3.5.43",
|
|
64
64
|
"vue-tsc": "^3.3.11"
|