@c9up/rover 0.1.8 → 0.1.10
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 +1 -1
- package/dist/BaseMail.d.ts +41 -1
- package/dist/BaseMail.d.ts.map +1 -1
- package/dist/BaseMail.js +48 -1
- package/dist/BaseMail.js.map +1 -1
- package/dist/Mail.d.ts +90 -24
- package/dist/Mail.d.ts.map +1 -1
- package/dist/Mail.js +123 -40
- package/dist/Mail.js.map +1 -1
- package/dist/MessageBuilder.d.ts +296 -8
- package/dist/MessageBuilder.d.ts.map +1 -1
- package/dist/MessageBuilder.js +537 -8
- package/dist/MessageBuilder.js.map +1 -1
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -1
- package/dist/format.d.ts +10 -0
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +28 -1
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/testing/FakeMail.d.ts +33 -0
- package/dist/testing/FakeMail.d.ts.map +1 -1
- package/dist/testing/FakeMail.js +28 -0
- package/dist/testing/FakeMail.js.map +1 -1
- package/dist/transports/BrevoTransport.d.ts.map +1 -1
- package/dist/transports/BrevoTransport.js +7 -5
- package/dist/transports/BrevoTransport.js.map +1 -1
- package/dist/transports/MailgunTransport.d.ts.map +1 -1
- package/dist/transports/MailgunTransport.js +5 -3
- package/dist/transports/MailgunTransport.js.map +1 -1
- package/dist/transports/ResendTransport.d.ts.map +1 -1
- package/dist/transports/ResendTransport.js +7 -5
- package/dist/transports/ResendTransport.js.map +1 -1
- package/dist/transports/SendGridTransport.d.ts.map +1 -1
- package/dist/transports/SendGridTransport.js +10 -6
- package/dist/transports/SendGridTransport.js.map +1 -1
- package/dist/transports/SesTransport.d.ts.map +1 -1
- package/dist/transports/SesTransport.js +16 -7
- package/dist/transports/SesTransport.js.map +1 -1
- package/dist/transports/SparkPostTransport.d.ts.map +1 -1
- package/dist/transports/SparkPostTransport.js +7 -5
- package/dist/transports/SparkPostTransport.js.map +1 -1
- package/dist/transports/fetchError.d.ts +10 -0
- package/dist/transports/fetchError.d.ts.map +1 -1
- package/dist/transports/fetchError.js +30 -0
- package/dist/transports/fetchError.js.map +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +6 -1
- package/src/BaseMail.ts +59 -2
- package/src/Mail.ts +194 -65
- package/src/MessageBuilder.ts +756 -12
- package/src/config.ts +46 -0
- package/src/format.ts +33 -1
- package/src/index.ts +31 -2
- package/src/testing/FakeMail.ts +46 -0
- package/src/transports/BrevoTransport.ts +7 -5
- package/src/transports/MailgunTransport.ts +5 -3
- package/src/transports/ResendTransport.ts +7 -5
- package/src/transports/SendGridTransport.ts +10 -6
- package/src/transports/SesTransport.ts +16 -7
- package/src/transports/SparkPostTransport.ts +17 -11
- package/src/transports/fetchError.ts +39 -0
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Providers push delivery events (`delivered`, `bounced`, `failed`) to a URL you c
|
|
|
27
27
|
import { createMailgunWebhookHandler } from '@c9up/rover/webhooks/mailgun'
|
|
28
28
|
import { Emitter } from '@c9up/ream/events'
|
|
29
29
|
|
|
30
|
-
const emitter = app.container.resolve(Emitter)
|
|
30
|
+
const emitter = await app.container.resolve(Emitter)
|
|
31
31
|
|
|
32
32
|
router.post(
|
|
33
33
|
'/webhooks/mailgun',
|
package/dist/BaseMail.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ export type MailAddress = string | {
|
|
|
3
3
|
address: string;
|
|
4
4
|
name?: string;
|
|
5
5
|
};
|
|
6
|
+
/** The little a mail needs of a mailer to dispatch itself. */
|
|
7
|
+
export interface MailSender<R> {
|
|
8
|
+
send(mail: BaseMail): Promise<R>;
|
|
9
|
+
sendLater(mail: BaseMail): Promise<unknown>;
|
|
10
|
+
}
|
|
6
11
|
/**
|
|
7
12
|
* Abstract base for class-based mail messages (Adonis parity).
|
|
8
13
|
*
|
|
@@ -18,12 +23,47 @@ export type MailAddress = string | {
|
|
|
18
23
|
* await mail.send(new WelcomeMail(user));
|
|
19
24
|
*/
|
|
20
25
|
export declare abstract class BaseMail {
|
|
21
|
-
|
|
26
|
+
#private;
|
|
27
|
+
/**
|
|
28
|
+
* The message this mail builds.
|
|
29
|
+
*
|
|
30
|
+
* Public, because a test asserts on it from outside —
|
|
31
|
+
* `mails.assertSent(WelcomeMail, (mail) => mail.message.hasTo(user.email))`
|
|
32
|
+
* — and a caller may add a recipient before dispatching.
|
|
33
|
+
*/
|
|
34
|
+
readonly message: MessageBuilder;
|
|
22
35
|
from?: MailAddress;
|
|
23
36
|
replyTo?: MailAddress;
|
|
24
37
|
subject?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Whether {@link build} has already run (AdonisJS `built`).
|
|
40
|
+
*
|
|
41
|
+
* Without it, building twice replayed `prepare()` against the SAME
|
|
42
|
+
* `MessageBuilder`, so every recipient and every attachment was added
|
|
43
|
+
* again — a mail asserted on with `buildWithContents()` and then sent went
|
|
44
|
+
* out twice to the same address, with the attachment doubled. Building
|
|
45
|
+
* twice is ordinary: inspect then send, or retry a send at the app level.
|
|
46
|
+
*/
|
|
47
|
+
built: boolean;
|
|
25
48
|
constructor();
|
|
26
49
|
abstract prepare(): void | Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Send this mail through `mailer`.
|
|
52
|
+
*
|
|
53
|
+
* await new WelcomeMail(user).send(mail.use('smtp'))
|
|
54
|
+
*
|
|
55
|
+
* The mail object is the dispatchable unit, so it can be handed around and
|
|
56
|
+
* sent by whoever holds a mailer.
|
|
57
|
+
*/
|
|
58
|
+
send<R>(mailer: MailSender<R>): Promise<R>;
|
|
59
|
+
/** Queue this mail for background delivery (Adonis `sendLater`). */
|
|
60
|
+
sendLater(mailer: MailSender<unknown>): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Build the message AND render its templates ahead of time (Adonis
|
|
63
|
+
* `buildWithContents`), so the contents can be inspected before sending —
|
|
64
|
+
* which is what an assertion on the rendered html needs.
|
|
65
|
+
*/
|
|
66
|
+
buildWithContents(viewsRoot?: string): Promise<MailMessage>;
|
|
27
67
|
build(viewsRoot?: string): Promise<MailMessage>;
|
|
28
68
|
}
|
|
29
69
|
//# sourceMappingURL=BaseMail.d.ts.map
|
package/dist/BaseMail.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseMail.d.ts","sourceRoot":"","sources":["../src/BaseMail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEvE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE
|
|
1
|
+
{"version":3,"file":"BaseMail.d.ts","sourceRoot":"","sources":["../src/BaseMail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEvE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,8DAA8D;AAC9D,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,8BAAsB,QAAQ;;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAwB;IAExD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;;;;OAQG;IACH,KAAK,UAAS;;IAYd,QAAQ,CAAC,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAExC;;;;;;;OAOG;IACG,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhD,oEAAoE;IAC9D,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D;;;;OAIG;IACG,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI3D,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAwBrD"}
|
package/dist/BaseMail.js
CHANGED
|
@@ -14,16 +14,62 @@ import { MessageBuilder } from "./MessageBuilder.js";
|
|
|
14
14
|
* await mail.send(new WelcomeMail(user));
|
|
15
15
|
*/
|
|
16
16
|
export class BaseMail {
|
|
17
|
+
/**
|
|
18
|
+
* The message this mail builds.
|
|
19
|
+
*
|
|
20
|
+
* Public, because a test asserts on it from outside —
|
|
21
|
+
* `mails.assertSent(WelcomeMail, (mail) => mail.message.hasTo(user.email))`
|
|
22
|
+
* — and a caller may add a recipient before dispatching.
|
|
23
|
+
*/
|
|
17
24
|
message = new MessageBuilder();
|
|
18
25
|
from;
|
|
19
26
|
replyTo;
|
|
20
27
|
subject;
|
|
28
|
+
/**
|
|
29
|
+
* Whether {@link build} has already run (AdonisJS `built`).
|
|
30
|
+
*
|
|
31
|
+
* Without it, building twice replayed `prepare()` against the SAME
|
|
32
|
+
* `MessageBuilder`, so every recipient and every attachment was added
|
|
33
|
+
* again — a mail asserted on with `buildWithContents()` and then sent went
|
|
34
|
+
* out twice to the same address, with the attachment doubled. Building
|
|
35
|
+
* twice is ordinary: inspect then send, or retry a send at the app level.
|
|
36
|
+
*/
|
|
37
|
+
built = false;
|
|
38
|
+
/** The message the first build produced, returned by every later call. */
|
|
39
|
+
#builtMessage;
|
|
21
40
|
constructor() {
|
|
22
41
|
if (new.target === BaseMail) {
|
|
23
42
|
throw new Error("BaseMail is abstract and cannot be instantiated directly");
|
|
24
43
|
}
|
|
25
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Send this mail through `mailer`.
|
|
47
|
+
*
|
|
48
|
+
* await new WelcomeMail(user).send(mail.use('smtp'))
|
|
49
|
+
*
|
|
50
|
+
* The mail object is the dispatchable unit, so it can be handed around and
|
|
51
|
+
* sent by whoever holds a mailer.
|
|
52
|
+
*/
|
|
53
|
+
async send(mailer) {
|
|
54
|
+
return mailer.send(this);
|
|
55
|
+
}
|
|
56
|
+
/** Queue this mail for background delivery (Adonis `sendLater`). */
|
|
57
|
+
async sendLater(mailer) {
|
|
58
|
+
await mailer.sendLater(this);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the message AND render its templates ahead of time (Adonis
|
|
62
|
+
* `buildWithContents`), so the contents can be inspected before sending —
|
|
63
|
+
* which is what an assertion on the rendered html needs.
|
|
64
|
+
*/
|
|
65
|
+
async buildWithContents(viewsRoot) {
|
|
66
|
+
return this.build(viewsRoot);
|
|
67
|
+
}
|
|
26
68
|
async build(viewsRoot) {
|
|
69
|
+
if (this.built && this.#builtMessage !== undefined) {
|
|
70
|
+
return this.#builtMessage;
|
|
71
|
+
}
|
|
72
|
+
this.built = true;
|
|
27
73
|
if (this.from !== undefined) {
|
|
28
74
|
applyAddress(this.from, (address, name) => this.message.from(address, name));
|
|
29
75
|
}
|
|
@@ -36,7 +82,8 @@ export class BaseMail {
|
|
|
36
82
|
await this.prepare();
|
|
37
83
|
// Forward the owning Mail's per-instance viewsRoot so template
|
|
38
84
|
// resolution doesn't depend on the process-wide mutable global.
|
|
39
|
-
|
|
85
|
+
this.#builtMessage = await this.message.build(viewsRoot);
|
|
86
|
+
return this.#builtMessage;
|
|
40
87
|
}
|
|
41
88
|
}
|
|
42
89
|
function applyAddress(addr, set) {
|
package/dist/BaseMail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseMail.js","sourceRoot":"","sources":["../src/BaseMail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"BaseMail.js","sourceRoot":"","sources":["../src/BaseMail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAUvE;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAgB,QAAQ;IAC7B;;;;;;OAMG;IACM,OAAO,GAAmB,IAAI,cAAc,EAAE,CAAC;IAExD,IAAI,CAAe;IACnB,OAAO,CAAe;IACtB,OAAO,CAAU;IAEjB;;;;;;;;OAQG;IACH,KAAK,GAAG,KAAK,CAAC;IACd,0EAA0E;IAC1E,aAAa,CAAe;IAE5B;QACC,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACd,0DAA0D,CAC1D,CAAC;QACH,CAAC;IACF,CAAC;IAID;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,CAAI,MAAqB;QAClC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,oEAAoE;IACpE,KAAK,CAAC,SAAS,CAAC,MAA2B;QAC1C,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CAAC,SAAkB;QACzC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAkB;QAC7B,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC,aAAa,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CACzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAChC,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAC5C,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CACnC,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,+DAA+D;QAC/D,gEAAgE;QAChE,IAAI,CAAC,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;CACD;AAED,SAAS,YAAY,CACpB,IAAiB,EACjB,GAA6C;IAE7C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,GAAG,CAAC,IAAI,CAAC,CAAC;QACV,OAAO;IACR,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC"}
|
package/dist/Mail.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseMail } from "./BaseMail.js";
|
|
2
|
-
import { type MailAttachment, type MailMessage, MessageBuilder } from "./MessageBuilder.js";
|
|
2
|
+
import { type MailAttachment, type MailMessage, type MessageBodyTemplates, MessageBuilder } from "./MessageBuilder.js";
|
|
3
3
|
import { type BayQueueLike } from "./queue/MailJob.js";
|
|
4
4
|
import { type RetryConfig } from "./retry.js";
|
|
5
5
|
import { FakeMail } from "./testing/FakeMail.js";
|
|
@@ -32,6 +32,11 @@ export interface MailSendResult {
|
|
|
32
32
|
export type MailSendOutcome = MailSendResult | undefined;
|
|
33
33
|
export interface MailTransport {
|
|
34
34
|
send(message: MailMessage): Promise<MailSendOutcome>;
|
|
35
|
+
/**
|
|
36
|
+
* Release what this transport holds open — an SMTP connection pool, mostly.
|
|
37
|
+
* Optional: an HTTP-API transport has nothing to close.
|
|
38
|
+
*/
|
|
39
|
+
close?(): Promise<void>;
|
|
35
40
|
}
|
|
36
41
|
/**
|
|
37
42
|
* Structural interface for the event bus `Emitter` — peer-dep friendly.
|
|
@@ -41,44 +46,50 @@ export interface EmitterLike {
|
|
|
41
46
|
emit(event: string, data: unknown): void;
|
|
42
47
|
}
|
|
43
48
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
49
|
+
* What every mail lifecycle event carries, matching `@adonisjs/mail`:
|
|
50
|
+
* the mailer that handled it, the message itself, and the templates it was
|
|
51
|
+
* rendered from. A listener migrating over reads `message.to` / `views.html`.
|
|
52
|
+
*
|
|
53
|
+
* `transportName` and the flattened recipient lists are rover's own and stay:
|
|
54
|
+
* the transport name is the same string as `mailerName` under the name rover
|
|
55
|
+
* used first, and a listener that only wants the addresses should not have to
|
|
56
|
+
* reach into the message for them.
|
|
46
57
|
*/
|
|
47
|
-
export
|
|
58
|
+
export type { MessageBodyTemplates };
|
|
59
|
+
export interface MailEventBase {
|
|
60
|
+
/** AdonisJS name for the mailer that handled the message. */
|
|
61
|
+
mailerName: string;
|
|
62
|
+
/** The built message. */
|
|
63
|
+
message: MailMessage;
|
|
64
|
+
/** Templates the message was rendered from, empty when it carried none. */
|
|
65
|
+
views: MessageBodyTemplates;
|
|
48
66
|
to: string[];
|
|
49
67
|
cc: string[];
|
|
50
68
|
bcc: string[];
|
|
69
|
+
/** rover's original name for {@link MailEventBase.mailerName}. */
|
|
51
70
|
transportName: string;
|
|
52
71
|
timestamp: number;
|
|
53
72
|
}
|
|
54
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Emitted (`mail:sending`) right before the transport `send` runs — no
|
|
75
|
+
* `messageId` yet, since the provider hasn't accepted the message.
|
|
76
|
+
*/
|
|
77
|
+
export interface MailSendingEvent extends MailEventBase {
|
|
78
|
+
}
|
|
79
|
+
export interface MailSentEvent extends MailEventBase {
|
|
55
80
|
messageId: string;
|
|
56
|
-
to: string[];
|
|
57
|
-
cc: string[];
|
|
58
|
-
bcc: string[];
|
|
59
|
-
transportName: string;
|
|
60
|
-
timestamp: number;
|
|
61
81
|
}
|
|
62
82
|
/**
|
|
63
83
|
* Emitted for the queue lifecycle (`mail:queueing` / `mail:queued`). `jobId` is
|
|
64
84
|
* only present on `mail:queued` (once the job has been accepted by the queue /
|
|
65
85
|
* in-memory messenger).
|
|
66
86
|
*/
|
|
67
|
-
export interface MailQueueEvent {
|
|
68
|
-
to: string[];
|
|
69
|
-
cc: string[];
|
|
70
|
-
bcc: string[];
|
|
71
|
-
transportName: string;
|
|
87
|
+
export interface MailQueueEvent extends MailEventBase {
|
|
72
88
|
queue: string;
|
|
73
89
|
jobId?: string;
|
|
74
|
-
timestamp: number;
|
|
75
90
|
}
|
|
76
|
-
export interface MailFailedEvent {
|
|
91
|
+
export interface MailFailedEvent extends MailEventBase {
|
|
77
92
|
messageId: string;
|
|
78
|
-
to: string[];
|
|
79
|
-
cc: string[];
|
|
80
|
-
bcc: string[];
|
|
81
|
-
transportName: string;
|
|
82
93
|
error: {
|
|
83
94
|
code: string;
|
|
84
95
|
message: string;
|
|
@@ -86,12 +97,21 @@ export interface MailFailedEvent {
|
|
|
86
97
|
upstreamStatusRaw?: string;
|
|
87
98
|
attempts: number;
|
|
88
99
|
};
|
|
89
|
-
timestamp: number;
|
|
90
100
|
}
|
|
91
101
|
export interface MailConfig {
|
|
92
102
|
default: string;
|
|
93
103
|
from: string;
|
|
94
|
-
transports
|
|
104
|
+
transports?: Record<string, {
|
|
105
|
+
transport: string;
|
|
106
|
+
retry?: RetryConfig;
|
|
107
|
+
[key: string]: unknown;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* AdonisJS spelling of `transports`. Both are accepted and mean the same
|
|
111
|
+
* thing, so a migrated `config/mail.ts` runs with its imports rewritten and
|
|
112
|
+
* nothing else.
|
|
113
|
+
*/
|
|
114
|
+
mailers?: Record<string, {
|
|
95
115
|
transport: string;
|
|
96
116
|
retry?: RetryConfig;
|
|
97
117
|
[key: string]: unknown;
|
|
@@ -133,6 +153,11 @@ export declare class SmtpTransport implements MailTransport {
|
|
|
133
153
|
#private;
|
|
134
154
|
constructor(config: Record<string, unknown>);
|
|
135
155
|
send(message: MailMessage): Promise<MailSendOutcome>;
|
|
156
|
+
/**
|
|
157
|
+
* Drain nodemailer's connection pool. Idempotent — nodemailer tolerates a
|
|
158
|
+
* second close, and a shutdown path may run twice.
|
|
159
|
+
*/
|
|
160
|
+
close(): Promise<void>;
|
|
136
161
|
}
|
|
137
162
|
/**
|
|
138
163
|
* Log transport — logs emails to console (development).
|
|
@@ -159,6 +184,36 @@ export declare class Mail {
|
|
|
159
184
|
hooks?: MailHooks;
|
|
160
185
|
emitter?: EmitterLike;
|
|
161
186
|
});
|
|
187
|
+
/**
|
|
188
|
+
* Send a message that is already built (AdonisJS `sendCompiled`).
|
|
189
|
+
*
|
|
190
|
+
* What a queue worker calls: the message was composed and serialised
|
|
191
|
+
* elsewhere, so there is nothing left to render. An alias of
|
|
192
|
+
* {@link dispatchMessage}, which is the name ream used first.
|
|
193
|
+
*/
|
|
194
|
+
sendCompiled(message: MailMessage, transport?: string): Promise<void>;
|
|
195
|
+
/**
|
|
196
|
+
* Queue a message that is already built (AdonisJS `sendLaterCompiled`).
|
|
197
|
+
*
|
|
198
|
+
* Returns the job id, like {@link sendLater}.
|
|
199
|
+
*/
|
|
200
|
+
sendLaterCompiled(message: MailMessage, options?: {
|
|
201
|
+
transport?: string;
|
|
202
|
+
queue?: string;
|
|
203
|
+
}): Promise<string>;
|
|
204
|
+
/**
|
|
205
|
+
* Hand `sendLater()` a queue after construction (AdonisJS `setMessenger`).
|
|
206
|
+
*
|
|
207
|
+
* The constructor takes one too, but a queue is often resolved later than
|
|
208
|
+
* the mailer — a provider that boots after this one, a test that swaps it.
|
|
209
|
+
* Without this the only way in was the constructor, so an app migrating
|
|
210
|
+
* from `mail.setMessenger(queue)` stopped at a TypeError.
|
|
211
|
+
*
|
|
212
|
+
* "Messenger" is upstream's word for what rover's config calls `queue`;
|
|
213
|
+
* they are the same thing, and the method keeps the upstream name so a
|
|
214
|
+
* migrated call site resolves.
|
|
215
|
+
*/
|
|
216
|
+
setMessenger(messenger: BayQueueLike): this;
|
|
162
217
|
/** Send an email using the fluent message builder. */
|
|
163
218
|
send(callback: (message: MessageBuilder) => void, transport?: string): Promise<void>;
|
|
164
219
|
/** Send an email using a class-based `BaseMail` instance. */
|
|
@@ -181,7 +236,7 @@ export declare class Mail {
|
|
|
181
236
|
* `overrideRetry` (optional) wins over config-level retry; `MailJobHandler` passes
|
|
182
237
|
* `{ maxAttempts: 1 }` so queue-level and sync-level retries don't compound.
|
|
183
238
|
*/
|
|
184
|
-
dispatchMessage(message: MailMessage, transportName?: string, overrideRetry?: RetryConfig): Promise<void>;
|
|
239
|
+
dispatchMessage(message: MailMessage, transportName?: string, overrideRetry?: RetryConfig, views?: MessageBodyTemplates): Promise<void>;
|
|
185
240
|
/**
|
|
186
241
|
* Get a `Mailer` bound to a named transport, so `mail.use('mailgun').send(cb)`
|
|
187
242
|
* routes through that transport (Adonis parity). Mailers are cached per name.
|
|
@@ -189,6 +244,17 @@ export declare class Mail {
|
|
|
189
244
|
use(name: string): Mailer;
|
|
190
245
|
/** @internal Resolve the raw transport instance behind a mailer name. */
|
|
191
246
|
transportFor(name: string): MailTransport;
|
|
247
|
+
/**
|
|
248
|
+
* Close one transport's open connections (Adonis `close`).
|
|
249
|
+
*
|
|
250
|
+
* An SMTP pool keeps sockets alive between sends; a process that exits
|
|
251
|
+
* without closing them leaves the server holding connections until it times
|
|
252
|
+
* them out. Unknown or already-closed names are a no-op — shutdown is not
|
|
253
|
+
* the place to throw.
|
|
254
|
+
*/
|
|
255
|
+
close(name?: string): Promise<void>;
|
|
256
|
+
/** Close every built transport (Adonis `closeAll`). What a shutdown hook calls. */
|
|
257
|
+
closeAll(): Promise<void>;
|
|
192
258
|
/**
|
|
193
259
|
* Enter fake mode. Every subsequent `send()` / `sendLater()` — including via
|
|
194
260
|
* `use(name)` — is captured by the returned `FakeMail` instead of hitting a
|
package/dist/Mail.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mail.d.ts","sourceRoot":"","sources":["../src/Mail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,cAAc,EACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,KAAK,YAAY,EAGjB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAMjD,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,cAAc;IAC9B,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC7B,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Mail.d.ts","sourceRoot":"","sources":["../src/Mail.ts"],"names":[],"mappings":"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"}
|
package/dist/Mail.js
CHANGED
|
@@ -79,6 +79,10 @@ export class SmtpTransport {
|
|
|
79
79
|
subject: message.subject,
|
|
80
80
|
html: message.html,
|
|
81
81
|
text: message.text,
|
|
82
|
+
// nodemailer reads `watchHtml`; there is no bare `watch` field in
|
|
83
|
+
// its mail composer, so writing one — as AdonisJS does — never
|
|
84
|
+
// reaches the wire.
|
|
85
|
+
watchHtml: message.watchHtml,
|
|
82
86
|
priority: message.priority,
|
|
83
87
|
messageId: message.messageId,
|
|
84
88
|
inReplyTo: message.inReplyTo,
|
|
@@ -86,12 +90,20 @@ export class SmtpTransport {
|
|
|
86
90
|
headers: Object.keys(message.headers).length
|
|
87
91
|
? message.headers
|
|
88
92
|
: undefined,
|
|
93
|
+
encoding: message.encoding,
|
|
94
|
+
// `list` is NOT passed: `build()` already rendered the `List-*`
|
|
95
|
+
// headers into `headers`, so handing nodemailer the structured form
|
|
96
|
+
// too would emit each of them twice.
|
|
97
|
+
icalEvent: message.icalEvent,
|
|
89
98
|
attachments: message.attachments.length
|
|
90
99
|
? message.attachments.map((att) => ({
|
|
91
100
|
filename: att.filename,
|
|
92
101
|
content: att.content,
|
|
93
102
|
contentType: att.contentType,
|
|
94
103
|
cid: att.cid,
|
|
104
|
+
contentDisposition: att.contentDisposition,
|
|
105
|
+
encoding: att.encoding,
|
|
106
|
+
headers: att.headers,
|
|
95
107
|
}))
|
|
96
108
|
: undefined,
|
|
97
109
|
});
|
|
@@ -103,6 +115,13 @@ export class SmtpTransport {
|
|
|
103
115
|
throw wrapSmtpError(err);
|
|
104
116
|
}
|
|
105
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Drain nodemailer's connection pool. Idempotent — nodemailer tolerates a
|
|
120
|
+
* second close, and a shutdown path may run twice.
|
|
121
|
+
*/
|
|
122
|
+
async close() {
|
|
123
|
+
this.#transporter.close();
|
|
124
|
+
}
|
|
106
125
|
}
|
|
107
126
|
/**
|
|
108
127
|
* Normalise nodemailer / socket errors into the uniform `MAIL_PROVIDER_ERROR`
|
|
@@ -192,7 +211,10 @@ export class Mail {
|
|
|
192
211
|
if (config.viewsRoot !== undefined) {
|
|
193
212
|
setViewsRoot(config.viewsRoot);
|
|
194
213
|
}
|
|
195
|
-
|
|
214
|
+
// `mailers` (AdonisJS) and `transports` (rover) are the same map under two
|
|
215
|
+
// names; a config that sets both gets both, last name wins per key.
|
|
216
|
+
const declared = { ...config.transports, ...config.mailers };
|
|
217
|
+
for (const [name, transportConfig] of Object.entries(declared)) {
|
|
196
218
|
const factory = transportFactories[transportConfig.transport];
|
|
197
219
|
if (!factory) {
|
|
198
220
|
throw new RoverError("MAIL_UNKNOWN_TRANSPORT", `Unknown mail transport type '${transportConfig.transport}' (configured under name '${name}')`, {
|
|
@@ -205,25 +227,55 @@ export class Mail {
|
|
|
205
227
|
}
|
|
206
228
|
}
|
|
207
229
|
if (options?.queue) {
|
|
208
|
-
this
|
|
209
|
-
this.#queue.register(this.#queueName, new MailJobHandler(this));
|
|
230
|
+
this.setMessenger(options.queue);
|
|
210
231
|
}
|
|
211
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Send a message that is already built (AdonisJS `sendCompiled`).
|
|
235
|
+
*
|
|
236
|
+
* What a queue worker calls: the message was composed and serialised
|
|
237
|
+
* elsewhere, so there is nothing left to render. An alias of
|
|
238
|
+
* {@link dispatchMessage}, which is the name ream used first.
|
|
239
|
+
*/
|
|
240
|
+
async sendCompiled(message, transport) {
|
|
241
|
+
await this.dispatchMessage(message, transport);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Queue a message that is already built (AdonisJS `sendLaterCompiled`).
|
|
245
|
+
*
|
|
246
|
+
* Returns the job id, like {@link sendLater}.
|
|
247
|
+
*/
|
|
248
|
+
async sendLaterCompiled(message, options) {
|
|
249
|
+
// No views: a compiled message was rendered elsewhere, and the templates
|
|
250
|
+
// that produced it did not travel with it.
|
|
251
|
+
return this.#enqueue({ message, views: {} }, options);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Hand `sendLater()` a queue after construction (AdonisJS `setMessenger`).
|
|
255
|
+
*
|
|
256
|
+
* The constructor takes one too, but a queue is often resolved later than
|
|
257
|
+
* the mailer — a provider that boots after this one, a test that swaps it.
|
|
258
|
+
* Without this the only way in was the constructor, so an app migrating
|
|
259
|
+
* from `mail.setMessenger(queue)` stopped at a TypeError.
|
|
260
|
+
*
|
|
261
|
+
* "Messenger" is upstream's word for what rover's config calls `queue`;
|
|
262
|
+
* they are the same thing, and the method keeps the upstream name so a
|
|
263
|
+
* migrated call site resolves.
|
|
264
|
+
*/
|
|
265
|
+
setMessenger(messenger) {
|
|
266
|
+
this.#queue = messenger;
|
|
267
|
+
this.#queue.register(this.#queueName, new MailJobHandler(this));
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
212
270
|
async send(arg, transport) {
|
|
213
271
|
const transportName = transport ?? this.#defaultTransport;
|
|
214
272
|
// Fake mode is manager-level: capture the built message (and the source
|
|
215
273
|
// BaseMail, for constructor-based assertions) instead of touching a
|
|
216
274
|
// transport. Still fire the send lifecycle so event wiring stays testable.
|
|
217
275
|
if (this.#fake !== null) {
|
|
218
|
-
const message = await this.#
|
|
276
|
+
const { message, views } = await this.#buildMessageWithViews(arg);
|
|
219
277
|
this.#fake.trackSent(message, arg instanceof BaseMail ? arg : undefined);
|
|
220
|
-
const base =
|
|
221
|
-
to: message.to.slice(),
|
|
222
|
-
cc: message.cc.slice(),
|
|
223
|
-
bcc: message.bcc.slice(),
|
|
224
|
-
transportName,
|
|
225
|
-
timestamp: Date.now(),
|
|
226
|
-
};
|
|
278
|
+
const base = this.#eventBase(message, views, transportName);
|
|
227
279
|
this.#fireSending(base);
|
|
228
280
|
this.#fireSent({ ...base, messageId: randomBytes(16).toString("hex") });
|
|
229
281
|
return;
|
|
@@ -232,8 +284,8 @@ export class Mail {
|
|
|
232
284
|
if (!this.#transports.has(transportName)) {
|
|
233
285
|
throw new Error(`Mail transport '${transportName}' not configured`);
|
|
234
286
|
}
|
|
235
|
-
const message = await this.#
|
|
236
|
-
await this.dispatchMessage(message, transportName);
|
|
287
|
+
const { message, views } = await this.#buildMessageWithViews(arg);
|
|
288
|
+
await this.dispatchMessage(message, transportName, undefined, views);
|
|
237
289
|
}
|
|
238
290
|
/**
|
|
239
291
|
* Enqueue a send. Returns the job id. When a `@c9up/bay` `QueueManager` was
|
|
@@ -242,20 +294,21 @@ export class Mail {
|
|
|
242
294
|
* `MemoryQueueMessenger` — `sendLater()` never throws for a missing queue.
|
|
243
295
|
*/
|
|
244
296
|
async sendLater(arg, options) {
|
|
245
|
-
const
|
|
297
|
+
const built = await this.#buildMessageWithViews(arg);
|
|
298
|
+
return this.#enqueue({ ...built, source: arg instanceof BaseMail ? arg : undefined }, options);
|
|
299
|
+
}
|
|
300
|
+
/** The queueing half, shared with {@link sendLaterCompiled}. */
|
|
301
|
+
async #enqueue(built, options) {
|
|
302
|
+
const { message, views } = built;
|
|
246
303
|
const queueName = options?.queue ?? this.#queueName;
|
|
247
304
|
const transportName = options?.transport ?? this.#defaultTransport;
|
|
248
305
|
const base = {
|
|
249
|
-
|
|
250
|
-
cc: message.cc.slice(),
|
|
251
|
-
bcc: message.bcc.slice(),
|
|
252
|
-
transportName,
|
|
306
|
+
...this.#eventBase(message, views, transportName),
|
|
253
307
|
queue: queueName,
|
|
254
|
-
timestamp: Date.now(),
|
|
255
308
|
};
|
|
256
309
|
// Fake mode: capture into the queued bucket, don't dispatch.
|
|
257
310
|
if (this.#fake !== null) {
|
|
258
|
-
this.#fake.trackQueued(message,
|
|
311
|
+
this.#fake.trackQueued(message, built.source);
|
|
259
312
|
const jobId = `fake_${randomBytes(12).toString("hex")}`;
|
|
260
313
|
this.#fireQueueing(base);
|
|
261
314
|
this.#fireQueued({ ...base, jobId });
|
|
@@ -276,7 +329,12 @@ export class Mail {
|
|
|
276
329
|
* `overrideRetry` (optional) wins over config-level retry; `MailJobHandler` passes
|
|
277
330
|
* `{ maxAttempts: 1 }` so queue-level and sync-level retries don't compound.
|
|
278
331
|
*/
|
|
279
|
-
async dispatchMessage(message, transportName, overrideRetry
|
|
332
|
+
async dispatchMessage(message, transportName, overrideRetry,
|
|
333
|
+
// The templates the message came from, when the caller still knows them.
|
|
334
|
+
// A message revived from a queue payload does not: the rendered bodies
|
|
335
|
+
// were serialised, the templates that produced them were not. Empty is
|
|
336
|
+
// the honest answer there rather than a guess.
|
|
337
|
+
views = {}) {
|
|
280
338
|
// Defense-in-depth: queue payloads bypass `#buildMessage`, so a
|
|
281
339
|
// malformed message deserialised from storage would otherwise reach
|
|
282
340
|
// the transport with the cryptic provider error A2 was meant to
|
|
@@ -294,13 +352,7 @@ export class Mail {
|
|
|
294
352
|
let lastError;
|
|
295
353
|
// Fire once before the first attempt — `mail:sending` signals intent, not
|
|
296
354
|
// per-retry, matching @adonisjs/mail.
|
|
297
|
-
this.#fireSending(
|
|
298
|
-
to: message.to.slice(),
|
|
299
|
-
cc: message.cc.slice(),
|
|
300
|
-
bcc: message.bcc.slice(),
|
|
301
|
-
transportName: name,
|
|
302
|
-
timestamp: Date.now(),
|
|
303
|
-
});
|
|
355
|
+
this.#fireSending(this.#eventBase(message, views, name));
|
|
304
356
|
for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
|
|
305
357
|
let sendResult;
|
|
306
358
|
try {
|
|
@@ -312,13 +364,9 @@ export class Mail {
|
|
|
312
364
|
if (!retryable || attempt === retry.maxAttempts) {
|
|
313
365
|
const annotated = this.#withAttempts(err, attempt);
|
|
314
366
|
this.#fireFailed({
|
|
367
|
+
...this.#eventBase(message, views, name),
|
|
315
368
|
messageId: generatedId,
|
|
316
|
-
to: message.to.slice(),
|
|
317
|
-
cc: message.cc.slice(),
|
|
318
|
-
bcc: message.bcc.slice(),
|
|
319
|
-
transportName: name,
|
|
320
369
|
error: errorDescriptor(annotated, attempt),
|
|
321
|
-
timestamp: Date.now(),
|
|
322
370
|
});
|
|
323
371
|
throw annotated;
|
|
324
372
|
}
|
|
@@ -338,12 +386,8 @@ export class Mail {
|
|
|
338
386
|
// internally generated correlation id.
|
|
339
387
|
const providerId = providerIdRaw && providerIdRaw.length > 0 ? providerIdRaw : undefined;
|
|
340
388
|
this.#fireSent({
|
|
389
|
+
...this.#eventBase(message, views, name),
|
|
341
390
|
messageId: providerId ?? generatedId,
|
|
342
|
-
to: message.to.slice(),
|
|
343
|
-
cc: message.cc.slice(),
|
|
344
|
-
bcc: message.bcc.slice(),
|
|
345
|
-
transportName: name,
|
|
346
|
-
timestamp: Date.now(),
|
|
347
391
|
});
|
|
348
392
|
return;
|
|
349
393
|
}
|
|
@@ -406,20 +450,43 @@ export class Mail {
|
|
|
406
450
|
}
|
|
407
451
|
}
|
|
408
452
|
}
|
|
409
|
-
|
|
453
|
+
/**
|
|
454
|
+
* Build the message AND report which templates it was rendered from, since
|
|
455
|
+
* every lifecycle event carries both (AdonisJS `message` + `views`).
|
|
456
|
+
*/
|
|
457
|
+
async #buildMessageWithViews(arg) {
|
|
410
458
|
let result;
|
|
459
|
+
let views;
|
|
411
460
|
if (arg instanceof BaseMail) {
|
|
412
461
|
const built = await arg.build(this.#viewsRoot);
|
|
413
462
|
result = built.from ? built : { ...built, from: this.#defaultFrom };
|
|
463
|
+
views = arg.message.views;
|
|
414
464
|
}
|
|
415
465
|
else {
|
|
416
466
|
const builder = new MessageBuilder();
|
|
417
467
|
builder.from(this.#defaultFrom);
|
|
418
468
|
arg(builder);
|
|
419
469
|
result = await builder.build(this.#viewsRoot);
|
|
470
|
+
views = builder.views;
|
|
420
471
|
}
|
|
421
472
|
validateMailMessage(result);
|
|
422
|
-
return result;
|
|
473
|
+
return { message: result, views };
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* The fields every lifecycle event shares. One place, so `mail:sending` and
|
|
477
|
+
* `mail:sent` cannot describe the same message differently.
|
|
478
|
+
*/
|
|
479
|
+
#eventBase(message, views, mailerName) {
|
|
480
|
+
return {
|
|
481
|
+
mailerName,
|
|
482
|
+
message,
|
|
483
|
+
views,
|
|
484
|
+
to: message.to.slice(),
|
|
485
|
+
cc: message.cc.slice(),
|
|
486
|
+
bcc: message.bcc.slice(),
|
|
487
|
+
transportName: mailerName,
|
|
488
|
+
timestamp: Date.now(),
|
|
489
|
+
};
|
|
423
490
|
}
|
|
424
491
|
/**
|
|
425
492
|
* Get a `Mailer` bound to a named transport, so `mail.use('mailgun').send(cb)`
|
|
@@ -443,6 +510,22 @@ export class Mail {
|
|
|
443
510
|
throw new Error(`Mail transport '${name}' not configured`);
|
|
444
511
|
return t;
|
|
445
512
|
}
|
|
513
|
+
/**
|
|
514
|
+
* Close one transport's open connections (Adonis `close`).
|
|
515
|
+
*
|
|
516
|
+
* An SMTP pool keeps sockets alive between sends; a process that exits
|
|
517
|
+
* without closing them leaves the server holding connections until it times
|
|
518
|
+
* them out. Unknown or already-closed names are a no-op — shutdown is not
|
|
519
|
+
* the place to throw.
|
|
520
|
+
*/
|
|
521
|
+
async close(name) {
|
|
522
|
+
const transportName = name ?? this.#defaultTransport;
|
|
523
|
+
await this.#transports.get(transportName)?.close?.();
|
|
524
|
+
}
|
|
525
|
+
/** Close every built transport (Adonis `closeAll`). What a shutdown hook calls. */
|
|
526
|
+
async closeAll() {
|
|
527
|
+
await Promise.all([...this.#transports.values()].map((transport) => transport.close?.()));
|
|
528
|
+
}
|
|
446
529
|
/**
|
|
447
530
|
* Enter fake mode. Every subsequent `send()` / `sendLater()` — including via
|
|
448
531
|
* `use(name)` — is captured by the returned `FakeMail` instead of hitting a
|