@breadstone/archipel-platform-mailing 0.0.34 → 0.0.35
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/package.json +5 -5
- package/src/MailModule.d.ts +29 -8
- package/src/MailModule.js +35 -80
- package/src/env.d.ts +0 -4
- package/src/env.js +3 -13
- package/src/events/IMailEvents.d.ts +12 -1
- package/src/events/listeners/MailEventSubscriber.js +2 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/models/IMailTemplateVariants.d.ts +10 -0
- package/src/models/IMailTemplateVariants.js +3 -0
- package/src/models/MailTemplateFormat.d.ts +6 -0
- package/src/models/MailTemplateFormat.js +3 -0
- package/src/services/MailService.d.ts +7 -3
- package/src/services/MailService.js +13 -6
- package/src/templating/MailTemplateEngine.d.ts +10 -1
- package/src/templating/MailTemplateEngine.js +16 -6
- package/src/templating/strategies/abstracts/TemplateFetchStrategyBase.d.ts +2 -4
- package/src/templating/strategies/blob/BlobTemplateFetchStrategy.d.ts +1 -1
- package/src/templating/strategies/database/DatabaseTemplateFetchStrategy.d.ts +1 -1
- package/src/templating/strategies/database/DatabaseTemplateFetchStrategy.js +3 -0
- package/src/templating/strategies/file/FileTemplateFetchStrategy.d.ts +6 -8
- package/src/templating/strategies/file/FileTemplateFetchStrategy.js +9 -14
- package/src/tokens/MailTokens.d.ts +1 -11
- package/src/tokens/MailTokens.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@breadstone/archipel-platform-mailing",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "Email delivery with strategy-based transport and template engines for NestJS applications.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -134,10 +134,10 @@
|
|
|
134
134
|
"README.md"
|
|
135
135
|
],
|
|
136
136
|
"dependencies": {
|
|
137
|
-
"@breadstone/archipel-platform-blob-storage": "0.0.
|
|
138
|
-
"@breadstone/archipel-platform-configuration": "0.0.
|
|
139
|
-
"@breadstone/archipel-platform-core": "0.0.
|
|
140
|
-
"@breadstone/archipel-platform-resources": "0.0.
|
|
137
|
+
"@breadstone/archipel-platform-blob-storage": "0.0.35",
|
|
138
|
+
"@breadstone/archipel-platform-configuration": "0.0.35",
|
|
139
|
+
"@breadstone/archipel-platform-core": "0.0.35",
|
|
140
|
+
"@breadstone/archipel-platform-resources": "0.0.35",
|
|
141
141
|
"tslib": "2.8.1"
|
|
142
142
|
},
|
|
143
143
|
"peerDependencies": {
|
package/src/MailModule.d.ts
CHANGED
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import type { IConfigRegistryEntry } from '@breadstone/archipel-platform-configuration';
|
|
2
|
-
import { type DynamicModule, type Provider } from '@nestjs/common';
|
|
2
|
+
import { type DynamicModule, type Provider, type Type } from '@nestjs/common';
|
|
3
|
+
import type { DeliveryStrategyBase } from './delivering/strategies/abstracts/DeliveryStrategyBase';
|
|
3
4
|
import type { IMailTemplateRepository } from './ports/IMailTemplateRepository';
|
|
5
|
+
import type { TemplateFetchStrategyBase } from './templating/strategies/abstracts/TemplateFetchStrategyBase';
|
|
4
6
|
/**
|
|
5
7
|
* Options for configuring the {@link MailModule}.
|
|
6
8
|
*
|
|
7
9
|
* @public
|
|
8
10
|
*/
|
|
9
11
|
export interface IMailModuleOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Concrete implementation of the {@link DeliveryStrategyBase} port.
|
|
14
|
+
* For example, `SmtpDeliveryStrategy`, `PostmarkDeliveryStrategy`, or `LogDeliveryStrategy`.
|
|
15
|
+
*/
|
|
16
|
+
readonly deliveryStrategy: Type<DeliveryStrategyBase>;
|
|
17
|
+
/**
|
|
18
|
+
* Concrete implementation of the {@link TemplateFetchStrategyBase} port.
|
|
19
|
+
* For example, `FileTemplateFetchStrategy`, `BlobTemplateFetchStrategy`, or `DatabaseTemplateFetchStrategy`.
|
|
20
|
+
*/
|
|
21
|
+
readonly templateFetchStrategy: Type<TemplateFetchStrategyBase>;
|
|
10
22
|
/**
|
|
11
23
|
* Whether to register the module globally.
|
|
12
24
|
* Defaults to `true`.
|
|
13
25
|
*/
|
|
14
26
|
readonly isGlobal?: boolean;
|
|
15
27
|
/**
|
|
16
|
-
*
|
|
17
|
-
* Merged with the platform default entries.
|
|
28
|
+
* Provider-specific configuration entries to register with the config module.
|
|
18
29
|
*/
|
|
19
30
|
readonly configEntries?: ReadonlyArray<Omit<IConfigRegistryEntry, 'module'>>;
|
|
31
|
+
/**
|
|
32
|
+
* Template names to load when using the `FileTemplateFetchStrategy`.
|
|
33
|
+
* Each name corresponds to a resource file (e.g. `'AuthRegister'` loads
|
|
34
|
+
* `AuthRegister.html` and `AuthRegister.txt`).
|
|
35
|
+
*
|
|
36
|
+
* Defaults to an empty array (no templates loaded).
|
|
37
|
+
*/
|
|
38
|
+
readonly templateNames?: ReadonlyArray<string>;
|
|
20
39
|
/**
|
|
21
40
|
* Provider for the `IMailTemplateRepository` port.
|
|
22
|
-
* Required when
|
|
41
|
+
* Required when using `DatabaseTemplateFetchStrategy`.
|
|
23
42
|
*
|
|
24
43
|
* Example:
|
|
25
44
|
* ```typescript
|
|
26
45
|
* MailModule.register({
|
|
46
|
+
* deliveryStrategy: SmtpDeliveryStrategy,
|
|
47
|
+
* templateFetchStrategy: DatabaseTemplateFetchStrategy,
|
|
27
48
|
* templateRepositoryProvider: {
|
|
28
49
|
* provide: MAIL_TEMPLATE_REPOSITORY_TOKEN,
|
|
29
50
|
* useClass: PrismaMailTemplateRepository,
|
|
@@ -36,19 +57,19 @@ export interface IMailModuleOptions {
|
|
|
36
57
|
/**
|
|
37
58
|
* Represents the mail module.
|
|
38
59
|
*
|
|
39
|
-
* Delivery and template strategies are
|
|
40
|
-
*
|
|
60
|
+
* Delivery and template strategies are selected at compile time via
|
|
61
|
+
* `IMailModuleOptions`. NestJS DI resolves the concrete classes.
|
|
41
62
|
*
|
|
42
63
|
* @public
|
|
43
64
|
*/
|
|
44
65
|
export declare class MailModule {
|
|
45
66
|
/**
|
|
46
|
-
* Registers the mail module with
|
|
67
|
+
* Registers the mail module with the specified configuration.
|
|
47
68
|
*
|
|
48
69
|
* @param options - Module registration options.
|
|
49
70
|
* @returns A configured `DynamicModule`.
|
|
50
71
|
*
|
|
51
72
|
* @public
|
|
52
73
|
*/
|
|
53
|
-
static register(options
|
|
74
|
+
static register(options: IMailModuleOptions): DynamicModule;
|
|
54
75
|
}
|
package/src/MailModule.js
CHANGED
|
@@ -7,7 +7,6 @@ const tslib_1 = require("tslib");
|
|
|
7
7
|
const archipel_platform_blob_storage_1 = require("@breadstone/archipel-platform-blob-storage");
|
|
8
8
|
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
9
9
|
const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
|
|
10
|
-
const archipel_platform_resources_1 = require("@breadstone/archipel-platform-resources");
|
|
11
10
|
const common_1 = require("@nestjs/common");
|
|
12
11
|
const env_1 = require("./env");
|
|
13
12
|
const MailEventSubscriber_1 = require("./events/listeners/MailEventSubscriber");
|
|
@@ -19,14 +18,15 @@ const MailTokens_1 = require("./tokens/MailTokens");
|
|
|
19
18
|
/**
|
|
20
19
|
* Represents the mail module.
|
|
21
20
|
*
|
|
22
|
-
* Delivery and template strategies are
|
|
23
|
-
*
|
|
21
|
+
* Delivery and template strategies are selected at compile time via
|
|
22
|
+
* `IMailModuleOptions`. NestJS DI resolves the concrete classes.
|
|
24
23
|
*
|
|
25
24
|
* @public
|
|
26
25
|
*/
|
|
27
26
|
let MailModule = MailModule_1 = class MailModule {
|
|
27
|
+
// #region Methods
|
|
28
28
|
/**
|
|
29
|
-
* Registers the mail module with
|
|
29
|
+
* Registers the mail module with the specified configuration.
|
|
30
30
|
*
|
|
31
31
|
* @param options - Module registration options.
|
|
32
32
|
* @returns A configured `DynamicModule`.
|
|
@@ -34,87 +34,42 @@ let MailModule = MailModule_1 = class MailModule {
|
|
|
34
34
|
* @public
|
|
35
35
|
*/
|
|
36
36
|
static register(options) {
|
|
37
|
-
const isGlobal = options
|
|
38
|
-
const configEntries = options
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const isGlobal = options.isGlobal ?? true;
|
|
38
|
+
const configEntries = options.configEntries ?? env_1.PLATFORM_MAILING_CONFIG_ENTRIES;
|
|
39
|
+
const providers = [
|
|
40
|
+
{
|
|
41
|
+
provide: MailTokens_1.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN,
|
|
42
|
+
useClass: options.templateFetchStrategy,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
provide: MailTokens_1.MAIL_DELIVERY_STRATEGY_TOKEN,
|
|
46
|
+
useClass: options.deliveryStrategy,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
provide: MailTokens_1.MAIL_TEMPLATE_NAMES_TOKEN,
|
|
50
|
+
useValue: options.templateNames ?? [],
|
|
51
|
+
},
|
|
52
|
+
archipel_platform_core_1.ContentTemplateEngine,
|
|
53
|
+
MailService_1.MailService,
|
|
54
|
+
MailTemplateEngine_1.MailTemplateEngine,
|
|
55
|
+
MailVerificationService_1.MailVerificationService,
|
|
56
|
+
SmtpConnectionVerifier_1.SmtpConnectionVerifier,
|
|
57
|
+
MailEventSubscriber_1.MailEventSubscriber,
|
|
58
|
+
];
|
|
59
|
+
const exports = [
|
|
60
|
+
MailService_1.MailService,
|
|
61
|
+
MailVerificationService_1.MailVerificationService,
|
|
62
|
+
SmtpConnectionVerifier_1.SmtpConnectionVerifier,
|
|
63
|
+
];
|
|
64
|
+
if (options.templateRepositoryProvider) {
|
|
65
|
+
providers.push(options.templateRepositoryProvider);
|
|
42
66
|
}
|
|
43
67
|
return {
|
|
44
68
|
module: MailModule_1,
|
|
45
69
|
global: isGlobal,
|
|
46
70
|
imports: [archipel_platform_configuration_1.ConfigModule.register('platform-mailing', configEntries), archipel_platform_blob_storage_1.BlobModule, archipel_platform_core_1.EventModule],
|
|
47
|
-
providers
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
provide: MailTokens_1.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN,
|
|
51
|
-
useFactory: async (configService, blobService, resourceManager, ...extra) => {
|
|
52
|
-
const templateStrategy = configService.get(env_1.MAIL_TEMPLATE_STRATEGY.key);
|
|
53
|
-
if (templateStrategy === 'file') {
|
|
54
|
-
const { FileTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./templating/strategies/file/FileTemplateFetchStrategy')));
|
|
55
|
-
return new FileTemplateFetchStrategy(resourceManager);
|
|
56
|
-
}
|
|
57
|
-
if (templateStrategy === 'blob') {
|
|
58
|
-
const { BlobTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./templating/strategies/blob/BlobTemplateFetchStrategy')));
|
|
59
|
-
return new BlobTemplateFetchStrategy(blobService);
|
|
60
|
-
}
|
|
61
|
-
if (templateStrategy === 'database') {
|
|
62
|
-
const repository = extra[0];
|
|
63
|
-
if (!repository) {
|
|
64
|
-
throw new Error("Template strategy 'database' requires a templateRepositoryProvider in MailModule.register() options.");
|
|
65
|
-
}
|
|
66
|
-
const { DatabaseTemplateFetchStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./templating/strategies/database/DatabaseTemplateFetchStrategy')));
|
|
67
|
-
return new DatabaseTemplateFetchStrategy(repository);
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Unknown template strategy: ${templateStrategy}`);
|
|
70
|
-
},
|
|
71
|
-
inject: [
|
|
72
|
-
archipel_platform_configuration_1.ConfigService,
|
|
73
|
-
archipel_platform_blob_storage_1.BlobService,
|
|
74
|
-
archipel_platform_resources_1.ResourceManager,
|
|
75
|
-
...(options?.templateRepositoryProvider ? [MailTokens_1.MAIL_TEMPLATE_REPOSITORY_TOKEN] : []),
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
provide: MailTokens_1.MAIL_DELIVERY_STRATEGY_TOKEN,
|
|
80
|
-
useFactory: async (configService) => {
|
|
81
|
-
const deliveryStrategy = configService.get(env_1.MAIL_DELIVERY_STRATEGY.key);
|
|
82
|
-
if (deliveryStrategy === 'log') {
|
|
83
|
-
const { LogDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/logger/LogDeliveryStrategy')));
|
|
84
|
-
return new LogDeliveryStrategy();
|
|
85
|
-
}
|
|
86
|
-
if (deliveryStrategy === 'smtp') {
|
|
87
|
-
const { SmtpDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/smtp/SmtpDeliveryStrategy')));
|
|
88
|
-
return new SmtpDeliveryStrategy(configService);
|
|
89
|
-
}
|
|
90
|
-
if (deliveryStrategy === 'postmark') {
|
|
91
|
-
const { PostmarkDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/postmark/PostmarkDeliveryStrategy')));
|
|
92
|
-
return new PostmarkDeliveryStrategy(configService);
|
|
93
|
-
}
|
|
94
|
-
if (deliveryStrategy === 'resend') {
|
|
95
|
-
const { ResendDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/resend/ResendDeliveryStrategy')));
|
|
96
|
-
return new ResendDeliveryStrategy(configService);
|
|
97
|
-
}
|
|
98
|
-
if (deliveryStrategy === 'sendgrid') {
|
|
99
|
-
const { SendGridDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/sendgrid/SendGridDeliveryStrategy')));
|
|
100
|
-
return new SendGridDeliveryStrategy(configService);
|
|
101
|
-
}
|
|
102
|
-
if (deliveryStrategy === 'mailgun') {
|
|
103
|
-
const { MailgunDeliveryStrategy } = await Promise.resolve().then(() => tslib_1.__importStar(require('./delivering/strategies/mailgun/MailgunDeliveryStrategy')));
|
|
104
|
-
return new MailgunDeliveryStrategy(configService);
|
|
105
|
-
}
|
|
106
|
-
throw new Error(`Unknown delivery strategy: ${deliveryStrategy}`);
|
|
107
|
-
},
|
|
108
|
-
inject: [archipel_platform_configuration_1.ConfigService],
|
|
109
|
-
},
|
|
110
|
-
archipel_platform_core_1.ContentTemplateEngine,
|
|
111
|
-
MailService_1.MailService,
|
|
112
|
-
MailTemplateEngine_1.MailTemplateEngine,
|
|
113
|
-
MailVerificationService_1.MailVerificationService,
|
|
114
|
-
SmtpConnectionVerifier_1.SmtpConnectionVerifier,
|
|
115
|
-
MailEventSubscriber_1.MailEventSubscriber,
|
|
116
|
-
],
|
|
117
|
-
exports: [MailService_1.MailService, MailVerificationService_1.MailVerificationService, SmtpConnectionVerifier_1.SmtpConnectionVerifier],
|
|
71
|
+
providers,
|
|
72
|
+
exports,
|
|
118
73
|
};
|
|
119
74
|
}
|
|
120
75
|
};
|
package/src/env.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { IConfigRegistryEntry } from '@breadstone/archipel-platform-configuration';
|
|
2
|
-
/** Which template-fetch strategy to use (e.g. `file`, `blob`). */
|
|
3
|
-
export declare const MAIL_TEMPLATE_STRATEGY: import("@breadstone/archipel-platform-configuration").IConfigKey<string>;
|
|
4
|
-
/** Which delivery strategy to use (e.g. `smtp`, `postmark`, `resend`, `sendgrid`, `mailgun`, `log`). */
|
|
5
|
-
export declare const MAIL_DELIVERY_STRATEGY: import("@breadstone/archipel-platform-configuration").IConfigKey<string>;
|
|
6
2
|
/** Sender e-mail address shown in the `From` header. */
|
|
7
3
|
export declare const MAIL_SENDER_EMAIL: import("@breadstone/archipel-platform-configuration").IConfigKey<string>;
|
|
8
4
|
/** Template engine format identifier (e.g. `handlebars`). */
|
package/src/env.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// #region Imports
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.PLATFORM_MAILING_CONFIG_ENTRIES = exports.MAIL_PORT = exports.MAIL_USER = exports.MAIL_HOST = exports.MAIL_SMTP_PASSWORD = exports.MAIL_SMTP_USER = exports.MAIL_SMTP_SECURE = exports.MAIL_SMTP_PORT = exports.MAIL_SMTP_HOST = exports.MAIL_MAILGUN_DOMAIN = exports.MAIL_MAILGUN_API_KEY = exports.MAIL_SENDGRID_API_KEY = exports.MAIL_RESEND_API_KEY = exports.MAIL_POSTMARK_API_KEY = exports.MAIL_TEMPLATE_ENGINE_FORMAT = exports.MAIL_SENDER_EMAIL =
|
|
4
|
+
exports.PLATFORM_MAILING_CONFIG_ENTRIES = exports.MAIL_PORT = exports.MAIL_USER = exports.MAIL_HOST = exports.MAIL_SMTP_PASSWORD = exports.MAIL_SMTP_USER = exports.MAIL_SMTP_SECURE = exports.MAIL_SMTP_PORT = exports.MAIL_SMTP_HOST = exports.MAIL_MAILGUN_DOMAIN = exports.MAIL_MAILGUN_API_KEY = exports.MAIL_SENDGRID_API_KEY = exports.MAIL_RESEND_API_KEY = exports.MAIL_POSTMARK_API_KEY = exports.MAIL_TEMPLATE_ENGINE_FORMAT = exports.MAIL_SENDER_EMAIL = void 0;
|
|
5
5
|
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
6
6
|
// #endregion
|
|
7
7
|
// ──────────────────────────────────────────────────────────────
|
|
8
|
-
//
|
|
8
|
+
// Core
|
|
9
9
|
// ──────────────────────────────────────────────────────────────
|
|
10
|
-
/** Which template-fetch strategy to use (e.g. `file`, `blob`). */
|
|
11
|
-
exports.MAIL_TEMPLATE_STRATEGY = (0, archipel_platform_configuration_1.createConfigKey)('MAIL_TEMPLATE_STRATEGY');
|
|
12
|
-
/** Which delivery strategy to use (e.g. `smtp`, `postmark`, `resend`, `sendgrid`, `mailgun`, `log`). */
|
|
13
|
-
exports.MAIL_DELIVERY_STRATEGY = (0, archipel_platform_configuration_1.createConfigKey)('MAIL_DELIVERY_STRATEGY');
|
|
14
10
|
/** Sender e-mail address shown in the `From` header. */
|
|
15
11
|
exports.MAIL_SENDER_EMAIL = (0, archipel_platform_configuration_1.createConfigKey)('MAIL_SENDER_EMAIL');
|
|
16
12
|
/** Template engine format identifier (e.g. `handlebars`). */
|
|
@@ -64,13 +60,7 @@ exports.MAIL_PORT = (0, archipel_platform_configuration_1.createConfigKey)('MAIL
|
|
|
64
60
|
// ──────────────────────────────────────────────────────────────
|
|
65
61
|
/** All configuration entries required by `platform-mailing`. */
|
|
66
62
|
exports.PLATFORM_MAILING_CONFIG_ENTRIES = [
|
|
67
|
-
//
|
|
68
|
-
{ key: exports.MAIL_TEMPLATE_STRATEGY, required: true, description: 'Template-fetch strategy (file, blob, database).' },
|
|
69
|
-
{
|
|
70
|
-
key: exports.MAIL_DELIVERY_STRATEGY,
|
|
71
|
-
required: true,
|
|
72
|
-
description: 'Delivery strategy (smtp, postmark, resend, sendgrid, mailgun, log).',
|
|
73
|
-
},
|
|
63
|
+
// Core
|
|
74
64
|
{ key: exports.MAIL_SENDER_EMAIL, required: true, description: 'Sender e-mail address for the From header.' },
|
|
75
65
|
{ key: exports.MAIL_TEMPLATE_ENGINE_FORMAT, required: true, description: 'Template engine format identifier.' },
|
|
76
66
|
// Postmark
|
|
@@ -2,14 +2,25 @@ import { IEventMap } from '@breadstone/archipel-platform-core';
|
|
|
2
2
|
/**
|
|
3
3
|
* Represents the contract of events emitted by the mail domain.
|
|
4
4
|
*
|
|
5
|
+
* Publishers only need to specify `recipientEmail`, `templateName`, and `templateParams`.
|
|
6
|
+
* The subject is resolved from the stored template (industry-standard behavior).
|
|
7
|
+
* An explicit `subject` can be provided as an override.
|
|
8
|
+
*
|
|
5
9
|
* @public
|
|
6
10
|
*/
|
|
7
11
|
export interface IMailEvents extends IEventMap {
|
|
8
12
|
sendMail: {
|
|
13
|
+
/** Recipient email address. */
|
|
9
14
|
recipientEmail: string;
|
|
10
|
-
|
|
15
|
+
/** Template key to resolve (e.g. `'AuthRegister'`). */
|
|
11
16
|
templateName: string;
|
|
17
|
+
/** Placeholder values to interpolate into subject and body. */
|
|
12
18
|
templateParams: Record<string, string | number | boolean | Date | undefined | null>;
|
|
19
|
+
/** Optional locale for template resolution (e.g. `'de'`, `'en'`). */
|
|
20
|
+
locale?: string;
|
|
21
|
+
/** Optional subject override. When omitted, resolved from the template. */
|
|
22
|
+
subject?: string;
|
|
23
|
+
/** Optional sender email override. When omitted, uses the configured default. */
|
|
13
24
|
senderEmail?: string;
|
|
14
25
|
};
|
|
15
26
|
}
|
|
@@ -29,7 +29,8 @@ let MailEventSubscriber = MailEventSubscriber_1 = class MailEventSubscriber {
|
|
|
29
29
|
this._mailService = mailService;
|
|
30
30
|
this._subscription = eventHub.observe(MailEventKeys_1.SEND_MAIL_EVENT_KEY).subscribe({
|
|
31
31
|
next: (payload) => {
|
|
32
|
-
this._logger.debug(`Received mail event for recipient ${(0, archipel_platform_core_1.maskSensitive)(payload.recipientEmail)}
|
|
32
|
+
this._logger.debug(`Received mail event for recipient ${(0, archipel_platform_core_1.maskSensitive)(payload.recipientEmail)} ` +
|
|
33
|
+
`with template "${payload.templateName}"${payload.subject ? ` (subject override: "${(0, archipel_platform_core_1.maskSensitive)(payload.subject)}")` : ''}`);
|
|
33
34
|
void this.handle(payload);
|
|
34
35
|
},
|
|
35
36
|
error: (err) => {
|
package/src/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export * from './events';
|
|
|
5
5
|
export * from './MailModule';
|
|
6
6
|
export * from './models/IMailAttachment';
|
|
7
7
|
export * from './models/IMailTemplate';
|
|
8
|
+
export * from './models/IMailTemplateVariants';
|
|
9
|
+
export * from './models/MailTemplateFormat';
|
|
8
10
|
export * from './ports/IMailTemplateRepository';
|
|
9
11
|
export * from './services/MailService';
|
|
10
12
|
export * from './services/MailVerificationService';
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,8 @@ tslib_1.__exportStar(require("./events"), exports);
|
|
|
8
8
|
tslib_1.__exportStar(require("./MailModule"), exports);
|
|
9
9
|
tslib_1.__exportStar(require("./models/IMailAttachment"), exports);
|
|
10
10
|
tslib_1.__exportStar(require("./models/IMailTemplate"), exports);
|
|
11
|
+
tslib_1.__exportStar(require("./models/IMailTemplateVariants"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./models/MailTemplateFormat"), exports);
|
|
11
13
|
tslib_1.__exportStar(require("./ports/IMailTemplateRepository"), exports);
|
|
12
14
|
tslib_1.__exportStar(require("./services/MailService"), exports);
|
|
13
15
|
tslib_1.__exportStar(require("./services/MailVerificationService"), exports);
|
|
@@ -37,17 +37,21 @@ export declare class MailService {
|
|
|
37
37
|
* Sends an email using a predefined template (HTML or text).
|
|
38
38
|
* Automatically uses the template format (either 'html' or 'txt') based on the environment variable.
|
|
39
39
|
*
|
|
40
|
+
* When no `subject` is provided in `mailOptions`, the subject is resolved from the
|
|
41
|
+
* stored template and interpolated with the given `templateParams` (industry-standard
|
|
42
|
+
* behavior matching Mailchimp/SendGrid/Postmark).
|
|
43
|
+
*
|
|
40
44
|
* @public
|
|
41
45
|
* @param mailOptions.from - Sender's email address (optional, defaults to config)
|
|
42
46
|
* @param mailOptions.to - Recipient's email address
|
|
43
|
-
* @param mailOptions.subject - Subject of the email
|
|
47
|
+
* @param mailOptions.subject - Subject of the email (optional — resolved from template if omitted)
|
|
44
48
|
* @param templateOptions.templateName - Name of the template to be used
|
|
45
|
-
* @param templateOptions.
|
|
49
|
+
* @param templateOptions.templateParams - Parameters to inject into the template
|
|
46
50
|
*/
|
|
47
51
|
sendTemplate(mailOptions: {
|
|
48
52
|
from?: string;
|
|
49
53
|
to: string;
|
|
50
|
-
subject
|
|
54
|
+
subject?: string;
|
|
51
55
|
}, templateOptions: {
|
|
52
56
|
templateName: string;
|
|
53
57
|
templateParams: Record<string, string | number | boolean | Date | undefined | null>;
|
|
@@ -62,17 +62,26 @@ let MailService = class MailService {
|
|
|
62
62
|
* Sends an email using a predefined template (HTML or text).
|
|
63
63
|
* Automatically uses the template format (either 'html' or 'txt') based on the environment variable.
|
|
64
64
|
*
|
|
65
|
+
* When no `subject` is provided in `mailOptions`, the subject is resolved from the
|
|
66
|
+
* stored template and interpolated with the given `templateParams` (industry-standard
|
|
67
|
+
* behavior matching Mailchimp/SendGrid/Postmark).
|
|
68
|
+
*
|
|
65
69
|
* @public
|
|
66
70
|
* @param mailOptions.from - Sender's email address (optional, defaults to config)
|
|
67
71
|
* @param mailOptions.to - Recipient's email address
|
|
68
|
-
* @param mailOptions.subject - Subject of the email
|
|
72
|
+
* @param mailOptions.subject - Subject of the email (optional — resolved from template if omitted)
|
|
69
73
|
* @param templateOptions.templateName - Name of the template to be used
|
|
70
|
-
* @param templateOptions.
|
|
74
|
+
* @param templateOptions.templateParams - Parameters to inject into the template
|
|
71
75
|
*/
|
|
72
76
|
async sendTemplate(mailOptions, templateOptions) {
|
|
73
77
|
const content = this._mailTemplateEngine.compileTemplate(templateOptions.templateName, templateOptions.templateParams, this._mailTemplateFormat);
|
|
74
78
|
const isHtml = this._mailTemplateFormat === 'html';
|
|
75
|
-
|
|
79
|
+
const subject = mailOptions.subject ?? this._mailTemplateEngine.resolveSubject(templateOptions.templateName, templateOptions.templateParams);
|
|
80
|
+
if (!subject) {
|
|
81
|
+
throw new Error(`No subject provided and template '${templateOptions.templateName}' has no stored subject. ` +
|
|
82
|
+
'Either pass a subject in mailOptions or ensure the template includes a subject.');
|
|
83
|
+
}
|
|
84
|
+
await this.send({ from: mailOptions.from, to: mailOptions.to, subject }, content, isHtml);
|
|
76
85
|
}
|
|
77
86
|
/**
|
|
78
87
|
* Performs a minimal email address validation. Not exhaustive but filters obvious mistakes.
|
|
@@ -97,9 +106,7 @@ let MailService = class MailService {
|
|
|
97
106
|
if (!IMailAttachment_1.ALLOWED_ATTACHMENT_MIME_TYPES.includes(attachment.contentType)) {
|
|
98
107
|
throw new Error(`Unsupported attachment MIME type: '${attachment.contentType}'`);
|
|
99
108
|
}
|
|
100
|
-
const size = typeof attachment.content === 'string'
|
|
101
|
-
? Buffer.byteLength(attachment.content, 'utf-8')
|
|
102
|
-
: attachment.content.length;
|
|
109
|
+
const size = typeof attachment.content === 'string' ? Buffer.byteLength(attachment.content, 'utf-8') : attachment.content.length;
|
|
103
110
|
if (size > IMailAttachment_1.MAX_ATTACHMENT_SIZE_BYTES) {
|
|
104
111
|
throw new Error(`Attachment '${attachment.filename}' exceeds maximum size of ${IMailAttachment_1.MAX_ATTACHMENT_SIZE_BYTES} bytes`);
|
|
105
112
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentTemplateEngine } from '@breadstone/archipel-platform-core';
|
|
2
2
|
import { OnModuleInit } from '@nestjs/common';
|
|
3
|
-
import { MailTemplateFormat } from '../
|
|
3
|
+
import type { MailTemplateFormat } from '../models/MailTemplateFormat';
|
|
4
4
|
import { TemplateFetchStrategyBase } from './strategies/abstracts/TemplateFetchStrategyBase';
|
|
5
5
|
/**
|
|
6
6
|
* The `MailTemplateEngine` class.
|
|
@@ -35,6 +35,15 @@ export declare class MailTemplateEngine implements OnModuleInit {
|
|
|
35
35
|
* @returns The compiled template as a string.
|
|
36
36
|
*/
|
|
37
37
|
compileTemplate(templateKey: string, params: Record<string, unknown>, format: MailTemplateFormat): string;
|
|
38
|
+
/**
|
|
39
|
+
* Resolves and interpolates the subject line from a stored template.
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
* @param templateKey The key of the template.
|
|
43
|
+
* @param params The parameters to insert into the subject.
|
|
44
|
+
* @returns The compiled subject string, or `undefined` if no subject is stored.
|
|
45
|
+
*/
|
|
46
|
+
resolveSubject(templateKey: string, params: Record<string, unknown>): string | undefined;
|
|
38
47
|
private escapeHtmlParams;
|
|
39
48
|
private escapeHtml;
|
|
40
49
|
}
|
|
@@ -66,6 +66,21 @@ let MailTemplateEngine = class MailTemplateEngine {
|
|
|
66
66
|
throw error;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolves and interpolates the subject line from a stored template.
|
|
71
|
+
*
|
|
72
|
+
* @public
|
|
73
|
+
* @param templateKey The key of the template.
|
|
74
|
+
* @param params The parameters to insert into the subject.
|
|
75
|
+
* @returns The compiled subject string, or `undefined` if no subject is stored.
|
|
76
|
+
*/
|
|
77
|
+
resolveSubject(templateKey, params) {
|
|
78
|
+
const templateVariants = this._templates[templateKey];
|
|
79
|
+
if (!templateVariants?.subject) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return this._contentTemplateEngine.compileTemplate(templateVariants.subject, params);
|
|
83
|
+
}
|
|
69
84
|
escapeHtmlParams(params) {
|
|
70
85
|
const escaped = {};
|
|
71
86
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -74,12 +89,7 @@ let MailTemplateEngine = class MailTemplateEngine {
|
|
|
74
89
|
return escaped;
|
|
75
90
|
}
|
|
76
91
|
escapeHtml(text) {
|
|
77
|
-
return text
|
|
78
|
-
.replace(/&/g, '&')
|
|
79
|
-
.replace(/</g, '<')
|
|
80
|
-
.replace(/>/g, '>')
|
|
81
|
-
.replace(/"/g, '"')
|
|
82
|
-
.replace(/'/g, ''');
|
|
92
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
83
93
|
}
|
|
84
94
|
};
|
|
85
95
|
exports.MailTemplateEngine = MailTemplateEngine;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IMailTemplateVariants } from '../../../models/IMailTemplateVariants';
|
|
1
2
|
/**
|
|
2
3
|
* Represents the base class for template fetch strategies.
|
|
3
4
|
*
|
|
@@ -17,8 +18,5 @@ export declare abstract class TemplateFetchStrategyBase {
|
|
|
17
18
|
* @public
|
|
18
19
|
* @abstract
|
|
19
20
|
*/
|
|
20
|
-
abstract load(): Promise<Record<string,
|
|
21
|
-
html?: string;
|
|
22
|
-
txt?: string;
|
|
23
|
-
}>>;
|
|
21
|
+
abstract load(): Promise<Record<string, IMailTemplateVariants>>;
|
|
24
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BlobService } from '@breadstone/archipel-platform-blob-storage';
|
|
2
|
-
import { IMailTemplateVariants } from '../../../
|
|
2
|
+
import type { IMailTemplateVariants } from '../../../models/IMailTemplateVariants';
|
|
3
3
|
import { TemplateFetchStrategyBase } from '../abstracts/TemplateFetchStrategyBase';
|
|
4
4
|
/**
|
|
5
5
|
* The `BlobTemplateFetchStrategy` class.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { IMailTemplateVariants } from '../../../models/IMailTemplateVariants';
|
|
1
2
|
import type { IMailTemplateRepository } from '../../../ports/IMailTemplateRepository';
|
|
2
|
-
import type { IMailTemplateVariants } from '../../../tokens/MailTokens';
|
|
3
3
|
import { TemplateFetchStrategyBase } from '../abstracts/TemplateFetchStrategyBase';
|
|
4
4
|
/**
|
|
5
5
|
* Template fetch strategy that loads templates from a database via the
|
|
@@ -44,6 +44,9 @@ let DatabaseTemplateFetchStrategy = DatabaseTemplateFetchStrategy_1 = class Data
|
|
|
44
44
|
templates[key] = {};
|
|
45
45
|
}
|
|
46
46
|
// Only set if not already filled (first match wins — default locale preferred)
|
|
47
|
+
if (template.subject && !templates[key].subject) {
|
|
48
|
+
templates[key].subject = template.subject;
|
|
49
|
+
}
|
|
47
50
|
if (template.htmlBody && !templates[key].html) {
|
|
48
51
|
templates[key].html = template.htmlBody;
|
|
49
52
|
}
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import { ResourceManager } from '@breadstone/archipel-platform-resources';
|
|
2
|
-
import { IMailTemplateVariants } from '../../../
|
|
2
|
+
import type { IMailTemplateVariants } from '../../../models/IMailTemplateVariants';
|
|
3
3
|
import { TemplateFetchStrategyBase } from '../abstracts/TemplateFetchStrategyBase';
|
|
4
4
|
/**
|
|
5
5
|
* The `FileTemplateFetchStrategy` class.
|
|
6
6
|
*
|
|
7
|
+
* Loads mail templates from the file system via `ResourceManager`.
|
|
8
|
+
* Template names are injected via `MAIL_TEMPLATE_NAMES_TOKEN` — defaults to an empty array.
|
|
9
|
+
*
|
|
7
10
|
* @public
|
|
8
11
|
*/
|
|
9
12
|
export declare class FileTemplateFetchStrategy extends TemplateFetchStrategyBase {
|
|
10
13
|
private readonly _resourceManager;
|
|
14
|
+
private readonly _templateNames;
|
|
11
15
|
private readonly _logger;
|
|
12
|
-
constructor(resourceManager: ResourceManager);
|
|
16
|
+
constructor(resourceManager: ResourceManager, templateNames: ReadonlyArray<string>);
|
|
13
17
|
/**
|
|
14
18
|
* Loads mail templates from the file system.
|
|
15
19
|
*
|
|
16
20
|
* @public
|
|
17
21
|
*/
|
|
18
22
|
load(): Promise<Record<string, IMailTemplateVariants>>;
|
|
19
|
-
/**
|
|
20
|
-
* Returns the list of known template names.
|
|
21
|
-
*
|
|
22
|
-
* @private
|
|
23
|
-
*/
|
|
24
|
-
private getTemplateNames;
|
|
25
23
|
}
|
|
@@ -6,20 +6,25 @@ exports.FileTemplateFetchStrategy = void 0;
|
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
7
|
const archipel_platform_resources_1 = require("@breadstone/archipel-platform-resources");
|
|
8
8
|
const common_1 = require("@nestjs/common");
|
|
9
|
+
const MailTokens_1 = require("../../../tokens/MailTokens");
|
|
9
10
|
const TemplateFetchStrategyBase_1 = require("../abstracts/TemplateFetchStrategyBase");
|
|
10
11
|
// #endregion
|
|
11
12
|
/**
|
|
12
13
|
* The `FileTemplateFetchStrategy` class.
|
|
13
14
|
*
|
|
15
|
+
* Loads mail templates from the file system via `ResourceManager`.
|
|
16
|
+
* Template names are injected via `MAIL_TEMPLATE_NAMES_TOKEN` — defaults to an empty array.
|
|
17
|
+
*
|
|
14
18
|
* @public
|
|
15
19
|
*/
|
|
16
20
|
let FileTemplateFetchStrategy = FileTemplateFetchStrategy_1 = class FileTemplateFetchStrategy extends TemplateFetchStrategyBase_1.TemplateFetchStrategyBase {
|
|
17
21
|
// #endregion
|
|
18
22
|
// #region Ctor
|
|
19
|
-
constructor(resourceManager) {
|
|
23
|
+
constructor(resourceManager, templateNames) {
|
|
20
24
|
super();
|
|
21
25
|
this._logger = new common_1.Logger(FileTemplateFetchStrategy_1.name);
|
|
22
26
|
this._resourceManager = resourceManager;
|
|
27
|
+
this._templateNames = templateNames;
|
|
23
28
|
}
|
|
24
29
|
// #endregion
|
|
25
30
|
// #region Methods
|
|
@@ -30,9 +35,8 @@ let FileTemplateFetchStrategy = FileTemplateFetchStrategy_1 = class FileTemplate
|
|
|
30
35
|
*/
|
|
31
36
|
async load() {
|
|
32
37
|
const templates = {};
|
|
33
|
-
const templateNames = this.getTemplateNames();
|
|
34
38
|
let templateCount = 0;
|
|
35
|
-
for (const templateName of
|
|
39
|
+
for (const templateName of this._templateNames) {
|
|
36
40
|
if (!templates[templateName]) {
|
|
37
41
|
templates[templateName] = {};
|
|
38
42
|
}
|
|
@@ -52,20 +56,11 @@ let FileTemplateFetchStrategy = FileTemplateFetchStrategy_1 = class FileTemplate
|
|
|
52
56
|
this._logger.log(`${templateCount} Templates loaded from file system`);
|
|
53
57
|
return templates;
|
|
54
58
|
}
|
|
55
|
-
// #endregion
|
|
56
|
-
// #region Private Methods
|
|
57
|
-
/**
|
|
58
|
-
* Returns the list of known template names.
|
|
59
|
-
*
|
|
60
|
-
* @private
|
|
61
|
-
*/
|
|
62
|
-
getTemplateNames() {
|
|
63
|
-
return ['AppointmentInvitation', 'AppointmentUpdate', 'AuthForgotPassword', 'AuthRegister', 'AuthVerify'];
|
|
64
|
-
}
|
|
65
59
|
};
|
|
66
60
|
exports.FileTemplateFetchStrategy = FileTemplateFetchStrategy;
|
|
67
61
|
exports.FileTemplateFetchStrategy = FileTemplateFetchStrategy = FileTemplateFetchStrategy_1 = tslib_1.__decorate([
|
|
68
62
|
(0, common_1.Injectable)(),
|
|
69
|
-
tslib_1.
|
|
63
|
+
tslib_1.__param(1, (0, common_1.Inject)(MailTokens_1.MAIL_TEMPLATE_NAMES_TOKEN)),
|
|
64
|
+
tslib_1.__metadata("design:paramtypes", [archipel_platform_resources_1.ResourceManager, Array])
|
|
70
65
|
], FileTemplateFetchStrategy);
|
|
71
66
|
//# sourceMappingURL=FileTemplateFetchStrategy.js.map
|
|
@@ -6,14 +6,4 @@
|
|
|
6
6
|
export declare const MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN: unique symbol;
|
|
7
7
|
export declare const MAIL_DELIVERY_STRATEGY_TOKEN: unique symbol;
|
|
8
8
|
export declare const MAIL_TEMPLATE_REPOSITORY_TOKEN: unique symbol;
|
|
9
|
-
|
|
10
|
-
* Union type of all supported template formats.
|
|
11
|
-
*/
|
|
12
|
-
export type MailTemplateFormat = 'html' | 'txt';
|
|
13
|
-
/**
|
|
14
|
-
* Represents the in-memory structure for a single template (multiple formats).
|
|
15
|
-
*/
|
|
16
|
-
export interface IMailTemplateVariants {
|
|
17
|
-
html?: string;
|
|
18
|
-
txt?: string;
|
|
19
|
-
}
|
|
9
|
+
export declare const MAIL_TEMPLATE_NAMES_TOKEN: unique symbol;
|
package/src/tokens/MailTokens.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
// #endregion
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.MAIL_TEMPLATE_REPOSITORY_TOKEN = exports.MAIL_DELIVERY_STRATEGY_TOKEN = exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = void 0;
|
|
10
|
+
exports.MAIL_TEMPLATE_NAMES_TOKEN = exports.MAIL_TEMPLATE_REPOSITORY_TOKEN = exports.MAIL_DELIVERY_STRATEGY_TOKEN = exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = void 0;
|
|
11
11
|
exports.MAIL_TEMPLATE_FETCH_STRATEGY_TOKEN = Symbol('MailTemplateFetchStrategy');
|
|
12
12
|
exports.MAIL_DELIVERY_STRATEGY_TOKEN = Symbol('MailDeliveryStrategy');
|
|
13
13
|
exports.MAIL_TEMPLATE_REPOSITORY_TOKEN = Symbol('MailTemplateRepository');
|
|
14
|
+
exports.MAIL_TEMPLATE_NAMES_TOKEN = Symbol('MailTemplateNames');
|
|
14
15
|
//# sourceMappingURL=MailTokens.js.map
|