@pearl-framework/mail 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @pearl-framework/mail@0.1.0 build /Users/sharvari/Desktop/Sharvari's Work Station/pearljs/packages/mail
4
+ > tsc
5
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sharvari Divekar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @pearl-framework/mail
2
+
3
+ > Mailable classes and multi-transport email sending for Pearl.js
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @pearl-framework/mail @pearl-framework/core nodemailer
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Define a mailable
14
+
15
+ ```ts
16
+ import { Mailable } from '@pearl-framework/mail'
17
+
18
+ export class WelcomeEmail extends Mailable {
19
+ constructor(private readonly user: User) { super() }
20
+
21
+ build(): this {
22
+ return this
23
+ .to(this.user.email)
24
+ .from({ name: 'Pearl App', address: 'hi@pearl.dev' })
25
+ .subject('Welcome to Pearl!')
26
+ .html(`<h1>Hi ${this.user.name}, welcome aboard!</h1>`)
27
+ .text(`Hi ${this.user.name}, welcome aboard!`)
28
+ }
29
+ }
30
+ ```
31
+
32
+ ### Send mail
33
+
34
+ ```ts
35
+ const mailer = app.make(Mailer)
36
+ await mailer.send(new WelcomeEmail(user))
37
+ await mailer.sendBulk([new WelcomeEmail(u1), new WelcomeEmail(u2)])
38
+ ```
39
+
40
+ ### MailServiceProvider
41
+
42
+ ```ts
43
+ export class AppMailServiceProvider extends MailServiceProvider {
44
+ protected config = {
45
+ driver: process.env.MAIL_DRIVER as MailDriver,
46
+ from: { name: 'Pearl App', address: process.env.MAIL_FROM! },
47
+ smtp: {
48
+ host: process.env.MAIL_HOST!,
49
+ port: Number(process.env.MAIL_PORT),
50
+ auth: { user: process.env.MAIL_USER!, pass: process.env.MAIL_PASS! },
51
+ },
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Transports
57
+
58
+ | Transport | Use case |
59
+ |-----------|---------|
60
+ | `SmtpTransport` | Production SMTP (Mailgun, Postmark, etc.) |
61
+ | `SesTransport` | AWS SES |
62
+ | `LogTransport` | Development — logs to console |
63
+ | `ArrayTransport` | Testing — captures emails in memory |
64
+
65
+ ---
@@ -0,0 +1,14 @@
1
+ import type { MailTransport } from './transports/index.js';
2
+ import type { Mailable, MailAddress } from './mail/Mailable.js';
3
+ export interface MailerConfig {
4
+ from?: MailAddress | string;
5
+ transport: MailTransport;
6
+ }
7
+ export declare class Mailer {
8
+ private readonly config;
9
+ constructor(config: MailerConfig);
10
+ send(mailable: Mailable): Promise<void>;
11
+ sendBulk(mailables: Mailable[]): Promise<void>;
12
+ get transport(): MailTransport;
13
+ }
14
+ //# sourceMappingURL=Mailer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mailer.d.ts","sourceRoot":"","sources":["../src/Mailer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAE/D,MAAM,WAAW,YAAY;IACzB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC3B,SAAS,EAAE,aAAa,CAAA;CAC3B;AAED,qBAAa,MAAM;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAI3C,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD,IAAI,SAAS,IAAI,aAAa,CAE7B;CACJ"}
package/dist/Mailer.js ADDED
@@ -0,0 +1,19 @@
1
+ export class Mailer {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ // ─── Sending ──────────────────────────────────────────────────────────────
7
+ async send(mailable) {
8
+ const mail = await mailable.compile(this.config.from);
9
+ await this.config.transport.send(mail);
10
+ }
11
+ async sendBulk(mailables) {
12
+ await Promise.all(mailables.map((m) => this.send(m)));
13
+ }
14
+ // ─── Transport access ─────────────────────────────────────────────────────
15
+ get transport() {
16
+ return this.config.transport;
17
+ }
18
+ }
19
+ //# sourceMappingURL=Mailer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mailer.js","sourceRoot":"","sources":["../src/Mailer.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,MAAM;IACc;IAA7B,YAA6B,MAAoB;QAApB,WAAM,GAAN,MAAM,CAAc;IAAG,CAAC;IAErD,6EAA6E;IAE7E,KAAK,CAAC,IAAI,CAAC,QAAkB;QACzB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACrD,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAqB;QAChC,MAAM,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACzD,CAAC;IAED,6EAA6E;IAE7E,IAAI,SAAS;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA;IAChC,CAAC;CACJ"}
@@ -0,0 +1,9 @@
1
+ export { Mailable } from './mail/Mailable.js';
2
+ export type { MailAddress, MailEnvelope, MailContent, BuiltMail } from './mail/Mailable.js';
3
+ export { Mailer } from './Mailer.js';
4
+ export type { MailerConfig } from './Mailer.js';
5
+ export { SmtpTransport, LogTransport, ArrayTransport, SesTransport } from './transports/index.js';
6
+ export type { MailTransport, SmtpConfig, SesConfig } from './transports/index.js';
7
+ export { MailServiceProvider } from './providers/MailServiceProvider.js';
8
+ export type { MailServiceConfig, MailDriver } from './providers/MailServiceProvider.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE3F,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACjG,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AACxE,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Mailable } from './mail/Mailable.js';
2
+ export { Mailer } from './Mailer.js';
3
+ export { SmtpTransport, LogTransport, ArrayTransport, SesTransport } from './transports/index.js';
4
+ export { MailServiceProvider } from './providers/MailServiceProvider.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGjG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA"}
@@ -0,0 +1,73 @@
1
+ import type { Attachment } from 'nodemailer/lib/mailer/index.js';
2
+ export interface MailAddress {
3
+ name?: string;
4
+ address: string;
5
+ }
6
+ export interface MailEnvelope {
7
+ from?: MailAddress | string;
8
+ to: Array<MailAddress | string>;
9
+ cc?: Array<MailAddress | string>;
10
+ bcc?: Array<MailAddress | string>;
11
+ replyTo?: MailAddress | string;
12
+ subject: string;
13
+ }
14
+ export interface MailContent {
15
+ html?: string;
16
+ text?: string;
17
+ attachments?: Attachment[];
18
+ }
19
+ export interface BuiltMail extends MailContent {
20
+ from?: MailAddress | string;
21
+ to: Array<MailAddress | string>;
22
+ cc?: Array<MailAddress | string>;
23
+ bcc?: Array<MailAddress | string>;
24
+ replyTo?: MailAddress | string;
25
+ subject: string;
26
+ }
27
+ /**
28
+ * Base class for all Pearl mailables.
29
+ *
30
+ * Usage:
31
+ * export class WelcomeEmail extends Mailable {
32
+ * constructor(private readonly user: User) {
33
+ * super()
34
+ * }
35
+ *
36
+ * build(): this {
37
+ * return this
38
+ * .to(this.user.email)
39
+ * .subject('Welcome to Pearl!')
40
+ * .html(`<h1>Hi ${this.user.name}!</h1>`)
41
+ * }
42
+ * }
43
+ */
44
+ export declare abstract class Mailable {
45
+ private _from?;
46
+ private _to;
47
+ private _cc;
48
+ private _bcc;
49
+ private _replyTo?;
50
+ private _subject;
51
+ private _html?;
52
+ private _text?;
53
+ private _attachments;
54
+ from(address: MailAddress | string): this;
55
+ to(...addresses: Array<MailAddress | string>): this;
56
+ cc(...addresses: Array<MailAddress | string>): this;
57
+ bcc(...addresses: Array<MailAddress | string>): this;
58
+ replyTo(address: MailAddress | string): this;
59
+ subject(value: string): this;
60
+ html(content: string): this;
61
+ text(content: string): this;
62
+ attach(attachment: Attachment): this;
63
+ /**
64
+ * Implement this to configure the email.
65
+ * Call this.to(), this.subject(), this.html() etc.
66
+ */
67
+ abstract build(): this | Promise<this>;
68
+ compile(defaultFrom?: MailAddress | string): Promise<BuiltMail>;
69
+ /** Override to send via queue instead of synchronously */
70
+ shouldQueue(): boolean;
71
+ get queue(): string;
72
+ }
73
+ //# sourceMappingURL=Mailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mailable.d.ts","sourceRoot":"","sources":["../../src/mail/Mailable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAEhE,MAAM,WAAW,WAAW;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC3B,EAAE,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IAC/B,EAAE,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IAChC,GAAG,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC9B,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,SAAU,SAAQ,WAAW;IAC1C,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC3B,EAAE,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IAC/B,EAAE,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IAChC,GAAG,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC9B,OAAO,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,8BAAsB,QAAQ;IAC1B,OAAO,CAAC,KAAK,CAAC,CAAsB;IACpC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,IAAI,CAAkC;IAC9C,OAAO,CAAC,QAAQ,CAAC,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,KAAK,CAAC,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAmB;IAIvC,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAKzC,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,IAAI;IAKnD,EAAE,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,IAAI;IAKnD,GAAG,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,IAAI;IAKpD,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAK5C,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK3B,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAOpC;;;OAGG;IACH,QAAQ,CAAC,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAEhC,OAAO,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAoBrE,0DAA0D;IAC1D,WAAW,IAAI,OAAO;IAItB,IAAI,KAAK,IAAI,MAAM,CAElB;CACJ"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Base class for all Pearl mailables.
3
+ *
4
+ * Usage:
5
+ * export class WelcomeEmail extends Mailable {
6
+ * constructor(private readonly user: User) {
7
+ * super()
8
+ * }
9
+ *
10
+ * build(): this {
11
+ * return this
12
+ * .to(this.user.email)
13
+ * .subject('Welcome to Pearl!')
14
+ * .html(`<h1>Hi ${this.user.name}!</h1>`)
15
+ * }
16
+ * }
17
+ */
18
+ export class Mailable {
19
+ _from;
20
+ _to = [];
21
+ _cc = [];
22
+ _bcc = [];
23
+ _replyTo;
24
+ _subject = '';
25
+ _html;
26
+ _text;
27
+ _attachments = [];
28
+ // ─── Builder methods ──────────────────────────────────────────────────────
29
+ from(address) {
30
+ this._from = address;
31
+ return this;
32
+ }
33
+ to(...addresses) {
34
+ this._to.push(...addresses);
35
+ return this;
36
+ }
37
+ cc(...addresses) {
38
+ this._cc.push(...addresses);
39
+ return this;
40
+ }
41
+ bcc(...addresses) {
42
+ this._bcc.push(...addresses);
43
+ return this;
44
+ }
45
+ replyTo(address) {
46
+ this._replyTo = address;
47
+ return this;
48
+ }
49
+ subject(value) {
50
+ this._subject = value;
51
+ return this;
52
+ }
53
+ html(content) {
54
+ this._html = content;
55
+ return this;
56
+ }
57
+ text(content) {
58
+ this._text = content;
59
+ return this;
60
+ }
61
+ attach(attachment) {
62
+ this._attachments.push(attachment);
63
+ return this;
64
+ }
65
+ async compile(defaultFrom) {
66
+ await this.build();
67
+ const from = this._from ?? defaultFrom;
68
+ return {
69
+ ...(from !== undefined && { from }),
70
+ to: this._to,
71
+ ...(this._cc.length && { cc: this._cc }),
72
+ ...(this._bcc.length && { bcc: this._bcc }),
73
+ ...(this._replyTo !== undefined && { replyTo: this._replyTo }),
74
+ subject: this._subject,
75
+ ...(this._html !== undefined && { html: this._html }),
76
+ ...(this._text !== undefined && { text: this._text }),
77
+ ...(this._attachments.length && { attachments: this._attachments }),
78
+ };
79
+ }
80
+ // ─── Queue support ────────────────────────────────────────────────────────
81
+ /** Override to send via queue instead of synchronously */
82
+ shouldQueue() {
83
+ return false;
84
+ }
85
+ get queue() {
86
+ return 'mail';
87
+ }
88
+ }
89
+ //# sourceMappingURL=Mailable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Mailable.js","sourceRoot":"","sources":["../../src/mail/Mailable.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { ServiceProvider } from '@pearl-framework/core';
2
+ import { type SmtpConfig } from '../transports/index.js';
3
+ import type { MailAddress } from '../mail/Mailable.js';
4
+ export type MailDriver = 'smtp' | 'log' | 'array';
5
+ export interface MailServiceConfig {
6
+ driver: MailDriver;
7
+ from?: MailAddress | string;
8
+ smtp?: SmtpConfig;
9
+ }
10
+ /**
11
+ * MailServiceProvider registers the Mailer into the container.
12
+ *
13
+ * Usage — extend this in your app:
14
+ *
15
+ * export class AppMailServiceProvider extends MailServiceProvider {
16
+ * protected config: MailServiceConfig = {
17
+ * driver: env('MAIL_DRIVER') as MailDriver,
18
+ * from: { name: 'Pearl App', address: env('MAIL_FROM') },
19
+ * smtp: {
20
+ * host: env('MAIL_HOST'),
21
+ * port: env.number('MAIL_PORT'),
22
+ * auth: { user: env('MAIL_USER'), pass: env('MAIL_PASS') },
23
+ * },
24
+ * }
25
+ * }
26
+ */
27
+ export declare class MailServiceProvider extends ServiceProvider {
28
+ protected config: MailServiceConfig;
29
+ register(): void;
30
+ private resolveTransport;
31
+ }
32
+ //# sourceMappingURL=MailServiceProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailServiceProvider.d.ts","sourceRoot":"","sources":["../../src/providers/MailServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAKH,KAAK,UAAU,EAClB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAA;AAEjD,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC3B,IAAI,CAAC,EAAE,UAAU,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,SAAQ,eAAe;IACpD,SAAS,CAAC,MAAM,EAAE,iBAAiB,CAElC;IAED,QAAQ,IAAI,IAAI;IAUhB,OAAO,CAAC,gBAAgB;CAe3B"}
@@ -0,0 +1,50 @@
1
+ import { ServiceProvider } from '@pearl-framework/core';
2
+ import { Mailer } from '../Mailer.js';
3
+ import { SmtpTransport, LogTransport, ArrayTransport, } from '../transports/index.js';
4
+ /**
5
+ * MailServiceProvider registers the Mailer into the container.
6
+ *
7
+ * Usage — extend this in your app:
8
+ *
9
+ * export class AppMailServiceProvider extends MailServiceProvider {
10
+ * protected config: MailServiceConfig = {
11
+ * driver: env('MAIL_DRIVER') as MailDriver,
12
+ * from: { name: 'Pearl App', address: env('MAIL_FROM') },
13
+ * smtp: {
14
+ * host: env('MAIL_HOST'),
15
+ * port: env.number('MAIL_PORT'),
16
+ * auth: { user: env('MAIL_USER'), pass: env('MAIL_PASS') },
17
+ * },
18
+ * }
19
+ * }
20
+ */
21
+ export class MailServiceProvider extends ServiceProvider {
22
+ config = {
23
+ driver: 'log',
24
+ };
25
+ register() {
26
+ this.container.singleton(Mailer, () => {
27
+ const transport = this.resolveTransport();
28
+ return new Mailer({
29
+ transport,
30
+ ...(this.config.from !== undefined && { from: this.config.from }),
31
+ });
32
+ });
33
+ }
34
+ resolveTransport() {
35
+ switch (this.config.driver) {
36
+ case 'smtp': {
37
+ if (!this.config.smtp) {
38
+ throw new Error('SMTP config is required when using the smtp mail driver.');
39
+ }
40
+ return new SmtpTransport(this.config.smtp);
41
+ }
42
+ case 'array':
43
+ return new ArrayTransport();
44
+ case 'log':
45
+ default:
46
+ return new LogTransport();
47
+ }
48
+ }
49
+ }
50
+ //# sourceMappingURL=MailServiceProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MailServiceProvider.js","sourceRoot":"","sources":["../../src/providers/MailServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EACH,aAAa,EACb,YAAY,EACZ,cAAc,GAGjB,MAAM,wBAAwB,CAAA;AAW/B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,mBAAoB,SAAQ,eAAe;IAC1C,MAAM,GAAsB;QAClC,MAAM,EAAE,KAAK;KAChB,CAAA;IAED,QAAQ;QACJ,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAA;YACzC,OAAO,IAAI,MAAM,CAAC;gBACd,SAAS;gBACT,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;aACpE,CAAC,CAAA;QACF,CAAC,CAAC,CAAA;IACN,CAAC;IAEO,gBAAgB;QACpB,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7B,KAAK,MAAM,CAAC,CAAC,CAAC;gBACV,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;oBACxB,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;gBAC3E,CAAC;gBACD,OAAO,IAAI,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAC9C,CAAC;YACD,KAAK,OAAO;gBACR,OAAO,IAAI,cAAc,EAAE,CAAA;YAC/B,KAAK,KAAK,CAAC;YACX;gBACI,OAAO,IAAI,YAAY,EAAE,CAAA;QAC7B,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,38 @@
1
+ import type { BuiltMail } from '../mail/Mailable.js';
2
+ export interface MailTransport {
3
+ send(mail: BuiltMail): Promise<void>;
4
+ }
5
+ export interface SmtpConfig {
6
+ host: string;
7
+ port: number;
8
+ secure?: boolean;
9
+ auth?: {
10
+ user: string;
11
+ pass: string;
12
+ };
13
+ }
14
+ export declare class SmtpTransport implements MailTransport {
15
+ private readonly config;
16
+ constructor(config: SmtpConfig);
17
+ send(mail: BuiltMail): Promise<void>;
18
+ }
19
+ export interface SesConfig {
20
+ region: string;
21
+ accessKeyId: string;
22
+ secretAccessKey: string;
23
+ }
24
+ export declare class SesTransport implements MailTransport {
25
+ private readonly config;
26
+ constructor(config: SesConfig);
27
+ send(mail: BuiltMail): Promise<void>;
28
+ }
29
+ export declare class LogTransport implements MailTransport {
30
+ send(mail: BuiltMail): Promise<void>;
31
+ }
32
+ export declare class ArrayTransport implements MailTransport {
33
+ readonly sent: BuiltMail[];
34
+ send(mail: BuiltMail): Promise<void>;
35
+ clear(): void;
36
+ last(): BuiltMail | undefined;
37
+ }
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,qBAAqB,CAAA;AAEjE,MAAM,WAAW,aAAa;IAC1B,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvC;AAOD,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACxC;AAED,qBAAa,aAAc,YAAW,aAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,UAAU;IAEzC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAqB7C;AAED,MAAM,WAAW,SAAS;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;CAC1B;AAED,qBAAa,YAAa,YAAW,aAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAExC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAuB7C;AAED,qBAAa,YAAa,YAAW,aAAa;IACxC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAS7C;AAEG,qBAAa,cAAe,YAAW,aAAa;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAK;IACzB,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,KAAK,IAAI,IAAI;IACb,IAAI,IAAI,SAAS,GAAG,SAAS;CAChC"}
@@ -0,0 +1,80 @@
1
+ function toNodemailerAddress(addr) {
2
+ if (typeof addr === 'string')
3
+ return addr;
4
+ return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address;
5
+ }
6
+ export class SmtpTransport {
7
+ config;
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ async send(mail) {
12
+ const nodemailer = await import('nodemailer');
13
+ const transporter = nodemailer.createTransport({
14
+ host: this.config.host,
15
+ port: this.config.port,
16
+ secure: this.config.secure ?? this.config.port === 465,
17
+ ...(this.config.auth !== undefined && { auth: this.config.auth }),
18
+ });
19
+ await transporter.sendMail({
20
+ ...(mail.from !== undefined && { from: toNodemailerAddress(mail.from) }),
21
+ to: mail.to.map(toNodemailerAddress).join(', '),
22
+ ...(mail.cc !== undefined && { cc: mail.cc.map(toNodemailerAddress).join(', ') }),
23
+ ...(mail.bcc !== undefined && { bcc: mail.bcc.map(toNodemailerAddress).join(', ') }),
24
+ ...(mail.replyTo !== undefined && { replyTo: toNodemailerAddress(mail.replyTo) }),
25
+ subject: mail.subject,
26
+ ...(mail.html !== undefined && { html: mail.html }),
27
+ ...(mail.text !== undefined && { text: mail.text }),
28
+ ...(mail.attachments !== undefined && { attachments: mail.attachments }),
29
+ });
30
+ }
31
+ }
32
+ export class SesTransport {
33
+ config;
34
+ constructor(config) {
35
+ this.config = config;
36
+ }
37
+ async send(mail) {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ const aws = await import('@aws-sdk/client-ses');
40
+ const client = new aws.SESClient({
41
+ region: this.config.region,
42
+ credentials: {
43
+ accessKeyId: this.config.accessKeyId,
44
+ secretAccessKey: this.config.secretAccessKey,
45
+ },
46
+ });
47
+ const toAddresses = mail.to.map((a) => typeof a === 'string' ? a : a.address);
48
+ await client.send(new aws.SendEmailCommand({
49
+ Source: mail.from ? toNodemailerAddress(mail.from) : '',
50
+ Destination: { ToAddresses: toAddresses },
51
+ Message: {
52
+ Subject: { Data: mail.subject },
53
+ Body: {
54
+ ...(mail.html !== undefined && { Html: { Data: mail.html } }),
55
+ ...(mail.text !== undefined && { Text: { Data: mail.text } }),
56
+ },
57
+ },
58
+ }));
59
+ }
60
+ }
61
+ export class LogTransport {
62
+ async send(mail) {
63
+ console.log('\nšŸ“§ [Pearl Mail — LogTransport]');
64
+ console.log(' From: ', mail.from);
65
+ console.log(' To: ', mail.to);
66
+ console.log(' Subject:', mail.subject);
67
+ if (mail.html)
68
+ console.log(' HTML: ', mail.html.slice(0, 100) + '...');
69
+ if (mail.text)
70
+ console.log(' Text: ', mail.text.slice(0, 100) + '...');
71
+ console.log('');
72
+ }
73
+ }
74
+ export class ArrayTransport {
75
+ sent = [];
76
+ async send(mail) { this.sent.push(mail); }
77
+ clear() { this.sent.length = 0; }
78
+ last() { return this.sent[this.sent.length - 1]; }
79
+ }
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@pearl-framework/mail",
3
+ "version": "0.1.0",
4
+ "description": "Pearl.js mail — Nodemailer-powered mailable classes, transports, and queue support",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "typesVersions": {
15
+ "*": {
16
+ "*": [
17
+ "./dist/index.d.ts"
18
+ ]
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "nodemailer": "^6.9.0",
23
+ "@pearl-framework/core": "0.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.0.0",
27
+ "@types/nodemailer": "^6.4.0",
28
+ "typescript": "^5.4.0",
29
+ "vitest": "^1.6.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/skd09/pearl.js.git",
38
+ "directory": "packages/mail"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "dev": "tsc --watch",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit",
45
+ "clean": "rm -rf dist"
46
+ }
47
+ }
package/src/Mailer.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { MailTransport } from './transports/index.js'
2
+ import type { Mailable, MailAddress } from './mail/Mailable.js'
3
+
4
+ export interface MailerConfig {
5
+ from?: MailAddress | string
6
+ transport: MailTransport
7
+ }
8
+
9
+ export class Mailer {
10
+ constructor(private readonly config: MailerConfig) {}
11
+
12
+ // ─── Sending ──────────────────────────────────────────────────────────────
13
+
14
+ async send(mailable: Mailable): Promise<void> {
15
+ const mail = await mailable.compile(this.config.from)
16
+ await this.config.transport.send(mail)
17
+ }
18
+
19
+ async sendBulk(mailables: Mailable[]): Promise<void> {
20
+ await Promise.all(mailables.map((m) => this.send(m)))
21
+ }
22
+
23
+ // ─── Transport access ─────────────────────────────────────────────────────
24
+
25
+ get transport(): MailTransport {
26
+ return this.config.transport
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { Mailable } from './mail/Mailable.js'
2
+ export type { MailAddress, MailEnvelope, MailContent, BuiltMail } from './mail/Mailable.js'
3
+
4
+ export { Mailer } from './Mailer.js'
5
+ export type { MailerConfig } from './Mailer.js'
6
+
7
+ export { SmtpTransport, LogTransport, ArrayTransport, SesTransport } from './transports/index.js'
8
+ export type { MailTransport, SmtpConfig, SesConfig } from './transports/index.js'
9
+
10
+ export { MailServiceProvider } from './providers/MailServiceProvider.js'
11
+ export type { MailServiceConfig, MailDriver } from './providers/MailServiceProvider.js'
@@ -0,0 +1,143 @@
1
+ import type { Attachment } from 'nodemailer/lib/mailer/index.js'
2
+
3
+ export interface MailAddress {
4
+ name?: string
5
+ address: string
6
+ }
7
+
8
+ export interface MailEnvelope {
9
+ from?: MailAddress | string
10
+ to: Array<MailAddress | string>
11
+ cc?: Array<MailAddress | string>
12
+ bcc?: Array<MailAddress | string>
13
+ replyTo?: MailAddress | string
14
+ subject: string
15
+ }
16
+
17
+ export interface MailContent {
18
+ html?: string
19
+ text?: string
20
+ attachments?: Attachment[]
21
+ }
22
+
23
+ export interface BuiltMail extends MailContent {
24
+ from?: MailAddress | string
25
+ to: Array<MailAddress | string>
26
+ cc?: Array<MailAddress | string>
27
+ bcc?: Array<MailAddress | string>
28
+ replyTo?: MailAddress | string
29
+ subject: string
30
+ }
31
+
32
+ /**
33
+ * Base class for all Pearl mailables.
34
+ *
35
+ * Usage:
36
+ * export class WelcomeEmail extends Mailable {
37
+ * constructor(private readonly user: User) {
38
+ * super()
39
+ * }
40
+ *
41
+ * build(): this {
42
+ * return this
43
+ * .to(this.user.email)
44
+ * .subject('Welcome to Pearl!')
45
+ * .html(`<h1>Hi ${this.user.name}!</h1>`)
46
+ * }
47
+ * }
48
+ */
49
+ export abstract class Mailable {
50
+ private _from?: MailAddress | string
51
+ private _to: Array<MailAddress | string> = []
52
+ private _cc: Array<MailAddress | string> = []
53
+ private _bcc: Array<MailAddress | string> = []
54
+ private _replyTo?: MailAddress | string
55
+ private _subject = ''
56
+ private _html?: string
57
+ private _text?: string
58
+ private _attachments: Attachment[] = []
59
+
60
+ // ─── Builder methods ──────────────────────────────────────────────────────
61
+
62
+ from(address: MailAddress | string): this {
63
+ this._from = address
64
+ return this
65
+ }
66
+
67
+ to(...addresses: Array<MailAddress | string>): this {
68
+ this._to.push(...addresses)
69
+ return this
70
+ }
71
+
72
+ cc(...addresses: Array<MailAddress | string>): this {
73
+ this._cc.push(...addresses)
74
+ return this
75
+ }
76
+
77
+ bcc(...addresses: Array<MailAddress | string>): this {
78
+ this._bcc.push(...addresses)
79
+ return this
80
+ }
81
+
82
+ replyTo(address: MailAddress | string): this {
83
+ this._replyTo = address
84
+ return this
85
+ }
86
+
87
+ subject(value: string): this {
88
+ this._subject = value
89
+ return this
90
+ }
91
+
92
+ html(content: string): this {
93
+ this._html = content
94
+ return this
95
+ }
96
+
97
+ text(content: string): this {
98
+ this._text = content
99
+ return this
100
+ }
101
+
102
+ attach(attachment: Attachment): this {
103
+ this._attachments.push(attachment)
104
+ return this
105
+ }
106
+
107
+ // ─── Build ────────────────────────────────────────────────────────────────
108
+
109
+ /**
110
+ * Implement this to configure the email.
111
+ * Call this.to(), this.subject(), this.html() etc.
112
+ */
113
+ abstract build(): this | Promise<this>
114
+
115
+ async compile(defaultFrom?: MailAddress | string): Promise<BuiltMail> {
116
+ await this.build()
117
+
118
+ const from = this._from ?? defaultFrom
119
+
120
+ return {
121
+ ...(from !== undefined && { from }),
122
+ to: this._to,
123
+ ...(this._cc.length && { cc: this._cc }),
124
+ ...(this._bcc.length && { bcc: this._bcc }),
125
+ ...(this._replyTo !== undefined && { replyTo: this._replyTo }),
126
+ subject: this._subject,
127
+ ...(this._html !== undefined && { html: this._html }),
128
+ ...(this._text !== undefined && { text: this._text }),
129
+ ...(this._attachments.length && { attachments: this._attachments }),
130
+ }
131
+ }
132
+
133
+ // ─── Queue support ────────────────────────────────────────────────────────
134
+
135
+ /** Override to send via queue instead of synchronously */
136
+ shouldQueue(): boolean {
137
+ return false
138
+ }
139
+
140
+ get queue(): string {
141
+ return 'mail'
142
+ }
143
+ }
@@ -0,0 +1,67 @@
1
+ import { ServiceProvider } from '@pearl-framework/core'
2
+ import { Mailer } from '../Mailer.js'
3
+ import {
4
+ SmtpTransport,
5
+ LogTransport,
6
+ ArrayTransport,
7
+ type MailTransport,
8
+ type SmtpConfig,
9
+ } from '../transports/index.js'
10
+ import type { MailAddress } from '../mail/Mailable.js'
11
+
12
+ export type MailDriver = 'smtp' | 'log' | 'array'
13
+
14
+ export interface MailServiceConfig {
15
+ driver: MailDriver
16
+ from?: MailAddress | string
17
+ smtp?: SmtpConfig
18
+ }
19
+
20
+ /**
21
+ * MailServiceProvider registers the Mailer into the container.
22
+ *
23
+ * Usage — extend this in your app:
24
+ *
25
+ * export class AppMailServiceProvider extends MailServiceProvider {
26
+ * protected config: MailServiceConfig = {
27
+ * driver: env('MAIL_DRIVER') as MailDriver,
28
+ * from: { name: 'Pearl App', address: env('MAIL_FROM') },
29
+ * smtp: {
30
+ * host: env('MAIL_HOST'),
31
+ * port: env.number('MAIL_PORT'),
32
+ * auth: { user: env('MAIL_USER'), pass: env('MAIL_PASS') },
33
+ * },
34
+ * }
35
+ * }
36
+ */
37
+ export class MailServiceProvider extends ServiceProvider {
38
+ protected config: MailServiceConfig = {
39
+ driver: 'log',
40
+ }
41
+
42
+ register(): void {
43
+ this.container.singleton(Mailer, () => {
44
+ const transport = this.resolveTransport()
45
+ return new Mailer({
46
+ transport,
47
+ ...(this.config.from !== undefined && { from: this.config.from }),
48
+ })
49
+ })
50
+ }
51
+
52
+ private resolveTransport(): MailTransport {
53
+ switch (this.config.driver) {
54
+ case 'smtp': {
55
+ if (!this.config.smtp) {
56
+ throw new Error('SMTP config is required when using the smtp mail driver.')
57
+ }
58
+ return new SmtpTransport(this.config.smtp)
59
+ }
60
+ case 'array':
61
+ return new ArrayTransport()
62
+ case 'log':
63
+ default:
64
+ return new LogTransport()
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,96 @@
1
+ import type { BuiltMail, MailAddress } from '../mail/Mailable.js'
2
+
3
+ export interface MailTransport {
4
+ send(mail: BuiltMail): Promise<void>
5
+ }
6
+
7
+ function toNodemailerAddress(addr: MailAddress | string): string {
8
+ if (typeof addr === 'string') return addr
9
+ return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address
10
+ }
11
+
12
+ export interface SmtpConfig {
13
+ host: string
14
+ port: number
15
+ secure?: boolean
16
+ auth?: { user: string; pass: string }
17
+ }
18
+
19
+ export class SmtpTransport implements MailTransport {
20
+ constructor(private readonly config: SmtpConfig) {}
21
+
22
+ async send(mail: BuiltMail): Promise<void> {
23
+ const nodemailer = await import('nodemailer')
24
+ const transporter = nodemailer.createTransport({
25
+ host: this.config.host,
26
+ port: this.config.port,
27
+ secure: this.config.secure ?? this.config.port === 465,
28
+ ...(this.config.auth !== undefined && { auth: this.config.auth }),
29
+ })
30
+
31
+ await transporter.sendMail({
32
+ ...(mail.from !== undefined && { from: toNodemailerAddress(mail.from) }),
33
+ to: mail.to.map(toNodemailerAddress).join(', '),
34
+ ...(mail.cc !== undefined && { cc: mail.cc.map(toNodemailerAddress).join(', ') }),
35
+ ...(mail.bcc !== undefined && { bcc: mail.bcc.map(toNodemailerAddress).join(', ') }),
36
+ ...(mail.replyTo !== undefined && { replyTo: toNodemailerAddress(mail.replyTo) }),
37
+ subject: mail.subject,
38
+ ...(mail.html !== undefined && { html: mail.html }),
39
+ ...(mail.text !== undefined && { text: mail.text }),
40
+ ...(mail.attachments !== undefined && { attachments: mail.attachments }),
41
+ })
42
+ }
43
+ }
44
+
45
+ export interface SesConfig {
46
+ region: string
47
+ accessKeyId: string
48
+ secretAccessKey: string
49
+ }
50
+
51
+ export class SesTransport implements MailTransport {
52
+ constructor(private readonly config: SesConfig) {}
53
+
54
+ async send(mail: BuiltMail): Promise<void> {
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ const aws = await import('@aws-sdk/client-ses' as string) as any
57
+ const client = new aws.SESClient({
58
+ region: this.config.region,
59
+ credentials: {
60
+ accessKeyId: this.config.accessKeyId,
61
+ secretAccessKey: this.config.secretAccessKey,
62
+ },
63
+ })
64
+ const toAddresses = mail.to.map((a) => typeof a === 'string' ? a : a.address)
65
+ await client.send(new aws.SendEmailCommand({
66
+ Source: mail.from ? toNodemailerAddress(mail.from) : '',
67
+ Destination: { ToAddresses: toAddresses },
68
+ Message: {
69
+ Subject: { Data: mail.subject },
70
+ Body: {
71
+ ...(mail.html !== undefined && { Html: { Data: mail.html } }),
72
+ ...(mail.text !== undefined && { Text: { Data: mail.text } }),
73
+ },
74
+ },
75
+ }))
76
+ }
77
+ }
78
+
79
+ export class LogTransport implements MailTransport {
80
+ async send(mail: BuiltMail): Promise<void> {
81
+ console.log('\nšŸ“§ [Pearl Mail — LogTransport]')
82
+ console.log(' From: ', mail.from)
83
+ console.log(' To: ', mail.to)
84
+ console.log(' Subject:', mail.subject)
85
+ if (mail.html) console.log(' HTML: ', mail.html.slice(0, 100) + '...')
86
+ if (mail.text) console.log(' Text: ', mail.text.slice(0, 100) + '...')
87
+ console.log('')
88
+ }
89
+ }
90
+
91
+ export class ArrayTransport implements MailTransport {
92
+ readonly sent: BuiltMail[] = []
93
+ async send(mail: BuiltMail): Promise<void> { this.sent.push(mail) }
94
+ clear(): void { this.sent.length = 0 }
95
+ last(): BuiltMail | undefined { return this.sent[this.sent.length - 1] }
96
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*"],
9
+ }