@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.
Files changed (43) hide show
  1. package/README.md +56 -15
  2. package/dist/expiring-mails.d.ts +7 -0
  3. package/dist/expiring-mails.d.ts.map +1 -0
  4. package/dist/expiry.d.ts +29 -0
  5. package/dist/expiry.d.ts.map +1 -0
  6. package/dist/generated/mail.d.ts +3 -0
  7. package/dist/generated/mail.d.ts.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +152 -84
  11. package/dist/index.js.map +10 -6
  12. package/dist/janus-mail.d.ts +6 -0
  13. package/dist/janus-mail.d.ts.map +1 -1
  14. package/dist/notice-mails.d.ts +12 -0
  15. package/dist/notice-mails.d.ts.map +1 -0
  16. package/dist/options.d.ts +2 -0
  17. package/dist/options.d.ts.map +1 -1
  18. package/dist/send.d.ts +27 -0
  19. package/dist/send.d.ts.map +1 -0
  20. package/dist/templates.d.ts.map +1 -1
  21. package/dist/types.d.ts +36 -7
  22. package/dist/types.d.ts.map +1 -1
  23. package/docs/README.md +2 -1
  24. package/docs/guide/building.md +1 -1
  25. package/docs/guide/locales.md +6 -4
  26. package/docs/guide/sending.md +65 -11
  27. package/docs/guide/templates.md +16 -14
  28. package/docs/roadmap.md +9 -2
  29. package/docs/troubleshooting.md +97 -4
  30. package/mails/en/reset-password.html +2 -2
  31. package/mails/en/reset-password.txt +3 -2
  32. package/mails/en/sign-in-code.html +1 -1
  33. package/mails/en/sign-in-code.txt +2 -0
  34. package/mails/en/verify-email.html +2 -2
  35. package/mails/en/verify-email.txt +3 -2
  36. package/mails/fr/reset-password.html +2 -2
  37. package/mails/fr/reset-password.txt +3 -2
  38. package/mails/fr/sign-in-code.html +1 -1
  39. package/mails/fr/sign-in-code.txt +2 -0
  40. package/mails/fr/verify-email.html +2 -2
  41. package/mails/fr/verify-email.txt +3 -2
  42. package/mails/mail-manifest.json +4 -1
  43. package/package.json +6 -6
@@ -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. `expiresAt` is not given either — the flows
75
- answer it, so a template that states the lifetime takes it from a closure:
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
- // The code lives ten minutes, @nxgt/janus's default for tokens.signInCode.
82
- signInCode: ({ code, locale }) =>
82
+ signInCode: ({ code, expiresIn, locale }) =>
83
83
  locale === 'fr'
84
- ? { subject: `Code : ${code}`, html: `<p>${code} — valable 10 minutes.</p>`, text: `${code} — valable 10 minutes.` }
85
- : { subject: `Code: ${code}`, html: `<p>${code} — valid for 10 minutes.</p>`, text: `${code} — valid for 10 minutes.` },
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.1.0, built with a neutral grey theme and no logo:
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
- **They do not state how long a link or a code lasts.** Replace the template
171
- when yours must: see above.
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
@@ -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`, or a
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>&emsp;</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>&emsp;&#8203;</i>
31
- <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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><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
32
- ignore this e-mail.</p>
31
+ <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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>&emsp;</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>&emsp;&#8203;</i>
31
- <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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><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
32
- ignore this e-mail.</p>
31
+ <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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>&emsp;</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>&emsp;&#8203;</i>
30
- <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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&nbsp;: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration: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
31
- de cette demande, vous pouvez ignorer cet e-mail.</p>
30
+ <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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&nbsp;: <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&nbsp;: 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&nbsp;: 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>&emsp;</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>&emsp;&#8203;</i>
31
- <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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&nbsp;: <a href="{{ link }}" style="color:#54a2ff;color:lab(65.0361% -1.42065 -56.9802);text-decoration: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
32
- de cette demande, vous pouvez ignorer cet e-mail.</p>
31
+ <![endif]--></a></div><div role="separator" style="line-height:24px;">&zwj;</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&nbsp;: <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
 
@@ -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.1.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.5.1",
58
- "@nxgt/mail-config": "0.2.0",
59
- "@nxgt/mail-i18n": "0.3.0",
60
- "@nxgt/mail-presets": "0.1.2",
61
- "@nxgt/mail-ui": "0.1.1",
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"