@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/src/Mail.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { BaseMail } from "./BaseMail.js";
|
|
|
4
4
|
import {
|
|
5
5
|
type MailAttachment,
|
|
6
6
|
type MailMessage,
|
|
7
|
+
type MessageBodyTemplates,
|
|
7
8
|
MessageBuilder,
|
|
8
9
|
} from "./MessageBuilder.js";
|
|
9
10
|
import {
|
|
@@ -59,6 +60,11 @@ export type MailSendOutcome = MailSendResult | undefined;
|
|
|
59
60
|
|
|
60
61
|
export interface MailTransport {
|
|
61
62
|
send(message: MailMessage): Promise<MailSendOutcome>;
|
|
63
|
+
/**
|
|
64
|
+
* Release what this transport holds open — an SMTP connection pool, mostly.
|
|
65
|
+
* Optional: an HTTP-API transport has nothing to close.
|
|
66
|
+
*/
|
|
67
|
+
close?(): Promise<void>;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -70,24 +76,40 @@ export interface EmitterLike {
|
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
79
|
+
* What every mail lifecycle event carries, matching `@adonisjs/mail`:
|
|
80
|
+
* the mailer that handled it, the message itself, and the templates it was
|
|
81
|
+
* rendered from. A listener migrating over reads `message.to` / `views.html`.
|
|
82
|
+
*
|
|
83
|
+
* `transportName` and the flattened recipient lists are rover's own and stay:
|
|
84
|
+
* the transport name is the same string as `mailerName` under the name rover
|
|
85
|
+
* used first, and a listener that only wants the addresses should not have to
|
|
86
|
+
* reach into the message for them.
|
|
75
87
|
*/
|
|
76
|
-
export
|
|
88
|
+
export type { MessageBodyTemplates };
|
|
89
|
+
|
|
90
|
+
export interface MailEventBase {
|
|
91
|
+
/** AdonisJS name for the mailer that handled the message. */
|
|
92
|
+
mailerName: string;
|
|
93
|
+
/** The built message. */
|
|
94
|
+
message: MailMessage;
|
|
95
|
+
/** Templates the message was rendered from, empty when it carried none. */
|
|
96
|
+
views: MessageBodyTemplates;
|
|
77
97
|
to: string[];
|
|
78
98
|
cc: string[];
|
|
79
99
|
bcc: string[];
|
|
100
|
+
/** rover's original name for {@link MailEventBase.mailerName}. */
|
|
80
101
|
transportName: string;
|
|
81
102
|
timestamp: number;
|
|
82
103
|
}
|
|
83
104
|
|
|
84
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Emitted (`mail:sending`) right before the transport `send` runs — no
|
|
107
|
+
* `messageId` yet, since the provider hasn't accepted the message.
|
|
108
|
+
*/
|
|
109
|
+
export interface MailSendingEvent extends MailEventBase {}
|
|
110
|
+
|
|
111
|
+
export interface MailSentEvent extends MailEventBase {
|
|
85
112
|
messageId: string;
|
|
86
|
-
to: string[];
|
|
87
|
-
cc: string[];
|
|
88
|
-
bcc: string[];
|
|
89
|
-
transportName: string;
|
|
90
|
-
timestamp: number;
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
/**
|
|
@@ -95,22 +117,13 @@ export interface MailSentEvent {
|
|
|
95
117
|
* only present on `mail:queued` (once the job has been accepted by the queue /
|
|
96
118
|
* in-memory messenger).
|
|
97
119
|
*/
|
|
98
|
-
export interface MailQueueEvent {
|
|
99
|
-
to: string[];
|
|
100
|
-
cc: string[];
|
|
101
|
-
bcc: string[];
|
|
102
|
-
transportName: string;
|
|
120
|
+
export interface MailQueueEvent extends MailEventBase {
|
|
103
121
|
queue: string;
|
|
104
122
|
jobId?: string;
|
|
105
|
-
timestamp: number;
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
export interface MailFailedEvent {
|
|
125
|
+
export interface MailFailedEvent extends MailEventBase {
|
|
109
126
|
messageId: string;
|
|
110
|
-
to: string[];
|
|
111
|
-
cc: string[];
|
|
112
|
-
bcc: string[];
|
|
113
|
-
transportName: string;
|
|
114
127
|
error: {
|
|
115
128
|
code: string;
|
|
116
129
|
message: string;
|
|
@@ -118,13 +131,21 @@ export interface MailFailedEvent {
|
|
|
118
131
|
upstreamStatusRaw?: string;
|
|
119
132
|
attempts: number;
|
|
120
133
|
};
|
|
121
|
-
timestamp: number;
|
|
122
134
|
}
|
|
123
135
|
|
|
124
136
|
export interface MailConfig {
|
|
125
137
|
default: string;
|
|
126
138
|
from: string;
|
|
127
|
-
transports
|
|
139
|
+
transports?: Record<
|
|
140
|
+
string,
|
|
141
|
+
{ transport: string; retry?: RetryConfig; [key: string]: unknown }
|
|
142
|
+
>;
|
|
143
|
+
/**
|
|
144
|
+
* AdonisJS spelling of `transports`. Both are accepted and mean the same
|
|
145
|
+
* thing, so a migrated `config/mail.ts` runs with its imports rewritten and
|
|
146
|
+
* nothing else.
|
|
147
|
+
*/
|
|
148
|
+
mailers?: Record<
|
|
128
149
|
string,
|
|
129
150
|
{ transport: string; retry?: RetryConfig; [key: string]: unknown }
|
|
130
151
|
>;
|
|
@@ -236,6 +257,10 @@ export class SmtpTransport implements MailTransport {
|
|
|
236
257
|
subject: message.subject,
|
|
237
258
|
html: message.html,
|
|
238
259
|
text: message.text,
|
|
260
|
+
// nodemailer reads `watchHtml`; there is no bare `watch` field in
|
|
261
|
+
// its mail composer, so writing one — as AdonisJS does — never
|
|
262
|
+
// reaches the wire.
|
|
263
|
+
watchHtml: message.watchHtml,
|
|
239
264
|
priority: message.priority,
|
|
240
265
|
messageId: message.messageId,
|
|
241
266
|
inReplyTo: message.inReplyTo,
|
|
@@ -243,12 +268,20 @@ export class SmtpTransport implements MailTransport {
|
|
|
243
268
|
headers: Object.keys(message.headers).length
|
|
244
269
|
? message.headers
|
|
245
270
|
: undefined,
|
|
271
|
+
encoding: message.encoding,
|
|
272
|
+
// `list` is NOT passed: `build()` already rendered the `List-*`
|
|
273
|
+
// headers into `headers`, so handing nodemailer the structured form
|
|
274
|
+
// too would emit each of them twice.
|
|
275
|
+
icalEvent: message.icalEvent,
|
|
246
276
|
attachments: message.attachments.length
|
|
247
277
|
? message.attachments.map((att) => ({
|
|
248
278
|
filename: att.filename,
|
|
249
279
|
content: att.content,
|
|
250
280
|
contentType: att.contentType,
|
|
251
281
|
cid: att.cid,
|
|
282
|
+
contentDisposition: att.contentDisposition,
|
|
283
|
+
encoding: att.encoding,
|
|
284
|
+
headers: att.headers,
|
|
252
285
|
}))
|
|
253
286
|
: undefined,
|
|
254
287
|
});
|
|
@@ -258,6 +291,14 @@ export class SmtpTransport implements MailTransport {
|
|
|
258
291
|
throw wrapSmtpError(err);
|
|
259
292
|
}
|
|
260
293
|
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Drain nodemailer's connection pool. Idempotent — nodemailer tolerates a
|
|
297
|
+
* second close, and a shutdown path may run twice.
|
|
298
|
+
*/
|
|
299
|
+
async close(): Promise<void> {
|
|
300
|
+
this.#transporter.close();
|
|
301
|
+
}
|
|
261
302
|
}
|
|
262
303
|
|
|
263
304
|
/**
|
|
@@ -377,7 +418,10 @@ export class Mail {
|
|
|
377
418
|
setViewsRoot(config.viewsRoot);
|
|
378
419
|
}
|
|
379
420
|
|
|
380
|
-
|
|
421
|
+
// `mailers` (AdonisJS) and `transports` (rover) are the same map under two
|
|
422
|
+
// names; a config that sets both gets both, last name wins per key.
|
|
423
|
+
const declared = { ...config.transports, ...config.mailers };
|
|
424
|
+
for (const [name, transportConfig] of Object.entries(declared)) {
|
|
381
425
|
const factory = transportFactories[transportConfig.transport];
|
|
382
426
|
if (!factory) {
|
|
383
427
|
throw new RoverError(
|
|
@@ -395,11 +439,53 @@ export class Mail {
|
|
|
395
439
|
}
|
|
396
440
|
|
|
397
441
|
if (options?.queue) {
|
|
398
|
-
this
|
|
399
|
-
this.#queue.register(this.#queueName, new MailJobHandler(this));
|
|
442
|
+
this.setMessenger(options.queue);
|
|
400
443
|
}
|
|
401
444
|
}
|
|
402
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Send a message that is already built (AdonisJS `sendCompiled`).
|
|
448
|
+
*
|
|
449
|
+
* What a queue worker calls: the message was composed and serialised
|
|
450
|
+
* elsewhere, so there is nothing left to render. An alias of
|
|
451
|
+
* {@link dispatchMessage}, which is the name ream used first.
|
|
452
|
+
*/
|
|
453
|
+
async sendCompiled(message: MailMessage, transport?: string): Promise<void> {
|
|
454
|
+
await this.dispatchMessage(message, transport);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Queue a message that is already built (AdonisJS `sendLaterCompiled`).
|
|
459
|
+
*
|
|
460
|
+
* Returns the job id, like {@link sendLater}.
|
|
461
|
+
*/
|
|
462
|
+
async sendLaterCompiled(
|
|
463
|
+
message: MailMessage,
|
|
464
|
+
options?: { transport?: string; queue?: string },
|
|
465
|
+
): Promise<string> {
|
|
466
|
+
// No views: a compiled message was rendered elsewhere, and the templates
|
|
467
|
+
// that produced it did not travel with it.
|
|
468
|
+
return this.#enqueue({ message, views: {} }, options);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Hand `sendLater()` a queue after construction (AdonisJS `setMessenger`).
|
|
473
|
+
*
|
|
474
|
+
* The constructor takes one too, but a queue is often resolved later than
|
|
475
|
+
* the mailer — a provider that boots after this one, a test that swaps it.
|
|
476
|
+
* Without this the only way in was the constructor, so an app migrating
|
|
477
|
+
* from `mail.setMessenger(queue)` stopped at a TypeError.
|
|
478
|
+
*
|
|
479
|
+
* "Messenger" is upstream's word for what rover's config calls `queue`;
|
|
480
|
+
* they are the same thing, and the method keeps the upstream name so a
|
|
481
|
+
* migrated call site resolves.
|
|
482
|
+
*/
|
|
483
|
+
setMessenger(messenger: BayQueueLike): this {
|
|
484
|
+
this.#queue = messenger;
|
|
485
|
+
this.#queue.register(this.#queueName, new MailJobHandler(this));
|
|
486
|
+
return this;
|
|
487
|
+
}
|
|
488
|
+
|
|
403
489
|
/** Send an email using the fluent message builder. */
|
|
404
490
|
async send(
|
|
405
491
|
callback: (message: MessageBuilder) => void,
|
|
@@ -416,15 +502,9 @@ export class Mail {
|
|
|
416
502
|
// BaseMail, for constructor-based assertions) instead of touching a
|
|
417
503
|
// transport. Still fire the send lifecycle so event wiring stays testable.
|
|
418
504
|
if (this.#fake !== null) {
|
|
419
|
-
const message = await this.#
|
|
505
|
+
const { message, views } = await this.#buildMessageWithViews(arg);
|
|
420
506
|
this.#fake.trackSent(message, arg instanceof BaseMail ? arg : undefined);
|
|
421
|
-
const base =
|
|
422
|
-
to: message.to.slice(),
|
|
423
|
-
cc: message.cc.slice(),
|
|
424
|
-
bcc: message.bcc.slice(),
|
|
425
|
-
transportName,
|
|
426
|
-
timestamp: Date.now(),
|
|
427
|
-
};
|
|
507
|
+
const base = this.#eventBase(message, views, transportName);
|
|
428
508
|
this.#fireSending(base);
|
|
429
509
|
this.#fireSent({ ...base, messageId: randomBytes(16).toString("hex") });
|
|
430
510
|
return;
|
|
@@ -434,8 +514,8 @@ export class Mail {
|
|
|
434
514
|
throw new Error(`Mail transport '${transportName}' not configured`);
|
|
435
515
|
}
|
|
436
516
|
|
|
437
|
-
const message = await this.#
|
|
438
|
-
await this.dispatchMessage(message, transportName);
|
|
517
|
+
const { message, views } = await this.#buildMessageWithViews(arg);
|
|
518
|
+
await this.dispatchMessage(message, transportName, undefined, views);
|
|
439
519
|
}
|
|
440
520
|
|
|
441
521
|
/**
|
|
@@ -448,24 +528,34 @@ export class Mail {
|
|
|
448
528
|
arg: ((message: MessageBuilder) => void) | BaseMail,
|
|
449
529
|
options?: { transport?: string; queue?: string },
|
|
450
530
|
): Promise<string> {
|
|
451
|
-
const
|
|
531
|
+
const built = await this.#buildMessageWithViews(arg);
|
|
532
|
+
return this.#enqueue(
|
|
533
|
+
{ ...built, source: arg instanceof BaseMail ? arg : undefined },
|
|
534
|
+
options,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** The queueing half, shared with {@link sendLaterCompiled}. */
|
|
539
|
+
async #enqueue(
|
|
540
|
+
built: {
|
|
541
|
+
message: MailMessage;
|
|
542
|
+
views: MessageBodyTemplates;
|
|
543
|
+
/** The BaseMail it came from, for `assertQueued(WelcomeMail)`. */
|
|
544
|
+
source?: BaseMail;
|
|
545
|
+
},
|
|
546
|
+
options?: { transport?: string; queue?: string },
|
|
547
|
+
): Promise<string> {
|
|
548
|
+
const { message, views } = built;
|
|
452
549
|
const queueName = options?.queue ?? this.#queueName;
|
|
453
550
|
const transportName = options?.transport ?? this.#defaultTransport;
|
|
454
551
|
const base: MailQueueEvent = {
|
|
455
|
-
|
|
456
|
-
cc: message.cc.slice(),
|
|
457
|
-
bcc: message.bcc.slice(),
|
|
458
|
-
transportName,
|
|
552
|
+
...this.#eventBase(message, views, transportName),
|
|
459
553
|
queue: queueName,
|
|
460
|
-
timestamp: Date.now(),
|
|
461
554
|
};
|
|
462
555
|
|
|
463
556
|
// Fake mode: capture into the queued bucket, don't dispatch.
|
|
464
557
|
if (this.#fake !== null) {
|
|
465
|
-
this.#fake.trackQueued(
|
|
466
|
-
message,
|
|
467
|
-
arg instanceof BaseMail ? arg : undefined,
|
|
468
|
-
);
|
|
558
|
+
this.#fake.trackQueued(message, built.source);
|
|
469
559
|
const jobId = `fake_${randomBytes(12).toString("hex")}`;
|
|
470
560
|
this.#fireQueueing(base);
|
|
471
561
|
this.#fireQueued({ ...base, jobId });
|
|
@@ -497,6 +587,11 @@ export class Mail {
|
|
|
497
587
|
message: MailMessage,
|
|
498
588
|
transportName?: string,
|
|
499
589
|
overrideRetry?: RetryConfig,
|
|
590
|
+
// The templates the message came from, when the caller still knows them.
|
|
591
|
+
// A message revived from a queue payload does not: the rendered bodies
|
|
592
|
+
// were serialised, the templates that produced them were not. Empty is
|
|
593
|
+
// the honest answer there rather than a guess.
|
|
594
|
+
views: MessageBodyTemplates = {},
|
|
500
595
|
): Promise<void> {
|
|
501
596
|
// Defense-in-depth: queue payloads bypass `#buildMessage`, so a
|
|
502
597
|
// malformed message deserialised from storage would otherwise reach
|
|
@@ -517,13 +612,7 @@ export class Mail {
|
|
|
517
612
|
|
|
518
613
|
// Fire once before the first attempt — `mail:sending` signals intent, not
|
|
519
614
|
// per-retry, matching @adonisjs/mail.
|
|
520
|
-
this.#fireSending(
|
|
521
|
-
to: message.to.slice(),
|
|
522
|
-
cc: message.cc.slice(),
|
|
523
|
-
bcc: message.bcc.slice(),
|
|
524
|
-
transportName: name,
|
|
525
|
-
timestamp: Date.now(),
|
|
526
|
-
});
|
|
615
|
+
this.#fireSending(this.#eventBase(message, views, name));
|
|
527
616
|
|
|
528
617
|
for (let attempt = 1; attempt <= retry.maxAttempts; attempt += 1) {
|
|
529
618
|
let sendResult: MailSendOutcome;
|
|
@@ -535,13 +624,9 @@ export class Mail {
|
|
|
535
624
|
if (!retryable || attempt === retry.maxAttempts) {
|
|
536
625
|
const annotated = this.#withAttempts(err, attempt);
|
|
537
626
|
this.#fireFailed({
|
|
627
|
+
...this.#eventBase(message, views, name),
|
|
538
628
|
messageId: generatedId,
|
|
539
|
-
to: message.to.slice(),
|
|
540
|
-
cc: message.cc.slice(),
|
|
541
|
-
bcc: message.bcc.slice(),
|
|
542
|
-
transportName: name,
|
|
543
629
|
error: errorDescriptor(annotated, attempt),
|
|
544
|
-
timestamp: Date.now(),
|
|
545
630
|
});
|
|
546
631
|
throw annotated;
|
|
547
632
|
}
|
|
@@ -563,12 +648,8 @@ export class Mail {
|
|
|
563
648
|
const providerId =
|
|
564
649
|
providerIdRaw && providerIdRaw.length > 0 ? providerIdRaw : undefined;
|
|
565
650
|
this.#fireSent({
|
|
651
|
+
...this.#eventBase(message, views, name),
|
|
566
652
|
messageId: providerId ?? generatedId,
|
|
567
|
-
to: message.to.slice(),
|
|
568
|
-
cc: message.cc.slice(),
|
|
569
|
-
bcc: message.bcc.slice(),
|
|
570
|
-
transportName: name,
|
|
571
|
-
timestamp: Date.now(),
|
|
572
653
|
});
|
|
573
654
|
return;
|
|
574
655
|
}
|
|
@@ -641,21 +722,49 @@ export class Mail {
|
|
|
641
722
|
}
|
|
642
723
|
}
|
|
643
724
|
|
|
644
|
-
|
|
725
|
+
/**
|
|
726
|
+
* Build the message AND report which templates it was rendered from, since
|
|
727
|
+
* every lifecycle event carries both (AdonisJS `message` + `views`).
|
|
728
|
+
*/
|
|
729
|
+
async #buildMessageWithViews(
|
|
645
730
|
arg: ((message: MessageBuilder) => void) | BaseMail,
|
|
646
|
-
): Promise<MailMessage> {
|
|
731
|
+
): Promise<{ message: MailMessage; views: MessageBodyTemplates }> {
|
|
647
732
|
let result: MailMessage;
|
|
733
|
+
let views: MessageBodyTemplates;
|
|
648
734
|
if (arg instanceof BaseMail) {
|
|
649
735
|
const built = await arg.build(this.#viewsRoot);
|
|
650
736
|
result = built.from ? built : { ...built, from: this.#defaultFrom };
|
|
737
|
+
views = arg.message.views;
|
|
651
738
|
} else {
|
|
652
739
|
const builder = new MessageBuilder();
|
|
653
740
|
builder.from(this.#defaultFrom);
|
|
654
741
|
arg(builder);
|
|
655
742
|
result = await builder.build(this.#viewsRoot);
|
|
743
|
+
views = builder.views;
|
|
656
744
|
}
|
|
657
745
|
validateMailMessage(result);
|
|
658
|
-
return result;
|
|
746
|
+
return { message: result, views };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* The fields every lifecycle event shares. One place, so `mail:sending` and
|
|
751
|
+
* `mail:sent` cannot describe the same message differently.
|
|
752
|
+
*/
|
|
753
|
+
#eventBase(
|
|
754
|
+
message: MailMessage,
|
|
755
|
+
views: MessageBodyTemplates,
|
|
756
|
+
mailerName: string,
|
|
757
|
+
): MailEventBase {
|
|
758
|
+
return {
|
|
759
|
+
mailerName,
|
|
760
|
+
message,
|
|
761
|
+
views,
|
|
762
|
+
to: message.to.slice(),
|
|
763
|
+
cc: message.cc.slice(),
|
|
764
|
+
bcc: message.bcc.slice(),
|
|
765
|
+
transportName: mailerName,
|
|
766
|
+
timestamp: Date.now(),
|
|
767
|
+
};
|
|
659
768
|
}
|
|
660
769
|
|
|
661
770
|
/**
|
|
@@ -681,6 +790,26 @@ export class Mail {
|
|
|
681
790
|
return t;
|
|
682
791
|
}
|
|
683
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Close one transport's open connections (Adonis `close`).
|
|
795
|
+
*
|
|
796
|
+
* An SMTP pool keeps sockets alive between sends; a process that exits
|
|
797
|
+
* without closing them leaves the server holding connections until it times
|
|
798
|
+
* them out. Unknown or already-closed names are a no-op — shutdown is not
|
|
799
|
+
* the place to throw.
|
|
800
|
+
*/
|
|
801
|
+
async close(name?: string): Promise<void> {
|
|
802
|
+
const transportName = name ?? this.#defaultTransport;
|
|
803
|
+
await this.#transports.get(transportName)?.close?.();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/** Close every built transport (Adonis `closeAll`). What a shutdown hook calls. */
|
|
807
|
+
async closeAll(): Promise<void> {
|
|
808
|
+
await Promise.all(
|
|
809
|
+
[...this.#transports.values()].map((transport) => transport.close?.()),
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
|
|
684
813
|
/**
|
|
685
814
|
* Enter fake mode. Every subsequent `send()` / `sendLater()` — including via
|
|
686
815
|
* `use(name)` — is captured by the returned `FakeMail` instead of hitting a
|