@zyther/tzmail 0.1.3 → 0.5.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 +412 -79
- package/dist/core/enums/theme.enum.d.ts +11 -1
- package/dist/core/enums/theme.enum.d.ts.map +1 -1
- package/dist/core/enums/theme.enum.js +10 -0
- package/dist/core/interfaces/email.interface.d.ts +95 -5
- package/dist/core/interfaces/email.interface.d.ts.map +1 -1
- package/dist/core/interfaces/template.interface.d.ts +12 -1
- package/dist/core/interfaces/template.interface.d.ts.map +1 -1
- package/dist/factories/email-factory.d.ts +32 -6
- package/dist/factories/email-factory.d.ts.map +1 -1
- package/dist/factories/email-factory.js +110 -6
- package/dist/factories/template-factory.d.ts.map +1 -1
- package/dist/factories/template-factory.js +170 -60
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -1
- package/dist/providers/mailgun.transport.d.ts +10 -0
- package/dist/providers/mailgun.transport.d.ts.map +1 -0
- package/dist/providers/mailgun.transport.js +85 -0
- package/dist/providers/nodemailer.transport.d.ts +11 -0
- package/dist/providers/nodemailer.transport.d.ts.map +1 -0
- package/dist/providers/nodemailer.transport.js +23 -0
- package/dist/providers/provider-utils.d.ts +17 -0
- package/dist/providers/provider-utils.d.ts.map +1 -0
- package/dist/providers/provider-utils.js +89 -0
- package/dist/providers/provider.interface.d.ts +30 -0
- package/dist/providers/provider.interface.d.ts.map +1 -0
- package/dist/providers/provider.interface.js +2 -0
- package/dist/providers/sendgrid.transport.d.ts +10 -0
- package/dist/providers/sendgrid.transport.d.ts.map +1 -0
- package/dist/providers/sendgrid.transport.js +76 -0
- package/dist/queues/bullmq-email-queue.d.ts +153 -0
- package/dist/queues/bullmq-email-queue.d.ts.map +1 -0
- package/dist/queues/bullmq-email-queue.js +292 -0
- package/dist/services/attachment.service.d.ts +15 -37
- package/dist/services/attachment.service.d.ts.map +1 -1
- package/dist/services/attachment.service.js +58 -46
- package/dist/services/email.service.d.ts +22 -22
- package/dist/services/email.service.d.ts.map +1 -1
- package/dist/services/email.service.js +117 -33
- package/dist/services/template.service.d.ts +24 -111
- package/dist/services/template.service.d.ts.map +1 -1
- package/dist/services/template.service.js +273 -362
- package/dist/templates/base/color-accessibility.d.ts +4 -0
- package/dist/templates/base/color-accessibility.d.ts.map +1 -0
- package/dist/templates/base/color-accessibility.js +38 -0
- package/dist/templates/base/template-builder.d.ts +4 -1
- package/dist/templates/base/template-builder.d.ts.map +1 -1
- package/dist/templates/base/template-builder.js +276 -527
- package/dist/templates/themes/aurora.theme.d.ts +12 -0
- package/dist/templates/themes/aurora.theme.d.ts.map +1 -0
- package/dist/templates/themes/aurora.theme.js +88 -0
- package/dist/templates/themes/corporate.theme.d.ts +2 -1
- package/dist/templates/themes/corporate.theme.d.ts.map +1 -1
- package/dist/templates/themes/corporate.theme.js +139 -102
- package/dist/templates/themes/editorial.theme.d.ts +12 -0
- package/dist/templates/themes/editorial.theme.d.ts.map +1 -0
- package/dist/templates/themes/editorial.theme.js +90 -0
- package/dist/templates/themes/forest.theme.d.ts +12 -0
- package/dist/templates/themes/forest.theme.d.ts.map +1 -0
- package/dist/templates/themes/forest.theme.js +89 -0
- package/dist/templates/themes/glass.theme.d.ts +12 -0
- package/dist/templates/themes/glass.theme.d.ts.map +1 -0
- package/dist/templates/themes/glass.theme.js +88 -0
- package/dist/templates/themes/lavender.theme.d.ts +12 -0
- package/dist/templates/themes/lavender.theme.d.ts.map +1 -0
- package/dist/templates/themes/lavender.theme.js +91 -0
- package/dist/templates/themes/midnight.theme.d.ts +12 -0
- package/dist/templates/themes/midnight.theme.d.ts.map +1 -0
- package/dist/templates/themes/midnight.theme.js +91 -0
- package/dist/templates/themes/minimal.theme.d.ts +2 -1
- package/dist/templates/themes/minimal.theme.d.ts.map +1 -1
- package/dist/templates/themes/minimal.theme.js +142 -107
- package/dist/templates/themes/modern.theme.d.ts +2 -1
- package/dist/templates/themes/modern.theme.d.ts.map +1 -1
- package/dist/templates/themes/modern.theme.js +109 -74
- package/dist/templates/themes/monokai.theme.d.ts +2 -1
- package/dist/templates/themes/monokai.theme.d.ts.map +1 -1
- package/dist/templates/themes/monokai.theme.js +102 -66
- package/dist/templates/themes/neobrutal.theme.d.ts +12 -0
- package/dist/templates/themes/neobrutal.theme.d.ts.map +1 -0
- package/dist/templates/themes/neobrutal.theme.js +91 -0
- package/dist/templates/themes/ocean.theme.d.ts +12 -0
- package/dist/templates/themes/ocean.theme.d.ts.map +1 -0
- package/dist/templates/themes/ocean.theme.js +90 -0
- package/dist/templates/themes/pastel.theme.d.ts +12 -0
- package/dist/templates/themes/pastel.theme.d.ts.map +1 -0
- package/dist/templates/themes/pastel.theme.js +89 -0
- package/dist/templates/themes/sunset.theme.d.ts +12 -0
- package/dist/templates/themes/sunset.theme.d.ts.map +1 -0
- package/dist/templates/themes/sunset.theme.js +90 -0
- package/dist/templates/themes/system.theme.d.ts +2 -1
- package/dist/templates/themes/system.theme.d.ts.map +1 -1
- package/dist/templates/themes/system.theme.js +81 -47
- package/dist/templates/themes/theme.interface.d.ts +48 -0
- package/dist/templates/themes/theme.interface.d.ts.map +1 -1
- package/package.json +36 -14
- package/src/core/enums/template-part.enum.ts +7 -0
- package/src/core/enums/theme.enum.ts +18 -0
- package/src/core/interfaces/email.interface.ts +116 -0
- package/src/core/interfaces/template.interface.ts +71 -0
- package/src/factories/email-factory.ts +208 -0
- package/src/factories/template-factory.ts +201 -0
- package/src/index.ts +53 -0
- package/src/providers/mailgun.transport.ts +80 -0
- package/src/providers/nodemailer.transport.ts +20 -0
- package/src/providers/provider-utils.ts +91 -0
- package/src/providers/provider.interface.ts +26 -0
- package/src/providers/sendgrid.transport.ts +80 -0
- package/src/queues/bullmq-email-queue.ts +418 -0
- package/src/services/attachment.service.ts +98 -0
- package/src/services/email.service.ts +151 -0
- package/src/services/template.service.ts +431 -0
- package/src/templates/base/color-accessibility.ts +31 -0
- package/src/templates/base/template-builder.ts +438 -0
- package/src/templates/themes/aurora.theme.ts +91 -0
- package/src/templates/themes/corporate.theme.ts +156 -0
- package/src/templates/themes/editorial.theme.ts +93 -0
- package/src/templates/themes/forest.theme.ts +92 -0
- package/src/templates/themes/glass.theme.ts +91 -0
- package/src/templates/themes/lavender.theme.ts +94 -0
- package/src/templates/themes/midnight.theme.ts +94 -0
- package/src/templates/themes/minimal.theme.ts +158 -0
- package/src/templates/themes/modern.theme.ts +124 -0
- package/src/templates/themes/monokai.theme.ts +117 -0
- package/src/templates/themes/neobrutal.theme.ts +94 -0
- package/src/templates/themes/ocean.theme.ts +93 -0
- package/src/templates/themes/pastel.theme.ts +92 -0
- package/src/templates/themes/sunset.theme.ts +93 -0
- package/src/templates/themes/system.theme.ts +92 -0
- package/src/templates/themes/theme.interface.ts +95 -0
- package/src/tests/template.service.test.ts +1 -0
|
@@ -37,67 +37,79 @@ exports.AttachmentService = void 0;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* - addFromPath(filePath: string, options?: Partial<IAttachment>): Promise<IAttachment>
|
|
44
|
-
* Adiciona um anexo a partir de um caminho de arquivo.
|
|
45
|
-
*
|
|
46
|
-
* - addFromBuffer(buffer: Buffer, filename: string, options?: Partial<IAttachment>): Promise<IAttachment>
|
|
47
|
-
* Adiciona um anexo a partir de um buffer em memória.
|
|
48
|
-
*
|
|
49
|
-
* - addFromUrl(url: string, filename: string): Promise<IAttachment>
|
|
50
|
-
* Adiciona um anexo a partir de uma URL (ainda não implementado).
|
|
51
|
-
*
|
|
52
|
-
* Exemplo de uso:
|
|
53
|
-
*
|
|
54
|
-
* const attachmentService = new AttachmentService();
|
|
55
|
-
* const attachment = await attachmentService.addFromPath('path/to/file.pdf');
|
|
56
|
-
*
|
|
57
|
-
* emailFactory.sendEmail({
|
|
58
|
-
* to: 'user@example.com',
|
|
59
|
-
* subject: 'Assunto do Email',
|
|
60
|
-
* template: myTemplate,
|
|
61
|
-
* attachments: [{...attachment}]
|
|
62
|
-
* });
|
|
40
|
+
* Creates Nodemailer attachment descriptors. Path attachments remain paths so
|
|
41
|
+
* Nodemailer can stream large files instead of loading them into memory.
|
|
42
|
+
* If allowedRoot is omitted, callers are responsible for supplying trusted paths.
|
|
63
43
|
*/
|
|
64
44
|
class AttachmentService {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
45
|
+
options;
|
|
46
|
+
maxSizeBytes;
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
if (options.maxSizeBytes !== undefined &&
|
|
50
|
+
(!Number.isFinite(options.maxSizeBytes) || options.maxSizeBytes < 0)) {
|
|
51
|
+
throw new Error('maxSizeBytes must be a finite non-negative number');
|
|
52
|
+
}
|
|
53
|
+
this.maxSizeBytes = options.maxSizeBytes;
|
|
54
|
+
}
|
|
55
|
+
/** Add an attachment as a file path, preserving streaming behavior. */
|
|
72
56
|
async addFromPath(filePath, options) {
|
|
73
|
-
|
|
57
|
+
if (typeof filePath !== 'string' || filePath.trim() === '') {
|
|
58
|
+
throw new Error('filePath must be a non-empty string');
|
|
59
|
+
}
|
|
60
|
+
let attachmentPath = filePath;
|
|
61
|
+
let stats;
|
|
62
|
+
if (this.options.allowedRoot !== undefined) {
|
|
63
|
+
const [rootRealPath, fileRealPath] = await Promise.all([
|
|
64
|
+
fs.promises.realpath(this.options.allowedRoot),
|
|
65
|
+
fs.promises.realpath(filePath)
|
|
66
|
+
]);
|
|
67
|
+
const relativePath = path.relative(rootRealPath, fileRealPath);
|
|
68
|
+
if (relativePath === '..' || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
|
|
69
|
+
throw new Error('Attachment path is outside allowedRoot');
|
|
70
|
+
}
|
|
71
|
+
attachmentPath = fileRealPath;
|
|
72
|
+
stats = await fs.promises.stat(fileRealPath);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
stats = await fs.promises.stat(filePath);
|
|
76
|
+
}
|
|
74
77
|
if (!stats.isFile()) {
|
|
75
|
-
throw new Error(
|
|
78
|
+
throw new Error('Attachment path is not a file');
|
|
76
79
|
}
|
|
80
|
+
this.assertSize(stats.size);
|
|
77
81
|
return {
|
|
78
|
-
filename:
|
|
79
|
-
path:
|
|
80
|
-
contentType: options
|
|
81
|
-
cid: options
|
|
82
|
+
filename: options?.filename || path.basename(filePath),
|
|
83
|
+
path: attachmentPath,
|
|
84
|
+
contentType: options?.contentType,
|
|
85
|
+
cid: options?.cid
|
|
82
86
|
};
|
|
83
87
|
}
|
|
84
88
|
async addFromBuffer(buffer, filename, options) {
|
|
89
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
90
|
+
throw new Error('buffer must be a Buffer');
|
|
91
|
+
}
|
|
92
|
+
if (typeof filename !== 'string' || filename.trim() === '') {
|
|
93
|
+
throw new Error('filename must be a non-empty string');
|
|
94
|
+
}
|
|
95
|
+
this.assertSize(buffer.length);
|
|
85
96
|
return {
|
|
86
97
|
filename,
|
|
87
98
|
content: buffer,
|
|
88
|
-
contentType: options
|
|
89
|
-
cid: options
|
|
99
|
+
contentType: options?.contentType,
|
|
100
|
+
cid: options?.cid
|
|
90
101
|
};
|
|
91
102
|
}
|
|
92
|
-
/**
|
|
93
|
-
* addFromUrl - Adiciona um anexo a partir de uma URL (a ser implementado)
|
|
94
|
-
* @param url - URL do recurso a ser anexado
|
|
95
|
-
* @param filename - Nome do arquivo para o anexo
|
|
96
|
-
* @returns Promise<IAttachment> - Objeto de anexo formatado para Nodemailer
|
|
97
|
-
* @throws Error se o método não for implementado
|
|
98
|
-
*/
|
|
103
|
+
/** URL attachments are deliberately not fetched by this service. */
|
|
99
104
|
addFromUrl(url, filename) {
|
|
100
|
-
|
|
105
|
+
void url;
|
|
106
|
+
void filename;
|
|
107
|
+
throw new Error('addFromUrl is not supported; fetch and validate content in the application before adding an attachment');
|
|
108
|
+
}
|
|
109
|
+
assertSize(size) {
|
|
110
|
+
if (this.maxSizeBytes !== undefined && size > this.maxSizeBytes) {
|
|
111
|
+
throw new Error(`Attachment exceeds configured maxSizeBytes (${this.maxSizeBytes})`);
|
|
112
|
+
}
|
|
101
113
|
}
|
|
102
114
|
}
|
|
103
115
|
exports.AttachmentService = AttachmentService;
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { IEmailOptions, IEmailSendResult } from '../core/interfaces/email.interface';
|
|
2
|
+
import { IEmailTransport } from '../providers/provider.interface';
|
|
3
3
|
/**
|
|
4
|
-
* EmailService
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Envia um email com as opções especificadas.
|
|
9
|
-
*
|
|
10
|
-
* Exemplo de uso:
|
|
11
|
-
*
|
|
12
|
-
* const emailService = new EmailService(transporter, 'from@example.com');
|
|
13
|
-
* const result = await emailService.send({
|
|
14
|
-
* to: 'to@example.com',
|
|
15
|
-
* subject: 'Assunto do Email',
|
|
16
|
-
* template: myTemplate
|
|
17
|
-
* });
|
|
4
|
+
* EmailService sends messages through a provider-neutral transport.
|
|
5
|
+
* `send` always reports validation, render and transport failures in a typed
|
|
6
|
+
* result; callers should inspect `status` and `rejected` rather than treating
|
|
7
|
+
* any resolved promise as proof that every recipient received the message.
|
|
18
8
|
*/
|
|
19
9
|
export declare class EmailService {
|
|
20
10
|
private transporter;
|
|
21
11
|
private defaultFrom?;
|
|
22
|
-
|
|
12
|
+
private errorStage;
|
|
13
|
+
constructor(transporter: IEmailTransport, defaultFrom?: string | undefined, errorStage?: 'smtp' | 'provider');
|
|
23
14
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @throws Error se o envio falhar
|
|
15
|
+
* Validates and normalizes basic message fields, then sends the message.
|
|
16
|
+
* Header fields must be non-empty and contain no control characters; deeper
|
|
17
|
+
* address syntax and deliverability policy are delegated to the selected transport/provider.
|
|
28
18
|
*/
|
|
29
|
-
send(options: IEmailOptions): Promise<
|
|
19
|
+
send(options: IEmailOptions): Promise<IEmailSendResult>;
|
|
20
|
+
/** Verify SMTP configuration/connectivity without delivering a message. */
|
|
21
|
+
verify(): Promise<true>;
|
|
22
|
+
/** Close pooled connections during graceful application shutdown. */
|
|
23
|
+
close(): void;
|
|
24
|
+
private normalizeRecipients;
|
|
25
|
+
private normalizeOptionalRecipients;
|
|
26
|
+
private normalizeRequiredText;
|
|
27
|
+
private normalizeOptionalText;
|
|
28
|
+
private normalizeBody;
|
|
29
|
+
private recipientList;
|
|
30
30
|
}
|
|
31
31
|
//# sourceMappingURL=email.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.service.d.ts","sourceRoot":"","sources":["../../src/services/email.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"email.service.d.ts","sourceRoot":"","sources":["../../src/services/email.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,eAAe,EAAqB,MAAM,iCAAiC,CAAC;AAErF;;;;;GAKG;AACH,qBAAa,YAAY;IAErB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,WAAW,CAAC;IACpB,OAAO,CAAC,UAAU;gBAFV,WAAW,EAAE,eAAe,EAC5B,WAAW,CAAC,EAAE,MAAM,YAAA,EACpB,UAAU,GAAE,MAAM,GAAG,UAAmB;IAGlD;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkE7D,2EAA2E;IACrE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAO7B,qEAAqE;IACrE,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,2BAA2B;IAKnC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -2,57 +2,141 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.EmailService = void 0;
|
|
4
4
|
/**
|
|
5
|
-
* EmailService
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Envia um email com as opções especificadas.
|
|
10
|
-
*
|
|
11
|
-
* Exemplo de uso:
|
|
12
|
-
*
|
|
13
|
-
* const emailService = new EmailService(transporter, 'from@example.com');
|
|
14
|
-
* const result = await emailService.send({
|
|
15
|
-
* to: 'to@example.com',
|
|
16
|
-
* subject: 'Assunto do Email',
|
|
17
|
-
* template: myTemplate
|
|
18
|
-
* });
|
|
5
|
+
* EmailService sends messages through a provider-neutral transport.
|
|
6
|
+
* `send` always reports validation, render and transport failures in a typed
|
|
7
|
+
* result; callers should inspect `status` and `rejected` rather than treating
|
|
8
|
+
* any resolved promise as proof that every recipient received the message.
|
|
19
9
|
*/
|
|
20
10
|
class EmailService {
|
|
21
|
-
|
|
11
|
+
transporter;
|
|
12
|
+
defaultFrom;
|
|
13
|
+
errorStage;
|
|
14
|
+
constructor(transporter, defaultFrom, errorStage = 'smtp') {
|
|
22
15
|
this.transporter = transporter;
|
|
23
16
|
this.defaultFrom = defaultFrom;
|
|
17
|
+
this.errorStage = errorStage;
|
|
24
18
|
}
|
|
25
19
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @throws Error se o envio falhar
|
|
20
|
+
* Validates and normalizes basic message fields, then sends the message.
|
|
21
|
+
* Header fields must be non-empty and contain no control characters; deeper
|
|
22
|
+
* address syntax and deliverability policy are delegated to the selected transport/provider.
|
|
30
23
|
*/
|
|
31
24
|
async send(options) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
let mailOptions;
|
|
26
|
+
try {
|
|
27
|
+
mailOptions = {
|
|
28
|
+
from: this.normalizeOptionalText(options.from ?? this.defaultFrom, 'from'),
|
|
29
|
+
to: this.normalizeRecipients(options.to, 'to'),
|
|
30
|
+
cc: this.normalizeOptionalRecipients(options.cc, 'cc'),
|
|
31
|
+
bcc: this.normalizeOptionalRecipients(options.bcc, 'bcc'),
|
|
32
|
+
subject: this.normalizeRequiredText(options.subject, 'subject'),
|
|
33
|
+
text: this.normalizeBody(options.text, 'text'),
|
|
34
|
+
html: this.normalizeBody(options.html, 'html'),
|
|
35
|
+
attachments: options.attachments
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return { success: false, status: 'failure', error, errorStage: 'validation' };
|
|
40
|
+
}
|
|
41
|
+
if (mailOptions.html === undefined && options.template) {
|
|
42
|
+
try {
|
|
43
|
+
mailOptions.html = await options.template.render(options);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return { success: false, status: 'failure', error, errorStage: 'render' };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
try {
|
|
43
50
|
const info = await this.transporter.sendMail(mailOptions);
|
|
51
|
+
const accepted = this.recipientList(info?.accepted);
|
|
52
|
+
const rejected = this.recipientList(info?.rejected);
|
|
53
|
+
const status = rejected.length > 0
|
|
54
|
+
? (accepted.length > 0 ? 'partial' : 'failure')
|
|
55
|
+
: 'success';
|
|
44
56
|
return {
|
|
45
|
-
success:
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
success: status === 'success',
|
|
58
|
+
status,
|
|
59
|
+
messageId: info?.messageId,
|
|
60
|
+
response: info?.response,
|
|
61
|
+
...(info?.accepted !== undefined ? { accepted } : {}),
|
|
62
|
+
...(info?.rejected !== undefined ? { rejected } : {})
|
|
48
63
|
};
|
|
49
64
|
}
|
|
50
65
|
catch (error) {
|
|
66
|
+
const details = error;
|
|
67
|
+
const accepted = this.recipientList(details?.accepted);
|
|
68
|
+
const rejected = this.recipientList(details?.rejected);
|
|
69
|
+
const partial = accepted.length > 0 && rejected.length > 0;
|
|
51
70
|
return {
|
|
52
71
|
success: false,
|
|
53
|
-
|
|
72
|
+
status: partial ? 'partial' : 'failure',
|
|
73
|
+
error,
|
|
74
|
+
errorStage: this.errorStage,
|
|
75
|
+
...(details?.messageId !== undefined ? { messageId: details.messageId } : {}),
|
|
76
|
+
...(details?.response !== undefined ? { response: details.response } : {}),
|
|
77
|
+
...(details?.accepted !== undefined ? { accepted } : {}),
|
|
78
|
+
...(details?.rejected !== undefined ? { rejected } : {})
|
|
54
79
|
};
|
|
55
80
|
}
|
|
56
81
|
}
|
|
82
|
+
/** Verify SMTP configuration/connectivity without delivering a message. */
|
|
83
|
+
async verify() {
|
|
84
|
+
if (!this.transporter.verify) {
|
|
85
|
+
throw new Error('This API transport does not expose a non-delivery verification operation.');
|
|
86
|
+
}
|
|
87
|
+
return this.transporter.verify();
|
|
88
|
+
}
|
|
89
|
+
/** Close pooled connections during graceful application shutdown. */
|
|
90
|
+
close() {
|
|
91
|
+
this.transporter.close?.();
|
|
92
|
+
}
|
|
93
|
+
normalizeRecipients(value, field) {
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
if (value.length === 0) {
|
|
96
|
+
throw new Error(`${field} must contain at least one recipient`);
|
|
97
|
+
}
|
|
98
|
+
const recipients = value.map((entry) => this.normalizeRequiredText(entry, field));
|
|
99
|
+
return recipients.join(', ');
|
|
100
|
+
}
|
|
101
|
+
return this.normalizeRequiredText(value, field);
|
|
102
|
+
}
|
|
103
|
+
normalizeOptionalRecipients(value, field) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
return this.normalizeRecipients(value, field);
|
|
107
|
+
}
|
|
108
|
+
normalizeRequiredText(value, field) {
|
|
109
|
+
const normalized = this.normalizeOptionalText(value, field);
|
|
110
|
+
if (normalized === undefined) {
|
|
111
|
+
throw new Error(`${field} is required`);
|
|
112
|
+
}
|
|
113
|
+
return normalized;
|
|
114
|
+
}
|
|
115
|
+
normalizeOptionalText(value, field) {
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
if (typeof value !== 'string') {
|
|
119
|
+
throw new Error(`${field} must be a string`);
|
|
120
|
+
}
|
|
121
|
+
const normalized = value.trim();
|
|
122
|
+
if (!normalized) {
|
|
123
|
+
throw new Error(`${field} must not be empty`);
|
|
124
|
+
}
|
|
125
|
+
if (/[\u0000-\u001f\u007f]/.test(normalized)) {
|
|
126
|
+
throw new Error(`${field} must not contain header control characters`);
|
|
127
|
+
}
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
|
130
|
+
normalizeBody(value, field) {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
if (typeof value !== 'string') {
|
|
134
|
+
throw new Error(`${field} must be a string`);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
recipientList(value) {
|
|
139
|
+
return Array.isArray(value) ? value : [];
|
|
140
|
+
}
|
|
57
141
|
}
|
|
58
142
|
exports.EmailService = EmailService;
|
|
@@ -6,81 +6,41 @@ interface TemplateCache {
|
|
|
6
6
|
expiresAt: Date;
|
|
7
7
|
hits: number;
|
|
8
8
|
}
|
|
9
|
-
interface
|
|
9
|
+
export interface ITemplateServiceOptions {
|
|
10
|
+
/** Cache ligado por padrão; pode ser desligado por chamada. */
|
|
10
11
|
cache?: boolean;
|
|
12
|
+
/** TTL em segundos. Zero desabilita o cache para aquela criação; valores negativos/não finitos são inválidos. */
|
|
11
13
|
cacheTTL?: number;
|
|
12
14
|
validateConfig?: boolean;
|
|
15
|
+
/** Mantido por compatibilidade. A transformação destrutiva foi desativada. */
|
|
13
16
|
minify?: boolean;
|
|
14
17
|
preview?: boolean;
|
|
18
|
+
/** Limite de templates em cache; padrão finito de 100. */
|
|
19
|
+
maxCacheEntries?: number;
|
|
20
|
+
/** Limite de IDs de template guardados no histórico; padrão finito de 100. */
|
|
21
|
+
maxHistoryEntries?: number;
|
|
22
|
+
/** Máximo de versões por ID de template; padrão 10. */
|
|
23
|
+
maxVersionsPerTemplate?: number;
|
|
15
24
|
}
|
|
25
|
+
type TemplateOptions = ITemplateServiceOptions;
|
|
16
26
|
/**
|
|
17
|
-
* TemplateService -
|
|
18
|
-
*
|
|
19
|
-
* Métodos:
|
|
20
|
-
* - createTemplate(themeType, variant, config, options): ITemplate
|
|
21
|
-
* Cria um novo template com as configurações especificadas.
|
|
22
|
-
*
|
|
23
|
-
* - cloneTemplate(template, modifications): ITemplate
|
|
24
|
-
* Cria um template a partir de um template existente (clone).
|
|
25
|
-
*
|
|
26
|
-
* - renderTemplate(template, data, options): Promise<string>
|
|
27
|
-
* Renderiza um template com os dados fornecidos.
|
|
28
|
-
*
|
|
29
|
-
* - previewTemplate(themeType, variant, config, data): Promise<string>
|
|
30
|
-
* Pré-visualiza um template sem enviar email.
|
|
31
|
-
*
|
|
32
|
-
* - getThemeInfo(themeType): object
|
|
33
|
-
* Obtém informações sobre um tema específico.
|
|
34
|
-
*
|
|
35
|
-
* - listThemes(): ThemeType[]
|
|
36
|
-
* Lista todos os temas disponíveis.
|
|
37
|
-
*
|
|
38
|
-
* - getTemplateStats(): object
|
|
39
|
-
* Obtém estatísticas de uso dos templates.
|
|
40
|
-
*
|
|
41
|
-
* - clearCache(themeType?): number
|
|
42
|
-
* Limpa o cache de templates, opcionalmente por tema.
|
|
43
|
-
*
|
|
44
|
-
* - getTemplateHistory(templateId): ITemplate[]
|
|
45
|
-
* Obtém histórico de versões de um template.
|
|
46
|
-
*
|
|
47
|
-
* - restoreTemplateVersion(templateId, versionIndex): ITemplate | null
|
|
48
|
-
* Restaura uma versão anterior do template.
|
|
49
|
-
*
|
|
50
|
-
* Exemplo de uso:
|
|
51
|
-
*
|
|
52
|
-
* const templateService = new TemplateService();
|
|
53
|
-
* const myTemplate = templateService.createTemplate(ThemeType.MODERN, 'light', { header: { show: true } });
|
|
54
|
-
* const html = await templateService.renderTemplate(myTemplate, { username: 'John' });
|
|
55
|
-
* console.log(html);
|
|
27
|
+
* TemplateService - criação, renderização e gestão de templates de email.
|
|
28
|
+
* Cache e histórico mantêm limites finitos e retornam snapshots defensivos.
|
|
56
29
|
*/
|
|
57
30
|
export declare class TemplateService {
|
|
58
|
-
private options;
|
|
59
31
|
private templateCache;
|
|
60
|
-
private defaultTTL;
|
|
61
32
|
private templateHistory;
|
|
33
|
+
private cacheRequests;
|
|
34
|
+
private cacheHits;
|
|
35
|
+
private readonly options;
|
|
36
|
+
private cleanupTimer;
|
|
62
37
|
constructor(options?: TemplateOptions);
|
|
63
|
-
/**
|
|
64
|
-
* Cria um novo template com as configurações especificadas
|
|
65
|
-
*/
|
|
66
38
|
createTemplate(themeType: ThemeType, variant: 'light' | 'dark', config: ITemplateConfig, options?: TemplateOptions): ITemplate;
|
|
67
|
-
/**
|
|
68
|
-
* Cria um template a partir de um template existente (clone)
|
|
69
|
-
*/
|
|
70
39
|
cloneTemplate(template: ITemplate, modifications?: Partial<ITemplateConfig>): ITemplate;
|
|
71
|
-
/**
|
|
72
|
-
* Renderiza um template com os dados fornecidos
|
|
73
|
-
*/
|
|
74
40
|
renderTemplate(template: ITemplate, data: any, options?: {
|
|
75
41
|
preview?: boolean;
|
|
76
42
|
}): Promise<string>;
|
|
77
|
-
/**
|
|
78
|
-
* Pré-visualiza um template sem enviar email
|
|
79
|
-
*/
|
|
80
43
|
previewTemplate(themeType: ThemeType, variant: 'light' | 'dark', config: ITemplateConfig, data: any): Promise<string>;
|
|
81
|
-
/**
|
|
82
|
-
* Obtém informações sobre um tema específico
|
|
83
|
-
*/
|
|
84
44
|
getThemeInfo(themeType: ThemeType): {
|
|
85
45
|
name: string;
|
|
86
46
|
description: string;
|
|
@@ -88,87 +48,40 @@ export declare class TemplateService {
|
|
|
88
48
|
availableVariants: ('light' | 'dark')[];
|
|
89
49
|
defaultConfig: Partial<ITemplateConfig>;
|
|
90
50
|
};
|
|
91
|
-
/**
|
|
92
|
-
* Lista todos os temas disponíveis
|
|
93
|
-
*/
|
|
94
51
|
listThemes(): ThemeType[];
|
|
95
|
-
/**
|
|
96
|
-
* Obtém estatísticas de uso dos templates
|
|
97
|
-
*/
|
|
98
52
|
getTemplateStats(): {
|
|
99
53
|
totalTemplates: number;
|
|
100
54
|
cachedTemplates: number;
|
|
101
55
|
totalHits: number;
|
|
56
|
+
cacheRequests: number;
|
|
102
57
|
templatesByTheme: Record<ThemeType, number>;
|
|
103
58
|
cacheHitRate: number;
|
|
104
59
|
};
|
|
105
|
-
/**
|
|
106
|
-
* Limpa o cache de templates
|
|
107
|
-
*/
|
|
108
60
|
clearCache(themeType?: ThemeType): number;
|
|
109
|
-
/**
|
|
110
|
-
* Obtém histórico de versões de um template
|
|
111
|
-
*/
|
|
112
61
|
getTemplateHistory(templateId: string): ITemplate[];
|
|
113
|
-
/**
|
|
114
|
-
* Restaura uma versão anterior do template
|
|
115
|
-
*/
|
|
116
62
|
restoreTemplateVersion(templateId: string, versionIndex: number): ITemplate | null;
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
*/
|
|
63
|
+
/** Encerrar o timer de limpeza quando a instância não for mais necessária. */
|
|
64
|
+
dispose(): void;
|
|
120
65
|
private validateTemplateConfig;
|
|
121
|
-
|
|
122
|
-
* Gera um ID único para o template baseado na configuração
|
|
123
|
-
*/
|
|
66
|
+
private assertThemeAndVariant;
|
|
124
67
|
private generateTemplateId;
|
|
125
|
-
/**
|
|
126
|
-
* Adiciona funcionalidades extras ao template
|
|
127
|
-
*/
|
|
128
68
|
private enhanceTemplate;
|
|
129
|
-
|
|
130
|
-
* Adiciona template ao histórico
|
|
131
|
-
*/
|
|
69
|
+
private copyTemplate;
|
|
132
70
|
private addToHistory;
|
|
133
|
-
|
|
134
|
-
* Remove templates expirados do cache
|
|
135
|
-
*/
|
|
71
|
+
private trimCache;
|
|
136
72
|
private cleanExpiredCache;
|
|
137
|
-
/**
|
|
138
|
-
* Minifica o HTML para reduzir tamanho
|
|
139
|
-
*/
|
|
140
73
|
private minifyHtml;
|
|
141
|
-
|
|
142
|
-
* Obtém configuração padrão para um tema
|
|
143
|
-
*/
|
|
74
|
+
private validateLimit;
|
|
144
75
|
private getDefaultConfigForTheme;
|
|
145
|
-
/**
|
|
146
|
-
* Pré-carrega templates no cache
|
|
147
|
-
*/
|
|
148
76
|
preloadTemplates(templates: Array<{
|
|
149
77
|
themeType: ThemeType;
|
|
150
78
|
variant: 'light' | 'dark';
|
|
151
79
|
config: ITemplateConfig;
|
|
152
80
|
}>): Promise<void>;
|
|
153
|
-
/**
|
|
154
|
-
* Exporta template para formato JSON
|
|
155
|
-
*/
|
|
156
81
|
exportTemplate(template: ITemplate): string;
|
|
157
|
-
/**
|
|
158
|
-
* Importa template de formato JSON
|
|
159
|
-
*/
|
|
160
82
|
importTemplate(jsonData: string): ITemplate;
|
|
161
|
-
/**
|
|
162
|
-
* Gera um template de exemplo para demonstração
|
|
163
|
-
*/
|
|
164
83
|
generateExampleTemplate(themeType?: ThemeType, variant?: 'light' | 'dark'): ITemplate;
|
|
165
|
-
/**
|
|
166
|
-
* Valida se um template é válido
|
|
167
|
-
*/
|
|
168
84
|
isValidTemplate(template: any): boolean;
|
|
169
|
-
/**
|
|
170
|
-
* Obtém estatísticas detalhadas de um template específico
|
|
171
|
-
*/
|
|
172
85
|
getTemplateDetails(templateId: string): {
|
|
173
86
|
template: ITemplate | null;
|
|
174
87
|
history: ITemplate[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.service.d.ts","sourceRoot":"","sources":["../../src/services/template.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAEnF,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"template.service.d.ts","sourceRoot":"","sources":["../../src/services/template.service.ts"],"names":[],"mappings":"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"}
|