@nxgt/janus-mail 0.8.0 → 0.9.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.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The links of the e-mails, in their own file for its length. Re-exported
3
+ * by `./types`.
4
+ */
5
+ /**
6
+ * The links of the e-mails, each an absolute `http(s)` URL of your
7
+ * application. Each is called at send time and must answer a string
8
+ * synchronously: an `async` function or a `URL` object is a compile error,
9
+ * and from JavaScript, or through a cast, the send throws a `TypeError`
10
+ * naming the call. A
11
+ * `mailto:` URL is accepted too — `secureAccount: () => 'mailto:security@acme.example'`
12
+ * sends the user to your support desk; any other scheme is refused by the
13
+ * renderer with a `MailRefused`. The functions are copied when `janusMail()`
14
+ * is called: replacing one afterwards changes nothing.
15
+ */
16
+ export interface JanusMailLinks {
17
+ /** The page that confirms an e-mail, given the one-time token: `(token) => \`https://app.example/verify?token=${token}\`` */
18
+ readonly verifyEmail: (token: string) => string;
19
+ /** The page that sets a new password, given the one-time token. */
20
+ readonly resetPassword: (token: string) => string;
21
+ /**
22
+ * Where a user who did not make a change secures their account — their
23
+ * security settings. `passwordChanged`, `emailChanged`, `twoFactorEnabled`,
24
+ * `twoFactorDisabled` and `newSignIn` link to it, and so does `stepUp`, for
25
+ * a user who asked for no code.
26
+ */
27
+ readonly secureAccount: () => string;
28
+ /**
29
+ * Where a new user starts — your application's home, or its sign-in page
30
+ * for an account someone else created. `welcome` links to it, from its
31
+ * "Get started" button.
32
+ */
33
+ readonly getStarted: () => string;
34
+ /**
35
+ * Where a user regenerates their recovery codes — `recoveryCodeUsed`
36
+ * links to it, from its "Secure my account" button. **Optional**: without
37
+ * it, that e-mail links to `secureAccount`, the security settings where
38
+ * the codes usually are.
39
+ */
40
+ readonly recoveryCodes?: () => string;
41
+ /**
42
+ * The page a sign-in link opens, given its one-time token —
43
+ * `magicLink` links to it. **Optional**, for an application that sends no
44
+ * sign-in link; without it, `magicLink` throws a `TypeError` before
45
+ * anything is rendered. That page spends nothing: its button posts the
46
+ * token to the route that calls `auth.magicLink.confirm`, so a mail
47
+ * scanner that opens the link does not sign in for the user.
48
+ */
49
+ readonly magicLink?: (token: string) => string;
50
+ }
51
+ //# sourceMappingURL=link-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../src/link-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC9B,6HAA6H;IAC7H,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `newSignIn`: the notice of a sign-in from a device the user had not signed
3
+ * in from — which device, when, and where when the sender knows — linking to
4
+ * where the account is secured.
5
+ *
6
+ * Apart from the other notices because of its optional `location`: Janus
7
+ * sees no IP, so the sender may know none, and the e-mail then says so in
8
+ * the send's locale — "Location Unknown", "Lieu Inconnu" (`./no-location`).
9
+ */
10
+ import type { ResolvedOptions } from './options';
11
+ import type { JanusMail } from './types';
12
+ /** The new sign-in notice, over the resolved options. */
13
+ export declare function newSignInMail(options: ResolvedOptions): Pick<JanusMail<string>, 'newSignIn'>;
14
+ //# sourceMappingURL=new-sign-in-mail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new-sign-in-mail.d.ts","sourceRoot":"","sources":["../src/new-sign-in-mail.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAiBzC,yDAAyD;AACzD,wBAAgB,aAAa,CAC5B,OAAO,EAAE,eAAe,GACtB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAiBtC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What `newSignIn` shows where the sender knew no location — "Unknown",
3
+ * "Inconnu" — in the recipient's locale: the value after the row's label,
4
+ * so "Location Unknown", "Lieu Inconnu".
5
+ *
6
+ * A map written here, not a message in `mail/locales/*.json`: those
7
+ * catalogues are read by the Maizzle build alone, and hold only the
8
+ * presets' own keys, which the build checks against `@nxgt/mail-presets`'
9
+ * `en`. This string is a send's — whether a location was given is known
10
+ * only then — so it is chosen at send time, as the codes left are
11
+ * (`./codes-left`). Keyed by `JanusMailLocale`, so a locale added to the
12
+ * build without its text here is a compile error.
13
+ */
14
+ import type { JanusMailLocale } from './generated/locales';
15
+ /** The text of a missing location, by built locale. */
16
+ export declare const NO_LOCATION: Readonly<Record<JanusMailLocale, string>>;
17
+ /**
18
+ * The text of a missing location in `locale` — the one `localeFor` picked —
19
+ * else in its language (`fr` for `fr-CA`), else in `en`: a locale beyond
20
+ * the built ones is sent only through templates of the consumer's own.
21
+ */
22
+ export declare function noLocationText(locale: string): string;
23
+ //# sourceMappingURL=no-location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-location.d.ts","sourceRoot":"","sources":["../src/no-location.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAI/D,CAAC;AAMJ;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAY1D,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAiJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAa1D,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAiJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
@@ -84,6 +84,21 @@ export interface JanusMailVariables {
84
84
  readonly recoveryCodesLeft: string;
85
85
  readonly link: string;
86
86
  };
87
+ /**
88
+ * `device`, `time` and `location` are the sender's text, already in the
89
+ * recipient's locale. Without a `location` from the sender, it is
90
+ * "Unknown" in the send's locale — "Inconnu" in `fr` — the
91
+ * language's text for a regional `en` or `fr` tag, `en`'s for any other.
92
+ * `link` is where the account is secured.
93
+ */
94
+ readonly newSignIn: {
95
+ readonly brand: string;
96
+ readonly name: string;
97
+ readonly device: string;
98
+ readonly location: string;
99
+ readonly time: string;
100
+ readonly link: string;
101
+ };
87
102
  /** `name` is in the subject too: "Welcome, Ada". */
88
103
  readonly welcome: {
89
104
  readonly brand: string;
@@ -91,7 +106,7 @@ export interface JanusMailVariables {
91
106
  readonly link: string;
92
107
  };
93
108
  }
94
- /** The name of one of the eleven templates: `verifyEmail`, `resetPassword`, … */
109
+ /** The name of one of the twelve templates: `verifyEmail`, `resetPassword`, … */
95
110
  export type JanusMailTemplateName = keyof JanusMailVariables;
96
111
  /**
97
112
  * One e-mail: its variables and the locale picked for the recipient, in; its
@@ -102,7 +117,7 @@ export type JanusMailTemplateName = keyof JanusMailVariables;
102
117
  export type JanusMailTemplate<V, L extends string = JanusMailLocale> = (variables: V & {
103
118
  readonly locale: L;
104
119
  }) => Rendered | PromiseLike<Rendered>;
105
- /** The eleven templates, each for the locales `L`. */
120
+ /** The twelve templates, each for the locales `L`. */
106
121
  export type JanusMailTemplates<L extends string = JanusMailLocale> = {
107
122
  readonly [K in JanusMailTemplateName]: JanusMailTemplate<JanusMailVariables[K], L>;
108
123
  };
@@ -1 +1 @@
1
- {"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { JanusMailTemplates } from './types';
2
2
  /**
3
- * The eleven default templates, in the locales they are built in. The
3
+ * The twelve default templates, in the locales they are built in. The
4
4
  * renderer behind them is created on the first render, not here.
5
5
  *
6
6
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CAwEnD"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgClD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CA6DnD"}
package/dist/types.d.ts CHANGED
@@ -6,8 +6,10 @@
6
6
  import type { Clock, IssuedCode, IssuedToken, StepUpByEmail } from '@nxgt/janus';
7
7
  import type { Address, Mailer, SentMail, WantedLocales } from '@nxgt/mail';
8
8
  import type { JanusMailLocale } from './generated/locales';
9
+ import type { JanusMailLinks } from './link-types';
9
10
  import type { JanusMailTemplates } from './template-types';
10
11
  export type { JanusMailLocale } from './generated/locales';
12
+ export type { JanusMailLinks } from './link-types';
11
13
  export type { JanusMailTemplate, JanusMailTemplateName, JanusMailTemplates, JanusMailVariables, } from './template-types';
12
14
  /**
13
15
  * Who an e-mail is for, besides the address: the name it greets, and the
@@ -19,52 +21,6 @@ export interface Recipient {
19
21
  readonly name: string;
20
22
  readonly locale?: WantedLocales;
21
23
  }
22
- /**
23
- * The links of the e-mails, each an absolute `http(s)` URL of your
24
- * application. Each is called at send time and must answer a string
25
- * synchronously: an `async` function or a `URL` object is a compile error,
26
- * and from JavaScript, or through a cast, the send throws a `TypeError`
27
- * naming the call. A
28
- * `mailto:` URL is accepted too — `secureAccount: () => 'mailto:security@acme.example'`
29
- * sends the user to your support desk; any other scheme is refused by the
30
- * renderer with a `MailRefused`. The functions are copied when `janusMail()`
31
- * is called: replacing one afterwards changes nothing.
32
- */
33
- export interface JanusMailLinks {
34
- /** The page that confirms an e-mail, given the one-time token: `(token) => \`https://app.example/verify?token=${token}\`` */
35
- readonly verifyEmail: (token: string) => string;
36
- /** The page that sets a new password, given the one-time token. */
37
- readonly resetPassword: (token: string) => string;
38
- /**
39
- * Where a user who did not make a change secures their account — their
40
- * security settings. `passwordChanged`, `emailChanged`, `twoFactorEnabled`
41
- * and `twoFactorDisabled` link to it, and so does `stepUp`, for a user who
42
- * asked for no code.
43
- */
44
- readonly secureAccount: () => string;
45
- /**
46
- * Where a new user starts — your application's home, or its sign-in page
47
- * for an account someone else created. `welcome` links to it, from its
48
- * "Get started" button.
49
- */
50
- readonly getStarted: () => string;
51
- /**
52
- * Where a user regenerates their recovery codes — `recoveryCodeUsed`
53
- * links to it, from its "Secure my account" button. **Optional**: without
54
- * it, that e-mail links to `secureAccount`, the security settings where
55
- * the codes usually are.
56
- */
57
- readonly recoveryCodes?: () => string;
58
- /**
59
- * The page a sign-in link opens, given its one-time token —
60
- * `magicLink` links to it. **Optional**, for an application that sends no
61
- * sign-in link; without it, `magicLink` throws a `TypeError` before
62
- * anything is rendered. That page spends nothing: its button posts the
63
- * token to the route that calls `auth.magicLink.confirm`, so a mail
64
- * scanner that opens the link does not sign in for the user.
65
- */
66
- readonly magicLink?: (token: string) => string;
67
- }
68
24
  /** The options every locale set shares. */
69
25
  interface JanusMailBaseOptions<L extends string> {
70
26
  /** The transport — `@nxgt/mail-smtp`, `@nxgt/mail-resend`, or `createMemoryMailer()` in tests. */
@@ -200,6 +156,24 @@ export interface JanusMail<L extends string = JanusMailLocale> {
200
156
  readonly when: string;
201
157
  readonly recoveryCodesLeft: number | string;
202
158
  }): Promise<SentMail>;
159
+ /**
160
+ * Tells `to.email` their account was signed in from a device they had
161
+ * not signed in from, `time`, on `device` — and `location`, when you know
162
+ * it — with a link to secure the account: once a sign-in answered
163
+ * `newDevice: true`, or on the `user.newDeviceSignedIn` event.
164
+ *
165
+ * Every value is your text, in the recipient's locale: `device` described
166
+ * from the request (`'Firefox on macOS'`), `time` formatted in their time
167
+ * zone. **Janus sees no IP**: `location` is yours to work out, and an
168
+ * e-mail without one says "Unknown" in its place — "Inconnu" in French.
169
+ */
170
+ newSignIn(to: Recipient & {
171
+ readonly email: string;
172
+ }, signIn: {
173
+ readonly device: string;
174
+ readonly time: string;
175
+ readonly location?: string;
176
+ }): Promise<SentMail>;
203
177
  /** The templates in use: the defaults, with `templates` over them. */
204
178
  readonly templates: JanusMailTemplates<L>;
205
179
  /** The locales sent in. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/docs/README.md CHANGED
@@ -5,7 +5,7 @@ the detail.
5
5
 
6
6
  | Page | Read it when |
7
7
  | --- | --- |
8
- | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the step-up's code, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed` and the change notices on `user.passwordChanged` and `user.emailChanged` among them; the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
8
+ | [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the step-up's code, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed`, the change notices on `user.passwordChanged` and `user.emailChanged` and the new sign-in notice among them; the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
9
9
  | [Templates](guide/templates.md) | You want an e-mail of your own: what a template takes and answers, a partial override, `janusTemplates()`, and what the defaults say |
10
10
  | [Locales](guide/locales.md) | You send in more than one language: how the locale is picked, `fallbackLocale`, fewer locales, and adding one |
11
11
  | [Building](guide/building.md) | You work on this package: the Maizzle project in `mail/`, `mails/`, the generated files, and why none of it reaches a consumer's build |
@@ -23,13 +23,13 @@ They mean the same here. These pages add:
23
23
 
24
24
  | Word | Means | Not |
25
25
  | --- | --- | --- |
26
- | **e-mail** | One of the eleven this package sends: `verify-email`, `reset-password`, `sign-in-code`, `magic-link`, `confirm-action`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `recovery-code-used`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
26
+ | **e-mail** | One of the twelve this package sends: `verify-email`, `reset-password`, `sign-in-code`, `magic-link`, `confirm-action`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `recovery-code-used`, `new-sign-in`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
27
27
  | **template** | The function that renders one e-mail: `(variables & { locale }) => Rendered`. Named in camelCase, after the method that sends it: `verifyEmail`, `signInCode` | "renderer" — the renderer is `@nxgt/mail`'s, and reads the build |
28
28
  | **default template** | A template of `janusTemplates()`: the prebuilt e-mail, filled by the renderer | "preset" — a preset is `@nxgt/mail-presets`' source, which the build compiles |
29
29
  | **override** | A template of yours, passed in `templates`, used instead of the default | "custom template" |
30
30
  | **issued** | What a flow answered: an `IssuedToken` from `verifyEmail.send`, `resetPassword.request` or `magicLink.request`, an `IssuedCode` from `signInCode.request`, a `StepUpByEmail` from `stepUp.request` once narrowed to `via: 'email'` | "result", "payload" |
31
31
  | **recipient** | Who an e-mail is for besides the address: a `Recipient`, `{ name, locale? }` | "user" — a user is a record of `@nxgt/janus`; "to" is the address |
32
- | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
32
+ | **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`, `newSignIn`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
33
33
  | **welcome** | The e-mail sent once a user was created, on `@nxgt/janus`'s `user.created`: `welcome`. It links to `links.getStarted()` — not a notice, since it warns of nothing | "onboarding", "greeting" |
34
34
  | **codes left** | How many recovery codes the user still holds, as the recovery code notice says it: `recoveryCodesLeft`, a sentence written from the count with the preset's plural in the recipient's locale — "You have 1 recovery code left." — or passed as text | "remaining codes" — the count itself is `@nxgt/janus`'s `recoveryCodesLeft` |
35
35
  | **expiry** | How long a link or a code lasts, as the e-mail says it: `expiresIn`, derived from the flow's `expiresAt` in the recipient's locale — "1 hour", "1 heure" — or passed per send | "TTL", "lifetime" — the `tokens` TTL is `@nxgt/janus`'s setting, which sets `expiresAt` |
@@ -55,7 +55,7 @@ packages/janus-mail/
55
55
  `src/generated/mail.ts` (`rendererTypes`), from the build.
56
56
  3. `scripts/build-mail.ts` reads the manifest and **fails unless its
57
57
  format is one every `@nxgt/mail` the peer admits reads** — see below —
58
- and **unless exactly the eleven e-mails were built**: a twelfth from a new
58
+ and **unless exactly the twelve e-mails were built**: a thirteenth from a new
59
59
  `@nxgt/mail-presets`, or one missing, stops the build.
60
60
  4. It writes `src/generated/locales.ts` from the manifest's locales, and
61
61
  `src/generated/codes-left.ts` — below — each only when its content
@@ -89,6 +89,8 @@ const templates: JanusMailTemplates<Locale> = {
89
89
  twoFactorDisabled: ({ name, link, locale }) => render('two-factor-disabled', locale, { name, link }),
90
90
  recoveryCodeUsed: ({ name, when, recoveryCodesLeft, link, locale }) =>
91
91
  render('recovery-code-used', locale, { name, when, recoveryCodesLeft, link }),
92
+ newSignIn: ({ name, device, location, time, link, locale }) =>
93
+ render('new-sign-in', locale, { name, device, location, time, link }),
92
94
  welcome: ({ name, link, locale }) => render('welcome', locale, { name, link }),
93
95
  };
94
96
 
@@ -102,8 +104,13 @@ mail.locales; // readonly ('en' | 'fr' | 'de')[]
102
104
  plural of the codes left in `en` and `fr` only, so a German recipient's
103
105
  `mail.recoveryCodeUsed` takes the sentence as text — "Sie haben noch 3
104
106
  Wiederherstellungscodes." — and a count is a `TypeError` there.
107
+ `location`, left out of a `newSignIn`, is "Unknown" for `de`:
108
+ the text of a missing location is held in `en` and `fr` only, so pass
109
+ `location` yourself — "Unbekannt", so the row reads `Ort Unbekannt` —
110
+ where it matters.
105
111
  `recoveryCodeUsed` is the ninth template, since 0.5.0, `magicLink` the
106
- tenth, since 0.7.0, and `stepUp` the eleventh, since 0.8.0: a `templates`
112
+ tenth, since 0.7.0, `stepUp` the eleventh, since 0.8.0, and `newSignIn` the
113
+ twelfth, since 0.9.0: a `templates`
107
114
  written for an earlier version no longer
108
115
  compiles with a locale beyond `en` and `fr` — add the one it lacks. A
109
116
  renderer over a Maizzle build of your own, with
@@ -112,13 +119,13 @@ With only some templates, the call does not compile — the error lands on
112
119
  `templates`:
113
120
 
114
121
  ```text
115
- TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and 6 more.
122
+ TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and 7 more.
116
123
  ```
117
124
 
118
125
  and in JavaScript it throws when `janusMail()` is called:
119
126
 
120
127
  ```text
121
- TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, welcome missing
128
+ TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, newSignIn, welcome missing
122
129
  ```
123
130
 
124
131
  A default template is typed for `'en' | 'fr'`, so reusing one in a wider set
@@ -131,4 +138,4 @@ Built-in locales for more languages are on the [roadmap](../roadmap.md).
131
138
 
132
139
  - `@nxgt/mail`'s [locales guide](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail/docs/guide/locales.md)
133
140
  — `pickLocale` and `parseAcceptLanguage` in full.
134
- - [Templates](templates.md) — writing the eleven for a new locale.
141
+ - [Templates](templates.md) — writing the twelve for a new locale.
@@ -46,7 +46,7 @@ time one is sent, and kept.
46
46
  | `brand` | `string` | required | The name the e-mails show, in the header, the body and the footer. Text: escaped in the HTML |
47
47
  | `links.verifyEmail` | `(token) => string` | required | The page that confirms an address, given the one-time token |
48
48
  | `links.resetPassword` | `(token) => string` | required | The page that sets a new password, given the one-time token |
49
- | `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it, and so does the step-up's code, for a user who asked for none |
49
+ | `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it — the new sign-in's included — and so does the step-up's code, for a user who asked for none |
50
50
  | `links.getStarted` | `() => string` | required | Where a new user starts — your home page, or your sign-in page for an account someone else created: the welcome's **Get started** button links to it |
51
51
  | `links.magicLink` | `(token) => string` | none: `magicLink` is a `TypeError` without it | The page a sign-in link opens, given the one-time token. That page spends nothing: its button posts the token to the route that calls `auth.magicLink.confirm` |
52
52
  | `links.recoveryCodes` | `() => string` | `links.secureAccount` | Where a user regenerates their recovery codes: the recovery code notice links to it, from its **Secure my account** button |
@@ -349,6 +349,56 @@ when opening the session then fails.
349
349
  codes; without it, `links.secureAccount()`. `recoveryCodes` is optional,
350
350
  so a `links` written for 0.4 still compiles.
351
351
 
352
+ ### `newSignIn(to, { device, time, location? })`
353
+
354
+ ```ts
355
+ // From the sign-in's answer, with @nxgt/janus given devices: { keys } and the device cookie:
356
+ const signedIn = await auth.signIn({ email, password }, { device: cookie ?? null });
357
+ if (signedIn.newDevice) {
358
+ const { name, email, locale } = signedIn.user;
359
+ await mail.newSignIn(
360
+ { name, email, locale },
361
+ {
362
+ device: describeDevice(request.headers.get('user-agent')), // yours: 'Firefox on macOS'
363
+ time: new Intl.DateTimeFormat(locale ?? 'en', {
364
+ dateStyle: 'long',
365
+ timeStyle: 'short',
366
+ timeZone: 'Europe/Paris', // the user's, when you keep it
367
+ }).format(signedIn.session.createdAt),
368
+ location: 'Lyon, France', // optional: from a geo-IP of yours
369
+ },
370
+ );
371
+ }
372
+ ```
373
+
374
+ A notice for the account at `to.email`: it was signed in from a device the
375
+ user had not signed in from — which device, where and when — with a
376
+ **Secure my account** button to `links.secureAccount()`. Send it when a
377
+ `@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or on the
378
+ `user.newDeviceSignedIn` event it sends then — the event names the user by
379
+ id alone, so read the name, the locale and the address from the user, and
380
+ it carries no request to describe the device from.
381
+
382
+ - **Every value is your text**, already in the recipient's locale: `device`
383
+ as you describe the request — "Firefox on macOS", "Firefox sur macOS" —
384
+ and `time` formatted in their time zone: "29 septembre 2026 à 09:12".
385
+ The session's `createdAt`, or the event's `occurredAt`, is when it
386
+ happened.
387
+ - **`location` is optional, and yours.** `@nxgt/janus` sees no IP: a
388
+ location comes from a geo-IP of your own, if you have one. Left out, the
389
+ e-mail says so in the locale it is sent in — `Location Unknown`, `Lieu
390
+ Inconnu` — since every line of the summary is always there. A locale
391
+ beyond `en` and `fr`, sent through templates of your own, gets its
392
+ language's text when it is `en` or `fr` (`fr-CA` gets "Inconnu"),
393
+ and "Unknown" otherwise: pass `location` yourself there. `null` is a
394
+ compile error, and in JavaScript `janusMail.newSignIn: location must be a
395
+ string`: spread it in only when you have one,
396
+ `...(city === null ? {} : { location: city })`.
397
+ - **The text part** gives each line of the summary on one line, its label
398
+ then its value — `Device Firefox on macOS`, `Location Unknown`,
399
+ `Time September 29, 2026 at 9:12 AM` — one row per line, with no blank line
400
+ between rows.
401
+
352
402
  ## The expiry
353
403
 
354
404
  `verifyEmail`, `resetPassword`, `signInCode`, `magicLink` and `stepUp` say
@@ -406,6 +456,7 @@ a `TypeError`, and nothing reaches the mailer: revive the date with
406
456
  | `emailChanged` | `to.formerEmail` | `to.newEmail` |
407
457
  | `twoFactorEnabled`, `twoFactorDisabled` | `to.email` | — |
408
458
  | `recoveryCodeUsed` | `to.email` | — |
459
+ | `newSignIn` | `to.email` | — |
409
460
  | `welcome` | `to.email` | — |
410
461
 
411
462
  `issued.email` is the address the user's record holds, as they registered
@@ -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 eleven, using the defaults on their
4
+ is given and answers, replacing some of the twelve, 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 ten others stay the defaults.
25
+ The sign-in code e-mail is now yours; the eleven others stay the defaults.
26
26
 
27
27
  ## What a template is
28
28
 
@@ -69,14 +69,18 @@ e-mail.
69
69
  | `twoFactorEnabled` | `brand`, `name`, `link` | `mail.twoFactorEnabled(to)` |
70
70
  | `twoFactorDisabled` | `brand`, `name`, `link` | `mail.twoFactorDisabled(to)` |
71
71
  | `recoveryCodeUsed` | `brand`, `name`, `when`, `recoveryCodesLeft`, `link` | `mail.recoveryCodeUsed(to, { when, recoveryCodesLeft })` |
72
+ | `newSignIn` | `brand`, `name`, `device`, `location`, `time`, `link` | `mail.newSignIn(to, { device, time, location? })` |
72
73
  | `welcome` | `brand`, `name`, `link` | `mail.welcome(to)` |
73
74
 
74
75
  Every one also gets `locale`, one of `locales`. Every value is a string:
75
76
  `brand` from the options, `name` from the recipient, `link` from `links` —
76
- `links.secureAccount()` for the four notices and `stepUp`, `links.recoveryCodes()` (else
77
+ `links.secureAccount()` for the notices other than `recoveryCodeUsed` and
78
+ for `stepUp`, `links.recoveryCodes()` (else
77
79
  `links.secureAccount()`) for `recoveryCodeUsed`, `links.getStarted()` for
78
- `welcome` — `code`, `newEmail` and `when` from the
79
- call, and `expiresIn` derived from the flow's `expiresAt` in the locale, or
80
+ `welcome` — `code`, `newEmail`, `when`, `device` and `time` from the
81
+ call, `location` from the call or, without one, "Unknown" in the
82
+ send's locale — "Inconnu" in `fr`; for another locale, its
83
+ language's text when it is `en` or `fr` (`fr-CA`), else `en`'s — and `expiresIn` derived from the flow's `expiresAt` in the locale, or
80
84
  the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
81
85
 
82
86
  `recoveryCodesLeft` is a **sentence**, not the count: "You have 9 recovery
@@ -126,11 +130,12 @@ methods live on its prototype and are refused — see
126
130
  [troubleshooting](../troubleshooting.md#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-).
127
131
 
128
132
  `templates` is `Partial` while `locales` stays within `en` and `fr`: give
129
- any of the eleven, and the defaults render the rest. Once `locales` holds
130
- another locale, it takes **all eleven** — `stepUp` included, since 0.8 — see
133
+ any of the twelve, and the defaults render the rest. Once `locales` holds
134
+ another locale, it takes **all twelve** — `stepUp` included, since 0.8, and
135
+ `newSignIn`, since 0.9 — see
131
136
  [Adding a locale](locales.md#adding-a-locale).
132
137
 
133
- `mail.templates` answers the eleven in use — yours, and the defaults for the
138
+ `mail.templates` answers the twelve in use — yours, and the defaults for the
134
139
  rest — frozen:
135
140
 
136
141
  ```ts
@@ -152,7 +157,7 @@ const { subject, html, text } = await defaults.resetPassword({
152
157
  });
153
158
  ```
154
159
 
155
- It answers the eleven defaults, typed for `'en' | 'fr'`, without a mailer:
160
+ It answers the twelve defaults, typed for `'en' | 'fr'`, without a mailer:
156
161
  to preview an e-mail, to wrap one — add a line to the default text, say — or
157
162
  to send one through something other than `janusMail()`. A default reused for
158
163
  a wider set of locales is a compile error: it could not render the one it
@@ -178,7 +183,7 @@ them renders: that is when the package's `mails/` folder is read, once.
178
183
  ## What the defaults say
179
184
 
180
185
  The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail-presets)
181
- 1.1.0, built with a neutral grey theme and no logo:
186
+ 1.1.1, built with a neutral grey theme and no logo:
182
187
 
183
188
  | Template | Built from | Subject (`en`) | Subject (`fr`) |
184
189
  | --- | --- | --- | --- |
@@ -192,18 +197,25 @@ The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail
192
197
  | `twoFactorEnabled` | `two-factor-enabled` | Two-factor authentication was turned on | L'authentification à deux facteurs a été activée |
193
198
  | `twoFactorDisabled` | `two-factor-disabled` | Two-factor authentication was turned off | L'authentification à deux facteurs a été désactivée |
194
199
  | `recoveryCodeUsed` | `recovery-code-used` | A recovery code was used on your account | Un code de récupération a été utilisé sur votre compte |
200
+ | `newSignIn` | `new-sign-in` | New sign-in to your account | Nouvelle connexion à votre compte |
195
201
  | `welcome` | `welcome` | Welcome, `Ada` | Bienvenue, `Ada` |
196
202
 
197
203
  Each has a header and a footer with the brand, a heading, a greeting by name
198
204
  (but `signInCode` and `magicLink`), a button with its link and the link again in text for a
199
- client that shows no button, and a text part. The four notices but `recoveryCodeUsed` add a warning:
205
+ client that shows no button, and a text part. The notices other than `recoveryCodeUsed` add a warning:
200
206
  *if this was not you, secure your account now*. `stepUp` shows its code as
201
207
  the sign-in code does, names no action — *someone, we hope you, asked to do
202
208
  something sensitive on your account* — and warns not to share the code if
203
209
  it was not the user, its button saying **Secure my account**. `recoveryCodeUsed` says it
204
210
  too, after a warning banner — *a recovery code was used on your account at
205
211
  `when`* — the codes left, and a line on generating new ones; its button says
206
- **Secure my account**. `welcome` has the brand in
212
+ **Secure my account**. `newSignIn` says the account *was signed in from a
213
+ device we had not seen*, then a summary of three lines — **Device**,
214
+ **Location** and **Time**, *Appareil*, *Lieu* and *Heure* in French — then
215
+ *if this was you, there is nothing to do; if this was not you, secure your
216
+ account now*, its button saying **Secure my account**; in the text part each
217
+ line of the summary is one line, its label then its value — `Device Firefox
218
+ on macOS` — with no blank line between rows. `welcome` has the brand in
207
219
  its heading — "Welcome to Acme" — and the recipient's name in its subject,
208
220
  filled at send time like the body; its button says **Get started**.
209
221
 
package/docs/roadmap.md CHANGED
@@ -9,9 +9,8 @@ Nothing between releases.
9
9
 
10
10
  ## Next
11
11
 
12
- - **The other presets** — `new-sign-in` and `invitation` from
13
- `@nxgt/mail-presets`, each once `@nxgt/janus` has a flow that sends it: a
14
- sign-in from a new device, an invitation to a user type.
12
+ - **The invitation** — `invitation` from `@nxgt/mail-presets`, once
13
+ `@nxgt/janus` has a flow that sends it: an invitation to a user type.
15
14
  - **The other presets of `@nxgt/mail-presets` 0.4.0** — `account-deleted`,
16
15
  after a user is deleted, once `@nxgt/janus` has a deletion a link can
17
16
  undo; and `invitation-accepted`, once it has invitations.
@@ -47,6 +46,24 @@ Nothing between releases.
47
46
 
48
47
  Newest first; from the first release on, the package's CHANGELOG holds every one.
49
48
 
49
+ - **A readable summary and a location in words, v0.9.1.** Built with
50
+ `@nxgt/mail-config` 1.0.2 and `@nxgt/mail-presets` 1.1.1, the text part of
51
+ `newSignIn` gives each row of its summary on one line — `Device Firefox on
52
+ macOS` — with no blank line between rows, where it split each into a label
53
+ line and a value line. A new sign-in sent without `location` says
54
+ "Unknown" — `Location Unknown`, `Lieu Inconnu` in French — in the
55
+ recipient's locale, where it showed `—`.
56
+
57
+ - **The new sign-in notice, v0.9.0.** `newSignIn(to, { device, time,
58
+ location? })`, built from `@nxgt/mail-presets` 1.1.0's `new-sign-in` in
59
+ English and French — "New sign-in to your account", which device, where
60
+ and when, and a **Secure my account** button to `links.secureAccount()`:
61
+ send it when an `@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or
62
+ on `user.newDeviceSignedIn`. Every value is your text in the recipient's
63
+ locale; `@nxgt/janus` sees no IP, so `location` is yours, and `—` without
64
+ one. Twelve e-mails now, and twelve templates to pass for a locale beyond
65
+ `en` and `fr`.
66
+
50
67
  - **The step-up's code, v0.8.0.** `stepUp(issued, to, options?)`, built
51
68
  from `@nxgt/mail-presets` 1.1.0's `confirm-action` in English and French:
52
69
  send what `@nxgt/janus`'s `auth.stepUp.request(user)` answered, once
@@ -121,23 +138,3 @@ Newest first; from the first release on, the package's CHANGELOG holds every one
121
138
  linking to `links.secureAccount()`: send them on `@nxgt/janus` 0.9's
122
139
  `user.secondFactorEnabled` and `user.secondFactorDisabled` events. Seven
123
140
  e-mails now, and seven templates to pass for a locale beyond `en` and `fr`.
124
- - **Dark mode, v0.3.0.** The e-mails follow the reader's dark mode in
125
- every client that supports it, Gmail excepted: a dark page, a dark card and
126
- light text. Built from `@nxgt/mail-ui` 0.4.0, `@nxgt/mail-presets` 0.4.0
127
- and `@nxgt/mail-i18n` 0.5.0; the text parts, subjects and variables are
128
- unchanged, the manifest is still format 1, and the `@nxgt/mail` peer stays
129
- `>=0.1.0 <1`.
130
-
131
- - **The expiry in the e-mail, v0.2.0.** The verification, reset and sign-in
132
- code e-mails say how long the link or code lasts — "1 hour", "1 heure" —
133
- derived from the flow's `expiresAt` in the recipient's locale, measured
134
- against `janusMail({ clock })` (the clock given to `janus()`), or given per
135
- send as `{ expiresIn }`. Two breaks: a template called directly takes
136
- `expiresIn`, and `signInCode` reads `issued.expiresAt`. Built from
137
- `@nxgt/mail-presets` 0.2.0, whose text parts keep each paragraph on one
138
- line.
139
-
140
- - **The first release, v0.1.0.** The five e-mails of `@nxgt/janus`'s flows,
141
- in English and French, over any `@nxgt/mail` transport: `janusMail()` and
142
- `janusTemplates()`. Built with Maizzle when the package is built, shipped
143
- in `mails/`, and only filled in at send time, every value escaped.