@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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/BaseMail.d.ts +41 -1
  3. package/dist/BaseMail.d.ts.map +1 -1
  4. package/dist/BaseMail.js +48 -1
  5. package/dist/BaseMail.js.map +1 -1
  6. package/dist/Mail.d.ts +90 -24
  7. package/dist/Mail.d.ts.map +1 -1
  8. package/dist/Mail.js +123 -40
  9. package/dist/Mail.js.map +1 -1
  10. package/dist/MessageBuilder.d.ts +296 -8
  11. package/dist/MessageBuilder.d.ts.map +1 -1
  12. package/dist/MessageBuilder.js +537 -8
  13. package/dist/MessageBuilder.js.map +1 -1
  14. package/dist/config.d.ts +38 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +34 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/format.d.ts +10 -0
  19. package/dist/format.d.ts.map +1 -1
  20. package/dist/format.js +28 -1
  21. package/dist/format.js.map +1 -1
  22. package/dist/index.d.ts +19 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +16 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/testing/FakeMail.d.ts +33 -0
  27. package/dist/testing/FakeMail.d.ts.map +1 -1
  28. package/dist/testing/FakeMail.js +28 -0
  29. package/dist/testing/FakeMail.js.map +1 -1
  30. package/dist/transports/BrevoTransport.d.ts.map +1 -1
  31. package/dist/transports/BrevoTransport.js +7 -5
  32. package/dist/transports/BrevoTransport.js.map +1 -1
  33. package/dist/transports/MailgunTransport.d.ts.map +1 -1
  34. package/dist/transports/MailgunTransport.js +5 -3
  35. package/dist/transports/MailgunTransport.js.map +1 -1
  36. package/dist/transports/ResendTransport.d.ts.map +1 -1
  37. package/dist/transports/ResendTransport.js +7 -5
  38. package/dist/transports/ResendTransport.js.map +1 -1
  39. package/dist/transports/SendGridTransport.d.ts.map +1 -1
  40. package/dist/transports/SendGridTransport.js +10 -6
  41. package/dist/transports/SendGridTransport.js.map +1 -1
  42. package/dist/transports/SesTransport.d.ts.map +1 -1
  43. package/dist/transports/SesTransport.js +16 -7
  44. package/dist/transports/SesTransport.js.map +1 -1
  45. package/dist/transports/SparkPostTransport.d.ts.map +1 -1
  46. package/dist/transports/SparkPostTransport.js +7 -5
  47. package/dist/transports/SparkPostTransport.js.map +1 -1
  48. package/dist/transports/fetchError.d.ts +10 -0
  49. package/dist/transports/fetchError.d.ts.map +1 -1
  50. package/dist/transports/fetchError.js +30 -0
  51. package/dist/transports/fetchError.js.map +1 -1
  52. package/index.darwin-arm64.node +0 -0
  53. package/index.darwin-x64.node +0 -0
  54. package/index.linux-arm64-gnu.node +0 -0
  55. package/index.linux-x64-gnu.node +0 -0
  56. package/index.win32-x64-msvc.node +0 -0
  57. package/package.json +6 -1
  58. package/src/BaseMail.ts +59 -2
  59. package/src/Mail.ts +194 -65
  60. package/src/MessageBuilder.ts +756 -12
  61. package/src/config.ts +46 -0
  62. package/src/format.ts +33 -1
  63. package/src/index.ts +31 -2
  64. package/src/testing/FakeMail.ts +46 -0
  65. package/src/transports/BrevoTransport.ts +7 -5
  66. package/src/transports/MailgunTransport.ts +5 -3
  67. package/src/transports/ResendTransport.ts +7 -5
  68. package/src/transports/SendGridTransport.ts +10 -6
  69. package/src/transports/SesTransport.ts +16 -7
  70. package/src/transports/SparkPostTransport.ts +17 -11
  71. 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',
@@ -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
- protected readonly message: MessageBuilder;
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
@@ -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;;;;;;;;;;;;;GAaG;AACH,8BAAsB,QAAQ;IAC7B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAwB;IAElE,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;;IAUjB,QAAQ,CAAC,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAElC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAmBrD"}
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
- return this.message.build(viewsRoot);
85
+ this.#builtMessage = await this.message.build(viewsRoot);
86
+ return this.#builtMessage;
40
87
  }
41
88
  }
42
89
  function applyAddress(addr, set) {
@@ -1 +1 @@
1
- {"version":3,"file":"BaseMail.js","sourceRoot":"","sources":["../src/BaseMail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvE;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAgB,QAAQ;IACV,OAAO,GAAmB,IAAI,cAAc,EAAE,CAAC;IAElE,IAAI,CAAe;IACnB,OAAO,CAAe;IACtB,OAAO,CAAU;IAEjB;QACC,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACd,0DAA0D,CAC1D,CAAC;QACH,CAAC;IACF,CAAC;IAID,KAAK,CAAC,KAAK,CAAC,SAAkB;QAC7B,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,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACtC,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"}
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
- * Emitted (`mail:sending`) right before the transport `send` runs — no
45
- * `messageId` yet, since the provider hasn't accepted the message.
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 interface MailSendingEvent {
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
- export interface MailSentEvent {
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: Record<string, {
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
@@ -1 +1 @@
1
- {"version":3,"file":"Mail.d.ts","sourceRoot":"","sources":["../src/Mail.ts"],"names":[],"mappings":"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"}
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
- for (const [name, transportConfig] of Object.entries(config.transports)) {
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.#queue = options.queue;
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.#buildMessage(arg);
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.#buildMessage(arg);
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 message = await this.#buildMessage(arg);
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
- to: message.to.slice(),
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, arg instanceof BaseMail ? arg : undefined);
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
- async #buildMessage(arg) {
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