@wisemen/nestjs-mail 0.3.0 → 1.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.
- package/README.md +91 -5
- package/dist/clients/mail-client.factory.d.ts +2 -1
- package/dist/clients/mail-client.factory.js +12 -1
- package/dist/clients/mail-client.factory.js.map +1 -1
- package/dist/clients/mail-delivery-hooks.client.d.ts +11 -0
- package/dist/clients/mail-delivery-hooks.client.js +47 -0
- package/dist/clients/mail-delivery-hooks.client.js.map +1 -0
- package/dist/clients/mail-delivery-hooks.test.d.ts +1 -0
- package/dist/clients/mail-delivery-hooks.test.js +104 -0
- package/dist/clients/mail-delivery-hooks.test.js.map +1 -0
- package/dist/clients/mail-providers.test.d.ts +1 -0
- package/dist/clients/mail-providers.test.js +275 -0
- package/dist/clients/mail-providers.test.js.map +1 -0
- package/dist/clients/mail.client.d.ts +26 -1
- package/dist/clients/mail.client.js +9 -0
- package/dist/clients/mail.client.js.map +1 -1
- package/dist/clients/mailgun-mail.client.d.ts +12 -0
- package/dist/clients/mailgun-mail.client.js +81 -0
- package/dist/clients/mailgun-mail.client.js.map +1 -0
- package/dist/clients/mailpit-mail.client.js +2 -1
- package/dist/clients/mailpit-mail.client.js.map +1 -1
- package/dist/clients/scaleway-mail.client.js +1 -1
- package/dist/clients/scaleway-mail.client.js.map +1 -1
- package/dist/clients/sendgrid-mail.client.js +1 -3
- package/dist/clients/sendgrid-mail.client.js.map +1 -1
- package/dist/enums/mail-provider.enum.d.ts +2 -1
- package/dist/enums/mail-provider.enum.js +1 -0
- package/dist/enums/mail-provider.enum.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/mail.module-options.d.ts +22 -4
- package/dist/modules/mail.module.js +1 -1
- package/dist/modules/mail.module.js.map +1 -1
- package/dist/queue/mail-job-handlers.test.d.ts +1 -0
- package/dist/queue/mail-job-handlers.test.js +66 -0
- package/dist/queue/mail-job-handlers.test.js.map +1 -0
- package/dist/queue/send-html-mail.job-handler.js +2 -1
- package/dist/queue/send-html-mail.job-handler.js.map +1 -1
- package/dist/queue/send-html-mail.job.d.ts +3 -1
- package/dist/queue/send-html-mail.job.js.map +1 -1
- package/dist/queue/send-template-mail.job-handler.js +2 -1
- package/dist/queue/send-template-mail.job-handler.js.map +1 -1
- package/dist/queue/send-template-mail.job.d.ts +3 -1
- package/dist/queue/send-template-mail.job.js.map +1 -1
- package/package.json +5 -2
- package/skills/getting-started/SKILL.md +51 -5
package/README.md
CHANGED
|
@@ -6,7 +6,10 @@ Reusable NestJS mail infrastructure for Wisemen services.
|
|
|
6
6
|
|
|
7
7
|
- `MailModule.forRoot()` and `MailModule.forRootAsync()` for DI-friendly mail client wiring
|
|
8
8
|
- `HandlebarsRenderer` integration through `@wisemen/nestjs-handlebars`
|
|
9
|
-
- Mail providers for MailPit, Scaleway, and
|
|
9
|
+
- Mail providers for MailPit, Scaleway, SendGrid, and Mailgun
|
|
10
|
+
- Named sender support (`from` as a string or `{ email, name? }`)
|
|
11
|
+
- Application-defined delivery metadata
|
|
12
|
+
- Delivery success and failure hooks without replacing packaged clients or queue handlers
|
|
10
13
|
- Shared mail errors, enums, and message types
|
|
11
14
|
|
|
12
15
|
## Usage
|
|
@@ -21,10 +24,15 @@ import { MailModule } from '@wisemen/nestjs-mail'
|
|
|
21
24
|
templateRootPath: process.cwd() + '/dist/src/modules',
|
|
22
25
|
client: {
|
|
23
26
|
type: 'mailpit',
|
|
24
|
-
url: 'http://127.0.0.1:8025'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
url: 'http://127.0.0.1:8025',
|
|
28
|
+
defaultFrom: {
|
|
29
|
+
email: 'hello@example.com',
|
|
30
|
+
name: 'Example Team',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
queueName: 'mail',
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
28
36
|
})
|
|
29
37
|
export class AppModule {}
|
|
30
38
|
```
|
|
@@ -32,3 +40,81 @@ export class AppModule {}
|
|
|
32
40
|
Project-specific policy belongs outside the package. In larger apps, use `forRootAsync()` and resolve the `client` options from environment variables, feature flags, secrets, or test doubles in your own factory.
|
|
33
41
|
|
|
34
42
|
Install `@wisemen/nestjs-handlebars` alongside this package when you want to inject `HandlebarsRenderer` for template-based mail rendering.
|
|
43
|
+
|
|
44
|
+
## Named senders and metadata
|
|
45
|
+
|
|
46
|
+
Both configured default senders and per-message `from` values accept an email
|
|
47
|
+
string or an object with an optional display name. `metadata` is opaque
|
|
48
|
+
application context exposed to delivery hooks; it is never sent to MailPit,
|
|
49
|
+
Scaleway, or SendGrid.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await mailClient.sendMail({
|
|
53
|
+
from: {
|
|
54
|
+
email: 'tickets@example.com',
|
|
55
|
+
name: 'Example Tickets',
|
|
56
|
+
},
|
|
57
|
+
to: 'customer@example.com',
|
|
58
|
+
subject: 'Your tickets',
|
|
59
|
+
html: '<p>Your tickets are ready.</p>',
|
|
60
|
+
metadata: {
|
|
61
|
+
deliveryId: 'delivery-123',
|
|
62
|
+
kind: 'ticket-confirmation',
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Metadata placed in `SendHtmlMailJobData` or `SendTemplateMailJobData` must be
|
|
68
|
+
JSON-serializable because it passes through the queue.
|
|
69
|
+
|
|
70
|
+
## Mailgun
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
MailModule.forRoot({
|
|
74
|
+
client: {
|
|
75
|
+
type: 'mailgun',
|
|
76
|
+
domain: 'mail.example.com',
|
|
77
|
+
apiKey: 'key-...',
|
|
78
|
+
defaultFrom: {
|
|
79
|
+
email: 'hello@example.com',
|
|
80
|
+
name: 'Example Team',
|
|
81
|
+
},
|
|
82
|
+
// Use 'https://api.eu.mailgun.net' for EU-region domains. Defaults to 'https://api.mailgun.net'.
|
|
83
|
+
baseUrl: 'https://api.eu.mailgun.net',
|
|
84
|
+
},
|
|
85
|
+
queueName: 'mail',
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Delivery hooks
|
|
90
|
+
|
|
91
|
+
Configure one hooks object on the module. Hooks receive the configured client
|
|
92
|
+
type, the original send options (including metadata), and either the exact
|
|
93
|
+
`SentMail[]` result or the original provider error.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
MailModule.forRoot({
|
|
97
|
+
client: {
|
|
98
|
+
type: 'mailpit',
|
|
99
|
+
url: 'http://127.0.0.1:8025',
|
|
100
|
+
},
|
|
101
|
+
queueName: 'mail',
|
|
102
|
+
deliveryHooks: {
|
|
103
|
+
async onSuccess ({ clientType, options, sentMails }) {
|
|
104
|
+
await deliveries.markSent({
|
|
105
|
+
clientType,
|
|
106
|
+
metadata: options.metadata,
|
|
107
|
+
providerIds: sentMails.map(mail => mail.id),
|
|
108
|
+
})
|
|
109
|
+
},
|
|
110
|
+
async onFailure ({ clientType, options, error }) {
|
|
111
|
+
await deliveries.markFailed({ clientType, metadata: options.metadata, error })
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Hook failures log only the hook phase, client type, and error stack. Recipients,
|
|
118
|
+
bodies, subjects, and metadata are never added to the log. Hook failures never
|
|
119
|
+
turn a successful provider delivery into a failed send or replace a provider
|
|
120
|
+
error, so they cannot trigger a duplicate-email queue retry.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MailModuleOptions } from '../modules/mail.module-options.js';
|
|
2
2
|
import type { MailClient } from './mail.client.js';
|
|
3
|
-
|
|
3
|
+
import type { MailDeliveryHooks } from './mail.client.js';
|
|
4
|
+
export declare function createMailClient(options: MailModuleOptions['client'], deliveryHooks?: MailDeliveryHooks): MailClient;
|
|
4
5
|
export declare const mailClientFactory: typeof createMailClient;
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { MailProvider } from '../enums/mail-provider.enum.js';
|
|
2
2
|
import { exhaustiveCheck } from '../utils/exhaustive-check.js';
|
|
3
|
+
import { MailDeliveryHooksClient } from './mail-delivery-hooks.client.js';
|
|
4
|
+
import { MailgunMailClient } from './mailgun-mail.client.js';
|
|
3
5
|
import { MailPitMailClient } from './mailpit-mail.client.js';
|
|
4
6
|
import { MockMailClient } from './mock-mail.client.js';
|
|
5
7
|
import { ScalewayMailClient } from './scaleway-mail.client.js';
|
|
6
8
|
import { SendGridMailClient } from './sendgrid-mail.client.js';
|
|
7
|
-
export function createMailClient(options) {
|
|
9
|
+
export function createMailClient(options, deliveryHooks) {
|
|
10
|
+
const client = createConcreteMailClient(options);
|
|
11
|
+
if (deliveryHooks === undefined) {
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
14
|
+
return new MailDeliveryHooksClient(client, options.type, deliveryHooks);
|
|
15
|
+
}
|
|
16
|
+
function createConcreteMailClient(options) {
|
|
8
17
|
switch (options.type) {
|
|
9
18
|
case 'mailpit':
|
|
10
19
|
return new MailPitMailClient(options);
|
|
@@ -14,6 +23,8 @@ export function createMailClient(options) {
|
|
|
14
23
|
return new ScalewayMailClient(options);
|
|
15
24
|
case MailProvider.SEND_GRID:
|
|
16
25
|
return new SendGridMailClient(options);
|
|
26
|
+
case MailProvider.MAILGUN:
|
|
27
|
+
return new MailgunMailClient(options);
|
|
17
28
|
default:
|
|
18
29
|
exhaustiveCheck(options);
|
|
19
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail-client.factory.js","sourceRoot":"","sources":["../../lib/clients/mail-client.factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"mail-client.factory.js","sourceRoot":"","sources":["../../lib/clients/mail-client.factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAG9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAE9D,MAAM,UAAU,gBAAgB,CAC9B,OAAoC,EACpC,aAAiC;IAEjC,MAAM,MAAM,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAA;IAEhD,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO,IAAI,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,aAAa,CAAC,CAAA;AACzE,CAAC;AAED,SAAS,wBAAwB,CAAE,OAAoC;IACrE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAA;QACvC,KAAK,MAAM;YACT,OAAO,IAAI,cAAc,EAAE,CAAA;QAC7B,KAAK,YAAY,CAAC,QAAQ;YACxB,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAA;QACxC,KAAK,YAAY,CAAC,SAAS;YACzB,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAA;QACxC,KAAK,YAAY,CAAC,OAAO;YACvB,OAAO,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAA;QACvC;YACE,eAAe,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MailClientType, MailDeliveryHooks, SendMailOptions, SentMail } from './mail.client.js';
|
|
2
|
+
import { MailClient } from './mail.client.js';
|
|
3
|
+
export declare class MailDeliveryHooksClient extends MailClient {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly clientType;
|
|
6
|
+
private readonly hooks;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(client: MailClient, clientType: MailClientType, hooks: MailDeliveryHooks);
|
|
9
|
+
sendMail(options: SendMailOptions): Promise<SentMail[]>;
|
|
10
|
+
private runHook;
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { MailClient } from './mail.client.js';
|
|
3
|
+
export class MailDeliveryHooksClient extends MailClient {
|
|
4
|
+
client;
|
|
5
|
+
clientType;
|
|
6
|
+
hooks;
|
|
7
|
+
logger = new Logger(MailDeliveryHooksClient.name);
|
|
8
|
+
constructor(client, clientType, hooks) {
|
|
9
|
+
super();
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.clientType = clientType;
|
|
12
|
+
this.hooks = hooks;
|
|
13
|
+
}
|
|
14
|
+
async sendMail(options) {
|
|
15
|
+
let sentMails;
|
|
16
|
+
try {
|
|
17
|
+
sentMails = await this.client.sendMail(options);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
await this.runHook('failure', () => this.hooks.onFailure?.({
|
|
21
|
+
clientType: this.clientType,
|
|
22
|
+
options,
|
|
23
|
+
error
|
|
24
|
+
}));
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
await this.runHook('success', () => this.hooks.onSuccess?.({
|
|
28
|
+
clientType: this.clientType,
|
|
29
|
+
options,
|
|
30
|
+
sentMails
|
|
31
|
+
}));
|
|
32
|
+
return sentMails;
|
|
33
|
+
}
|
|
34
|
+
async runHook(phase, hook) {
|
|
35
|
+
try {
|
|
36
|
+
await hook();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.logger.error({
|
|
40
|
+
phase,
|
|
41
|
+
clientType: this.clientType,
|
|
42
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=mail-delivery-hooks.client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-delivery-hooks.client.js","sourceRoot":"","sources":["../../lib/clients/mail-delivery-hooks.client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAI7C,MAAM,OAAO,uBAAwB,SAAQ,UAAU;IAIlC;IACA;IACA;IALF,MAAM,GAAG,IAAI,MAAM,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAA;IAElE,YACmB,MAAkB,EAClB,UAA0B,EAC1B,KAAwB;QAEzC,KAAK,EAAE,CAAA;QAJU,WAAM,GAAN,MAAM,CAAY;QAClB,eAAU,GAAV,UAAU,CAAgB;QAC1B,UAAK,GAAL,KAAK,CAAmB;IAG3C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAE,OAAwB;QACtC,IAAI,SAAqB,CAAA;QAEzB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACjD,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;gBACzD,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,OAAO;gBACP,KAAK;aACN,CAAC,CAAC,CAAA;YAEH,MAAM,KAAK,CAAA;QACb,CAAC;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;YACzD,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO;YACP,SAAS;SACV,CAAC,CAAC,CAAA;QAEH,OAAO,SAAS,CAAA;IAClB,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,KAA4B,EAC5B,IAAqC;QAErC,IAAI,CAAC;YACH,MAAM,IAAI,EAAE,CAAA;QACd,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;gBAChB,KAAK;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;aACxD,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { afterEach, describe, it, mock } from 'node:test';
|
|
3
|
+
import { expect } from 'expect';
|
|
4
|
+
import { createMailClient } from './mail-client.factory.js';
|
|
5
|
+
import { MockMailClient } from './mock-mail.client.js';
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
mock.restoreAll();
|
|
8
|
+
});
|
|
9
|
+
const options = {
|
|
10
|
+
to: 'recipient@example.com',
|
|
11
|
+
subject: 'Sensitive subject',
|
|
12
|
+
text: 'Sensitive body',
|
|
13
|
+
metadata: { deliveryId: 'sensitive-metadata' }
|
|
14
|
+
};
|
|
15
|
+
describe('mail delivery hooks', () => {
|
|
16
|
+
it('awaits success hooks and preserves the exact result', async () => {
|
|
17
|
+
const sentMails = [{ id: 'provider-id', recipients: ['recipient@example.com'] }];
|
|
18
|
+
let delivery;
|
|
19
|
+
let hookFinished = false;
|
|
20
|
+
mock.method(MockMailClient.prototype, 'sendMail', () => Promise.resolve(sentMails));
|
|
21
|
+
const client = createMailClient({ type: 'mock' }, {
|
|
22
|
+
onSuccess: async (value) => {
|
|
23
|
+
await Promise.resolve();
|
|
24
|
+
delivery = value;
|
|
25
|
+
hookFinished = true;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const result = await client.sendMail(options);
|
|
29
|
+
expect(hookFinished).toBe(true);
|
|
30
|
+
expect(delivery?.clientType).toBe('mock');
|
|
31
|
+
expect(delivery?.options).toBe(options);
|
|
32
|
+
expect(delivery?.sentMails).toBe(sentMails);
|
|
33
|
+
expect(result).toBe(sentMails);
|
|
34
|
+
});
|
|
35
|
+
it('invokes failure hooks and rethrows the exact provider error', async () => {
|
|
36
|
+
const providerError = new Error('provider failed');
|
|
37
|
+
let delivery;
|
|
38
|
+
mock.method(MockMailClient.prototype, 'sendMail', () => Promise.reject(providerError));
|
|
39
|
+
const client = createMailClient({ type: 'mock' }, {
|
|
40
|
+
onFailure: async (value) => {
|
|
41
|
+
delivery = value;
|
|
42
|
+
await Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
await expect(client.sendMail(options)).rejects.toBe(providerError);
|
|
46
|
+
expect(delivery?.clientType).toBe('mock');
|
|
47
|
+
expect(delivery?.options).toBe(options);
|
|
48
|
+
expect(delivery?.error).toBe(providerError);
|
|
49
|
+
});
|
|
50
|
+
it('logs and swallows success-hook failures without invoking failure hooks', async () => {
|
|
51
|
+
const sentMails = [{ id: 'provider-id', recipients: ['recipient@example.com'] }];
|
|
52
|
+
const hookError = new Error('success hook failed');
|
|
53
|
+
const logs = [];
|
|
54
|
+
let failureHookCalled = false;
|
|
55
|
+
mock.method(MockMailClient.prototype, 'sendMail', () => Promise.resolve(sentMails));
|
|
56
|
+
mock.method(Logger.prototype, 'error', (...args) => {
|
|
57
|
+
logs.push(args);
|
|
58
|
+
});
|
|
59
|
+
const client = createMailClient({ type: 'mock' }, {
|
|
60
|
+
onSuccess: () => Promise.reject(hookError),
|
|
61
|
+
onFailure: () => {
|
|
62
|
+
failureHookCalled = true;
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const result = await client.sendMail(options);
|
|
67
|
+
expect(result).toBe(sentMails);
|
|
68
|
+
expect(failureHookCalled).toBe(false);
|
|
69
|
+
expect(logs).toEqual([[{
|
|
70
|
+
phase: 'success',
|
|
71
|
+
clientType: 'mock',
|
|
72
|
+
stack: hookError.stack
|
|
73
|
+
}]]);
|
|
74
|
+
expect(JSON.stringify(logs)).not.toContain(options.subject);
|
|
75
|
+
expect(JSON.stringify(logs)).not.toContain(options.text);
|
|
76
|
+
expect(JSON.stringify(logs)).not.toContain('sensitive-metadata');
|
|
77
|
+
});
|
|
78
|
+
it('logs failure-hook failures and preserves the provider error', async () => {
|
|
79
|
+
const providerError = new Error('provider failed');
|
|
80
|
+
const hookError = new Error('failure hook failed');
|
|
81
|
+
const logs = [];
|
|
82
|
+
mock.method(MockMailClient.prototype, 'sendMail', () => Promise.reject(providerError));
|
|
83
|
+
mock.method(Logger.prototype, 'error', (...args) => {
|
|
84
|
+
logs.push(args);
|
|
85
|
+
});
|
|
86
|
+
const client = createMailClient({ type: 'mock' }, {
|
|
87
|
+
onFailure: () => Promise.reject(hookError)
|
|
88
|
+
});
|
|
89
|
+
await expect(client.sendMail(options)).rejects.toBe(providerError);
|
|
90
|
+
expect(logs).toEqual([[{
|
|
91
|
+
phase: 'failure',
|
|
92
|
+
clientType: 'mock',
|
|
93
|
+
stack: hookError.stack
|
|
94
|
+
}]]);
|
|
95
|
+
expect(JSON.stringify(logs)).not.toContain(options.subject);
|
|
96
|
+
expect(JSON.stringify(logs)).not.toContain(options.text);
|
|
97
|
+
expect(JSON.stringify(logs)).not.toContain('sensitive-metadata');
|
|
98
|
+
});
|
|
99
|
+
it('returns the existing concrete client unchanged without hooks', () => {
|
|
100
|
+
const client = createMailClient({ type: 'mock' });
|
|
101
|
+
expect(client).toBeInstanceOf(MockMailClient);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=mail-delivery-hooks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-delivery-hooks.test.js","sourceRoot":"","sources":["../../lib/clients/mail-delivery-hooks.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { afterEach, describe, it, mock } from 'node:test';
|
|
2
|
+
import { expect } from 'expect';
|
|
3
|
+
import { MailProvider } from '../enums/mail-provider.enum.js';
|
|
4
|
+
import { MailgunMailClient } from './mailgun-mail.client.js';
|
|
5
|
+
import { MailPitMailClient } from './mailpit-mail.client.js';
|
|
6
|
+
import { ScalewayMailClient } from './scaleway-mail.client.js';
|
|
7
|
+
import { SendGridMailClient } from './sendgrid-mail.client.js';
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
mock.restoreAll();
|
|
10
|
+
});
|
|
11
|
+
function captureFetchBody(response) {
|
|
12
|
+
let body;
|
|
13
|
+
mock.method(globalThis, 'fetch', (_input, init) => {
|
|
14
|
+
if (typeof init?.body !== 'string') {
|
|
15
|
+
return Promise.reject(new Error('Expected a JSON request body'));
|
|
16
|
+
}
|
|
17
|
+
body = init.body;
|
|
18
|
+
return Promise.resolve(response);
|
|
19
|
+
});
|
|
20
|
+
return () => JSON.parse(body ?? '');
|
|
21
|
+
}
|
|
22
|
+
function scalewayResponse() {
|
|
23
|
+
return new Response(JSON.stringify({
|
|
24
|
+
emails: [{ id: 'scaleway-id', mail_rcpt: 'recipient@example.com' }]
|
|
25
|
+
}), { status: 200 });
|
|
26
|
+
}
|
|
27
|
+
function sendGridResponse() {
|
|
28
|
+
return new Response(null, {
|
|
29
|
+
status: 202,
|
|
30
|
+
headers: { 'x-message-id': 'sendgrid-id' }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function captureFormDataBody(response) {
|
|
34
|
+
let body;
|
|
35
|
+
mock.method(globalThis, 'fetch', (_input, init) => {
|
|
36
|
+
if (!(init?.body instanceof FormData)) {
|
|
37
|
+
return Promise.reject(new Error('Expected a FormData request body'));
|
|
38
|
+
}
|
|
39
|
+
body = init.body;
|
|
40
|
+
return Promise.resolve(response);
|
|
41
|
+
});
|
|
42
|
+
return () => {
|
|
43
|
+
if (body === undefined) {
|
|
44
|
+
throw new Error('fetch was not called');
|
|
45
|
+
}
|
|
46
|
+
return body;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mailgunResponse() {
|
|
50
|
+
return new Response(JSON.stringify({ id: 'mailgun-id', message: 'Queued. Thank you.' }), { status: 200 });
|
|
51
|
+
}
|
|
52
|
+
describe('ScalewayMailClient sender payload', () => {
|
|
53
|
+
it('normalizes a string sender and preserves SentMail', async () => {
|
|
54
|
+
const getBody = captureFetchBody(scalewayResponse());
|
|
55
|
+
const client = new ScalewayMailClient({
|
|
56
|
+
type: MailProvider.SCALEWAY,
|
|
57
|
+
projectId: 'project-id',
|
|
58
|
+
apiKey: 'api-key',
|
|
59
|
+
from: 'default@example.com'
|
|
60
|
+
});
|
|
61
|
+
const sentMails = await client.sendMail({
|
|
62
|
+
from: 'sender@example.com',
|
|
63
|
+
to: 'recipient@example.com',
|
|
64
|
+
subject: 'Subject',
|
|
65
|
+
text: 'Body'
|
|
66
|
+
});
|
|
67
|
+
expect(getBody()).toMatchObject({ from: { email: 'sender@example.com' } });
|
|
68
|
+
expect(sentMails).toEqual([{ id: 'scaleway-id', recipients: ['recipient@example.com'] }]);
|
|
69
|
+
});
|
|
70
|
+
it('maps a named sender and excludes metadata', async () => {
|
|
71
|
+
const getBody = captureFetchBody(scalewayResponse());
|
|
72
|
+
const client = new ScalewayMailClient({
|
|
73
|
+
type: MailProvider.SCALEWAY,
|
|
74
|
+
projectId: 'project-id',
|
|
75
|
+
apiKey: 'api-key',
|
|
76
|
+
from: 'default@example.com'
|
|
77
|
+
});
|
|
78
|
+
await client.sendMail({
|
|
79
|
+
from: { email: 'sender@example.com', name: 'Sender Name' },
|
|
80
|
+
to: 'recipient@example.com',
|
|
81
|
+
subject: 'Subject',
|
|
82
|
+
text: 'Body',
|
|
83
|
+
metadata: { deliveryId: 'secret-delivery-id' }
|
|
84
|
+
});
|
|
85
|
+
const body = getBody();
|
|
86
|
+
expect(body).toMatchObject({ from: { email: 'sender@example.com', name: 'Sender Name' } });
|
|
87
|
+
expect(body).not.toHaveProperty('metadata');
|
|
88
|
+
expect(JSON.stringify(body)).not.toContain('secret-delivery-id');
|
|
89
|
+
});
|
|
90
|
+
it('maps a named default sender', async () => {
|
|
91
|
+
const getBody = captureFetchBody(scalewayResponse());
|
|
92
|
+
const client = new ScalewayMailClient({
|
|
93
|
+
type: MailProvider.SCALEWAY,
|
|
94
|
+
projectId: 'project-id',
|
|
95
|
+
apiKey: 'api-key',
|
|
96
|
+
from: { email: 'default@example.com', name: 'Default Sender' }
|
|
97
|
+
});
|
|
98
|
+
await client.sendMail({
|
|
99
|
+
to: 'recipient@example.com',
|
|
100
|
+
subject: 'Subject',
|
|
101
|
+
text: 'Body'
|
|
102
|
+
});
|
|
103
|
+
expect(getBody()).toMatchObject({ from: { email: 'default@example.com', name: 'Default Sender' } });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('SendGridMailClient sender payload', () => {
|
|
107
|
+
it('normalizes a string sender and preserves SentMail', async () => {
|
|
108
|
+
const getBody = captureFetchBody(sendGridResponse());
|
|
109
|
+
const client = new SendGridMailClient({
|
|
110
|
+
type: MailProvider.SEND_GRID,
|
|
111
|
+
apiToken: 'api-token',
|
|
112
|
+
defaultFrom: 'default@example.com'
|
|
113
|
+
});
|
|
114
|
+
const sentMails = await client.sendMail({
|
|
115
|
+
from: 'sender@example.com',
|
|
116
|
+
to: 'recipient@example.com',
|
|
117
|
+
subject: 'Subject',
|
|
118
|
+
text: 'Body'
|
|
119
|
+
});
|
|
120
|
+
expect(getBody()).toMatchObject({ from: { email: 'sender@example.com' } });
|
|
121
|
+
expect(sentMails).toEqual([{ id: 'sendgrid-id', recipients: ['recipient@example.com'] }]);
|
|
122
|
+
});
|
|
123
|
+
it('maps a named sender and excludes metadata', async () => {
|
|
124
|
+
const getBody = captureFetchBody(sendGridResponse());
|
|
125
|
+
const client = new SendGridMailClient({
|
|
126
|
+
type: MailProvider.SEND_GRID,
|
|
127
|
+
apiToken: 'api-token',
|
|
128
|
+
defaultFrom: 'default@example.com'
|
|
129
|
+
});
|
|
130
|
+
await client.sendMail({
|
|
131
|
+
from: { email: 'sender@example.com', name: 'Sender Name' },
|
|
132
|
+
to: 'recipient@example.com',
|
|
133
|
+
subject: 'Subject',
|
|
134
|
+
text: 'Body',
|
|
135
|
+
metadata: { deliveryId: 'secret-delivery-id' }
|
|
136
|
+
});
|
|
137
|
+
const body = getBody();
|
|
138
|
+
expect(body).toMatchObject({ from: { email: 'sender@example.com', name: 'Sender Name' } });
|
|
139
|
+
expect(body).not.toHaveProperty('metadata');
|
|
140
|
+
expect(JSON.stringify(body)).not.toContain('secret-delivery-id');
|
|
141
|
+
});
|
|
142
|
+
it('maps a named default sender', async () => {
|
|
143
|
+
const getBody = captureFetchBody(sendGridResponse());
|
|
144
|
+
const client = new SendGridMailClient({
|
|
145
|
+
type: MailProvider.SEND_GRID,
|
|
146
|
+
apiToken: 'api-token',
|
|
147
|
+
defaultFrom: { email: 'default@example.com', name: 'Default Sender' }
|
|
148
|
+
});
|
|
149
|
+
await client.sendMail({
|
|
150
|
+
to: 'recipient@example.com',
|
|
151
|
+
subject: 'Subject',
|
|
152
|
+
text: 'Body'
|
|
153
|
+
});
|
|
154
|
+
expect(getBody()).toMatchObject({ from: { email: 'default@example.com', name: 'Default Sender' } });
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('MailgunMailClient sender payload', () => {
|
|
158
|
+
it('normalizes a string sender and preserves SentMail', async () => {
|
|
159
|
+
const getForm = captureFormDataBody(mailgunResponse());
|
|
160
|
+
const client = new MailgunMailClient({
|
|
161
|
+
type: MailProvider.MAILGUN,
|
|
162
|
+
domain: 'mail.example.com',
|
|
163
|
+
apiKey: 'api-key',
|
|
164
|
+
defaultFrom: 'default@example.com'
|
|
165
|
+
});
|
|
166
|
+
const sentMails = await client.sendMail({
|
|
167
|
+
from: 'sender@example.com',
|
|
168
|
+
to: 'recipient@example.com',
|
|
169
|
+
subject: 'Subject',
|
|
170
|
+
text: 'Body'
|
|
171
|
+
});
|
|
172
|
+
expect(getForm().get('from')).toBe('sender@example.com');
|
|
173
|
+
expect(sentMails).toEqual([{ id: 'mailgun-id', recipients: ['recipient@example.com'] }]);
|
|
174
|
+
});
|
|
175
|
+
it('maps a named sender and excludes metadata', async () => {
|
|
176
|
+
const getForm = captureFormDataBody(mailgunResponse());
|
|
177
|
+
const client = new MailgunMailClient({
|
|
178
|
+
type: MailProvider.MAILGUN,
|
|
179
|
+
domain: 'mail.example.com',
|
|
180
|
+
apiKey: 'api-key',
|
|
181
|
+
defaultFrom: 'default@example.com'
|
|
182
|
+
});
|
|
183
|
+
await client.sendMail({
|
|
184
|
+
from: { email: 'sender@example.com', name: 'Sender Name' },
|
|
185
|
+
to: 'recipient@example.com',
|
|
186
|
+
subject: 'Subject',
|
|
187
|
+
text: 'Body',
|
|
188
|
+
metadata: { deliveryId: 'secret-delivery-id' }
|
|
189
|
+
});
|
|
190
|
+
const form = getForm();
|
|
191
|
+
expect(form.get('from')).toBe('Sender Name <sender@example.com>');
|
|
192
|
+
const values = [...form.values()]
|
|
193
|
+
.filter((value) => typeof value === 'string')
|
|
194
|
+
.join(' ');
|
|
195
|
+
expect(values).not.toContain('secret-delivery-id');
|
|
196
|
+
expect(values).not.toContain('metadata');
|
|
197
|
+
});
|
|
198
|
+
it('maps a named default sender', async () => {
|
|
199
|
+
const getForm = captureFormDataBody(mailgunResponse());
|
|
200
|
+
const client = new MailgunMailClient({
|
|
201
|
+
type: MailProvider.MAILGUN,
|
|
202
|
+
domain: 'mail.example.com',
|
|
203
|
+
apiKey: 'api-key',
|
|
204
|
+
defaultFrom: { email: 'default@example.com', name: 'Default Sender' }
|
|
205
|
+
});
|
|
206
|
+
await client.sendMail({
|
|
207
|
+
to: 'recipient@example.com',
|
|
208
|
+
subject: 'Subject',
|
|
209
|
+
text: 'Body'
|
|
210
|
+
});
|
|
211
|
+
expect(getForm().get('from')).toBe('Default Sender <default@example.com>');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
function captureMailPitRequest(client) {
|
|
215
|
+
let request;
|
|
216
|
+
Object.defineProperty(client, 'client', {
|
|
217
|
+
value: {
|
|
218
|
+
sendMessage: (value) => {
|
|
219
|
+
request = value;
|
|
220
|
+
return Promise.resolve({ ID: 'mailpit-id' });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return () => request;
|
|
225
|
+
}
|
|
226
|
+
describe('MailPitMailClient sender payload', () => {
|
|
227
|
+
it('normalizes a string sender and preserves SentMail', async () => {
|
|
228
|
+
const client = new MailPitMailClient({
|
|
229
|
+
type: 'mailpit',
|
|
230
|
+
url: 'http://127.0.0.1:8025'
|
|
231
|
+
});
|
|
232
|
+
const getRequest = captureMailPitRequest(client);
|
|
233
|
+
const sentMails = await client.sendMail({
|
|
234
|
+
from: 'sender@example.com',
|
|
235
|
+
to: 'recipient@example.com',
|
|
236
|
+
subject: 'Subject',
|
|
237
|
+
text: 'Body'
|
|
238
|
+
});
|
|
239
|
+
expect(getRequest()).toMatchObject({ From: { Email: 'sender@example.com' } });
|
|
240
|
+
expect(sentMails).toEqual([{ id: 'mailpit-id', recipients: ['recipient@example.com'] }]);
|
|
241
|
+
});
|
|
242
|
+
it('maps a named sender and excludes metadata', async () => {
|
|
243
|
+
const client = new MailPitMailClient({
|
|
244
|
+
type: 'mailpit',
|
|
245
|
+
url: 'http://127.0.0.1:8025'
|
|
246
|
+
});
|
|
247
|
+
const getRequest = captureMailPitRequest(client);
|
|
248
|
+
await client.sendMail({
|
|
249
|
+
from: { email: 'sender@example.com', name: 'Sender Name' },
|
|
250
|
+
to: 'recipient@example.com',
|
|
251
|
+
subject: 'Subject',
|
|
252
|
+
text: 'Body',
|
|
253
|
+
metadata: { deliveryId: 'secret-delivery-id' }
|
|
254
|
+
});
|
|
255
|
+
const request = getRequest();
|
|
256
|
+
expect(request).toMatchObject({ From: { Email: 'sender@example.com', Name: 'Sender Name' } });
|
|
257
|
+
expect(request).not.toHaveProperty('metadata');
|
|
258
|
+
expect(JSON.stringify(request)).not.toContain('secret-delivery-id');
|
|
259
|
+
});
|
|
260
|
+
it('maps a named default sender', async () => {
|
|
261
|
+
const client = new MailPitMailClient({
|
|
262
|
+
type: 'mailpit',
|
|
263
|
+
url: 'http://127.0.0.1:8025',
|
|
264
|
+
defaultFrom: { email: 'default@example.com', name: 'Default Sender' }
|
|
265
|
+
});
|
|
266
|
+
const getRequest = captureMailPitRequest(client);
|
|
267
|
+
await client.sendMail({
|
|
268
|
+
to: 'recipient@example.com',
|
|
269
|
+
subject: 'Subject',
|
|
270
|
+
text: 'Body'
|
|
271
|
+
});
|
|
272
|
+
expect(getRequest()).toMatchObject({ From: { Email: 'default@example.com', Name: 'Default Sender' } });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
//# sourceMappingURL=mail-providers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail-providers.test.js","sourceRoot":"","sources":["../../lib/clients/mail-providers.test.ts"],"names":[],"mappings":"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"}
|