ts-mailkit 2.0.0 → 2.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 +114 -63
- package/dist/core/bulk-types.d.ts +196 -0
- package/dist/core/bulk-types.d.ts.map +1 -0
- package/dist/core/client.d.ts +38 -6
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.cjs +926 -754
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +19 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +904 -709
- package/dist/index.js.map +1 -1
- package/dist/renderers/default/default-renderer.d.ts +2 -2
- package/dist/renderers/default/default-renderer.d.ts.map +1 -1
- package/dist/renderers/renderer.interface.d.ts +1 -1
- package/dist/templates/factory.d.ts +1 -1
- package/dist/templates/factory.d.ts.map +1 -1
- package/dist/templates/template-builder.d.ts +36 -0
- package/dist/templates/template-builder.d.ts.map +1 -0
- package/dist/transports/console/console-sender.d.ts +7 -2
- package/dist/transports/console/console-sender.d.ts.map +1 -1
- package/dist/transports/cpaas/cpaas-sender.d.ts +51 -0
- package/dist/transports/cpaas/cpaas-sender.d.ts.map +1 -0
- package/dist/transports/cpaas/types.d.ts +133 -0
- package/dist/transports/cpaas/types.d.ts.map +1 -0
- package/dist/transports/memory/memory-sender.d.ts +26 -6
- package/dist/transports/memory/memory-sender.d.ts.map +1 -1
- package/dist/transports/sender.interface.d.ts +34 -3
- package/dist/transports/sender.interface.d.ts.map +1 -1
- package/dist/transports/zeptomail/types.d.ts +5 -46
- package/dist/transports/zeptomail/types.d.ts.map +1 -1
- package/dist/transports/zeptomail/zeptomail-sender.d.ts +4 -19
- package/dist/transports/zeptomail/zeptomail-sender.d.ts.map +1 -1
- package/package.json +40 -38
package/dist/index.cjs
CHANGED
|
@@ -1,802 +1,974 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
ConfigurationError: () => ConfigurationError,
|
|
24
|
-
ConsoleEmailSender: () => ConsoleEmailSender,
|
|
25
|
-
DefaultEmailRenderer: () => DefaultEmailRenderer,
|
|
26
|
-
EmailTemplateFactory: () => EmailTemplateFactory,
|
|
27
|
-
MailKit: () => MailKit,
|
|
28
|
-
MailKitError: () => MailKitError,
|
|
29
|
-
MemoryEmailSender: () => MemoryEmailSender,
|
|
30
|
-
TemplateError: () => TemplateError,
|
|
31
|
-
TransportError: () => TransportError,
|
|
32
|
-
ZeptoMailHttpSender: () => ZeptoMailHttpSender,
|
|
33
|
-
buildHtml: () => buildHtml,
|
|
34
|
-
buildPlainText: () => buildPlainText,
|
|
35
|
-
createMailKit: () => createMailKit,
|
|
36
|
-
decodeHtmlEntities: () => decodeHtmlEntities,
|
|
37
|
-
htmlToPlainText: () => htmlToPlainText,
|
|
38
|
-
isValidHttpUrl: () => isValidHttpUrl,
|
|
39
|
-
normalizeRecipient: () => normalizeRecipient,
|
|
40
|
-
normalizeRecipients: () => normalizeRecipients,
|
|
41
|
-
parseEmailAddress: () => parseEmailAddress
|
|
42
|
-
});
|
|
43
|
-
module.exports = __toCommonJS(index_exports);
|
|
44
|
-
|
|
45
|
-
// src/core/types.ts
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region src/core/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Parses an RFC 5322 formatted address string into an EmailAddress.
|
|
5
|
+
* Supports formats such as:
|
|
6
|
+
* - "user@example.com" -> { address: "user@example.com" }
|
|
7
|
+
* - "Sender Name <sender@example.com>" -> { name: "Sender Name", address: "sender@example.com" }
|
|
8
|
+
* - "\"Sender Name\" <sender@example.com>" -> { name: "Sender Name", address: "sender@example.com" }
|
|
9
|
+
*/
|
|
46
10
|
function parseEmailAddress(input) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
address: match[2].trim(),
|
|
57
|
-
name: match[1]?.trim() || void 0
|
|
58
|
-
};
|
|
11
|
+
if (!input || typeof input !== "string") return { address: "" };
|
|
12
|
+
const trimmed = input.trim();
|
|
13
|
+
const match = trimmed.match(/^(?:["']?([^"']+)["']?\s+)?<?([^\s<>@]+@[^\s<>@]+)>?$/);
|
|
14
|
+
if (!match) return { address: trimmed };
|
|
15
|
+
return {
|
|
16
|
+
address: match[2].trim(),
|
|
17
|
+
name: match[1]?.trim() || void 0
|
|
18
|
+
};
|
|
59
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes a Recipient value into a standardized EmailAddress.
|
|
22
|
+
*/
|
|
60
23
|
function normalizeRecipient(recipient) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
address: (recipient.address ?? "").trim(),
|
|
72
|
-
name: recipient.name?.trim()
|
|
73
|
-
};
|
|
24
|
+
if (typeof recipient === "string") return { address: recipient.trim() };
|
|
25
|
+
if ("email" in recipient) return {
|
|
26
|
+
address: (recipient.email ?? "").trim(),
|
|
27
|
+
name: recipient.name?.trim()
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
address: (recipient.address ?? "").trim(),
|
|
31
|
+
name: recipient.name?.trim()
|
|
32
|
+
};
|
|
74
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Normalizes a single or array of Recipients into an array of EmailAddress.
|
|
36
|
+
*/
|
|
75
37
|
function normalizeRecipients(recipients) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return list.map(normalizeRecipient).filter((r) => r.address.length > 0);
|
|
38
|
+
if (!recipients) return [];
|
|
39
|
+
return (Array.isArray(recipients) ? recipients : [recipients]).map(normalizeRecipient).filter((r) => r.address.length > 0);
|
|
79
40
|
}
|
|
80
41
|
|
|
81
|
-
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/core/errors.ts
|
|
44
|
+
/**
|
|
45
|
+
* Base error class for all ts-mailkit errors.
|
|
46
|
+
*/
|
|
82
47
|
var MailKitError = class extends Error {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "MailKitError";
|
|
51
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
52
|
+
}
|
|
88
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when transport configuration is missing or invalid.
|
|
56
|
+
*/
|
|
89
57
|
var ConfigurationError = class extends MailKitError {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "ConfigurationError";
|
|
61
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
62
|
+
}
|
|
95
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when an email transport fails to dispatch an email.
|
|
66
|
+
*/
|
|
96
67
|
var TransportError = class extends MailKitError {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
68
|
+
transportName;
|
|
69
|
+
statusCode;
|
|
70
|
+
responseBody;
|
|
71
|
+
/** Parsed raw provider response, when available. */
|
|
72
|
+
rawResponse;
|
|
73
|
+
cause;
|
|
74
|
+
constructor(message, options) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = "TransportError";
|
|
77
|
+
this.transportName = options.transportName;
|
|
78
|
+
this.statusCode = options.statusCode;
|
|
79
|
+
this.responseBody = options.responseBody;
|
|
80
|
+
this.rawResponse = options.rawResponse;
|
|
81
|
+
this.cause = options.cause;
|
|
82
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
83
|
+
}
|
|
110
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Thrown when template rendering or DTO validation fails.
|
|
87
|
+
*/
|
|
111
88
|
var TemplateError = class extends MailKitError {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
constructor(message) {
|
|
90
|
+
super(message);
|
|
91
|
+
this.name = "TemplateError";
|
|
92
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
93
|
+
}
|
|
117
94
|
};
|
|
118
95
|
|
|
119
|
-
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/renderers/utils/html-to-plaintext.ts
|
|
98
|
+
/**
|
|
99
|
+
* Decodes common HTML entities into their plain text equivalents.
|
|
100
|
+
*/
|
|
120
101
|
function decodeHtmlEntities(text) {
|
|
121
|
-
|
|
102
|
+
return text.replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, "\"").replace(/'/gi, "'").replace(/©/gi, "©").replace(/®/gi, "®").replace(/™/gi, "™");
|
|
122
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Strips HTML tags and normalizes whitespace into readable plain text.
|
|
106
|
+
* Converts <br>, <p>, and <div> into appropriate line breaks.
|
|
107
|
+
*/
|
|
123
108
|
function htmlToPlainText(html) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<\/h[1-6]>/gi, "\n\n").replace(/<[^>]*>/g, "")
|
|
127
|
-
).replace(/[ \t]+/g, " ").replace(/\n\s*\n\s*\n+/g, "\n\n").trim();
|
|
109
|
+
if (!html) return "";
|
|
110
|
+
return decodeHtmlEntities(html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<\/h[1-6]>/gi, "\n\n").replace(/<[^>]*>/g, "")).replace(/[ \t]+/g, " ").replace(/\n\s*\n\s*\n+/g, "\n\n").trim();
|
|
128
111
|
}
|
|
129
112
|
|
|
130
|
-
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/renderers/default/default-renderer.ts
|
|
115
|
+
/**
|
|
116
|
+
* Validates whether a string is a valid HTTP or HTTPS URL.
|
|
117
|
+
*/
|
|
131
118
|
function isValidHttpUrl(urlString) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
119
|
+
if (!urlString || typeof urlString !== "string") return false;
|
|
120
|
+
try {
|
|
121
|
+
const url = new URL(urlString.trim());
|
|
122
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
139
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Builds a plain-text fallback string from an email DTO.
|
|
129
|
+
*/
|
|
140
130
|
function buildPlainText(dto) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
if (dto.otpCode) {
|
|
153
|
-
parts.push(`Verification Code: ${dto.otpCode}`);
|
|
154
|
-
}
|
|
155
|
-
if (dto.actionText && dto.actionUrl) {
|
|
156
|
-
parts.push(`${dto.actionText}: ${dto.actionUrl}`);
|
|
157
|
-
}
|
|
158
|
-
if (dto.extraMessage) {
|
|
159
|
-
parts.push(htmlToPlainText(dto.extraMessage));
|
|
160
|
-
}
|
|
161
|
-
if (dto.footer) {
|
|
162
|
-
parts.push(`${divider}
|
|
163
|
-
${htmlToPlainText(dto.footer)}`);
|
|
164
|
-
}
|
|
165
|
-
return parts.join("\n\n");
|
|
131
|
+
if (dto.noHtmlMessage) return dto.noHtmlMessage;
|
|
132
|
+
const divider = "──────────────────────────────";
|
|
133
|
+
const parts = [];
|
|
134
|
+
if (dto.header) parts.push(dto.header, divider);
|
|
135
|
+
if (dto.body) parts.push(htmlToPlainText(dto.body));
|
|
136
|
+
if (dto.otpCode) parts.push(`Verification Code: ${dto.otpCode}`);
|
|
137
|
+
if (dto.actionText && dto.actionUrl) parts.push(`${dto.actionText}: ${dto.actionUrl}`);
|
|
138
|
+
if (dto.extraMessage) parts.push(htmlToPlainText(dto.extraMessage));
|
|
139
|
+
if (dto.footer) parts.push(`${divider}\n${htmlToPlainText(dto.footer)}`);
|
|
140
|
+
return parts.join("\n\n");
|
|
166
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Builds a responsive, styled HTML email document from an email DTO.
|
|
144
|
+
*/
|
|
167
145
|
function buildHtml(dto, options = {}) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
if (dto.header) {
|
|
199
|
-
parts.push(
|
|
200
|
-
`<h2 style="margin:0 0 16px;font-size:1.5rem;font-weight:700;color:#111827;letter-spacing:-0.025em;">${dto.header}</h2>`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
if (dto.body) {
|
|
204
|
-
parts.push(`<div style="margin:0 0 20px;font-size:1rem;color:#374151;">${dto.body}</div>`);
|
|
205
|
-
}
|
|
206
|
-
if (dto.otpCode) {
|
|
207
|
-
parts.push(
|
|
208
|
-
`<div style="margin:24px 0;padding:20px;background-color:#f3f4f6;border-radius:8px;text-align:center;">`,
|
|
209
|
-
` <span style="font-size:2rem;letter-spacing:0.5rem;font-weight:700;color:#111827;font-family:monospace;">${dto.otpCode}</span>`,
|
|
210
|
-
`</div>`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
if (dto.actionUrl && dto.actionText) {
|
|
214
|
-
parts.push(
|
|
215
|
-
`<div style="margin:24px 0;text-align:center;">`,
|
|
216
|
-
` <a href="${dto.actionUrl}" style="display:inline-block;padding:12px 28px;background-color:${primaryColor};color:#ffffff;border-radius:6px;text-decoration:none;font-weight:600;font-size:1rem;text-align:center;box-sizing:border-box;">${dto.actionText}</a>`,
|
|
217
|
-
`</div>`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (dto.extraMessage) {
|
|
221
|
-
parts.push(
|
|
222
|
-
`<div style="margin:20px 0 0;font-size:0.875rem;color:#6b7280;line-height:1.5;">${dto.extraMessage}</div>`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
if (dto.footer) {
|
|
226
|
-
parts.push(
|
|
227
|
-
`<div style="margin-top:32px;padding-top:20px;border-top:1px solid #e5e7eb;font-size:0.75rem;color:#9ca3af;text-align:center;">${dto.footer}</div>`
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
parts.push(`</div>`, `</body>`, `</html>`);
|
|
231
|
-
return parts.join("\n");
|
|
146
|
+
const primaryColor = options.primaryColor || "#4f46e5";
|
|
147
|
+
const fontFamily = options.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
148
|
+
const maxWidth = options.maxWidth || "600px";
|
|
149
|
+
const parts = [
|
|
150
|
+
`<!DOCTYPE html>`,
|
|
151
|
+
`<html lang="en">`,
|
|
152
|
+
`<head>`,
|
|
153
|
+
`<meta charset="UTF-8">`,
|
|
154
|
+
`<meta name="viewport" content="width=device-width, initial-scale=1.0">`,
|
|
155
|
+
`<title>${dto.subject}</title>`,
|
|
156
|
+
`</head>`,
|
|
157
|
+
`<body style="margin:0;padding:24px 16px;background-color:#f9fafb;font-family:${fontFamily};color:#1f2937;line-height:1.6;">`,
|
|
158
|
+
`<div style="max-width:${maxWidth};margin:0 auto;background-color:#ffffff;padding:32px 28px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.1);border:1px solid #f3f4f6;">`
|
|
159
|
+
];
|
|
160
|
+
const logoCandidate = dto.logoUrl ?? options.logoUrl;
|
|
161
|
+
if (logoCandidate && isValidHttpUrl(logoCandidate)) {
|
|
162
|
+
const validLogo = logoCandidate.trim();
|
|
163
|
+
parts.push(`<div style="margin-bottom:24px;text-align:center;">`, ` <img src="${validLogo}" alt="${dto.fromName || "Logo"}" style="max-height:48px;max-width:200px;height:auto;width:auto;display:inline-block;border:0;outline:none;text-decoration:none;" />`, `</div>`);
|
|
164
|
+
}
|
|
165
|
+
if (dto.imageUrl) parts.push(`<div style="margin-bottom:24px;text-align:center;">`, ` <img src="${dto.imageUrl}" alt="Header" style="max-width:100%;height:auto;border-radius:6px;" />`, `</div>`);
|
|
166
|
+
if (dto.header) parts.push(`<h2 style="margin:0 0 16px;font-size:1.5rem;font-weight:700;color:#111827;letter-spacing:-0.025em;">${dto.header}</h2>`);
|
|
167
|
+
if (dto.body) parts.push(`<div style="margin:0 0 20px;font-size:1rem;color:#374151;">${dto.body}</div>`);
|
|
168
|
+
if (dto.otpCode) parts.push(`<div style="margin:24px 0;padding:20px;background-color:#f3f4f6;border-radius:8px;text-align:center;">`, ` <span style="font-size:2rem;letter-spacing:0.5rem;font-weight:700;color:#111827;font-family:monospace;">${dto.otpCode}</span>`, `</div>`);
|
|
169
|
+
if (dto.actionUrl && dto.actionText) parts.push(`<div style="margin:24px 0;text-align:center;">`, ` <a href="${dto.actionUrl}" style="display:inline-block;padding:12px 28px;background-color:${primaryColor};color:#ffffff;border-radius:6px;text-decoration:none;font-weight:600;font-size:1rem;text-align:center;box-sizing:border-box;">${dto.actionText}</a>`, `</div>`);
|
|
170
|
+
if (dto.extraMessage) parts.push(`<div style="margin:20px 0 0;font-size:0.875rem;color:#6b7280;line-height:1.5;">${dto.extraMessage}</div>`);
|
|
171
|
+
if (dto.footer) parts.push(`<div style="margin-top:32px;padding-top:20px;border-top:1px solid #e5e7eb;font-size:0.75rem;color:#9ca3af;text-align:center;">${dto.footer}</div>`);
|
|
172
|
+
parts.push(`</div>`, `</body>`, `</html>`);
|
|
173
|
+
return parts.join("\n");
|
|
232
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Default HTML and plain-text email renderer for ts-mailkit.
|
|
177
|
+
*/
|
|
233
178
|
var DefaultEmailRenderer = class {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
179
|
+
options;
|
|
180
|
+
constructor(options = {}) {
|
|
181
|
+
this.options = options;
|
|
182
|
+
}
|
|
183
|
+
render(dto) {
|
|
184
|
+
return {
|
|
185
|
+
html: buildHtml(dto, this.options),
|
|
186
|
+
text: buildPlainText(dto)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
244
189
|
};
|
|
245
190
|
|
|
246
|
-
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/core/client.ts
|
|
193
|
+
/**
|
|
194
|
+
* Main email client and orchestration service for ts-mailkit.
|
|
195
|
+
*/
|
|
247
196
|
var MailKit = class {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
197
|
+
transport;
|
|
198
|
+
renderer;
|
|
199
|
+
defaultFrom;
|
|
200
|
+
defaultBrandName;
|
|
201
|
+
brandNameResolver;
|
|
202
|
+
constructor(options) {
|
|
203
|
+
if (!options.transport) throw new ConfigurationError("MailKit: A transport adapter (IEmailSender) is required.");
|
|
204
|
+
this.transport = options.transport;
|
|
205
|
+
this.renderer = options.renderer ?? new DefaultEmailRenderer();
|
|
206
|
+
this.defaultFrom = options.defaultFrom;
|
|
207
|
+
this.defaultBrandName = options.defaultBrandName;
|
|
208
|
+
this.brandNameResolver = options.brandNameResolver;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Returns the underlying transport instance.
|
|
212
|
+
*/
|
|
213
|
+
getTransport() {
|
|
214
|
+
return this.transport;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Renders an email DTO to HTML and plain text without dispatching.
|
|
218
|
+
*/
|
|
219
|
+
async render(template) {
|
|
220
|
+
return await this.renderer.render(template);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Sends an email using a strongly-typed template DTO.
|
|
224
|
+
*
|
|
225
|
+
* Example:
|
|
226
|
+
* ```ts
|
|
227
|
+
* await mailer.sendMail({
|
|
228
|
+
* to: 'user@example.com',
|
|
229
|
+
* template: myDto,
|
|
230
|
+
* });
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
async sendMail(options) {
|
|
234
|
+
const template = options.template;
|
|
235
|
+
const recipientName = options.toName ?? template.userName;
|
|
236
|
+
const resolvedFromName = await this.resolveFromName(template, options.fromName);
|
|
237
|
+
const rendered = await this.renderer.render(template);
|
|
238
|
+
const plaintextBody = template.noHtmlMessage ?? rendered.text;
|
|
239
|
+
let toRecipients;
|
|
240
|
+
if (typeof options.to === "string" && recipientName) toRecipients = {
|
|
241
|
+
address: options.to,
|
|
242
|
+
name: recipientName
|
|
243
|
+
};
|
|
244
|
+
else toRecipients = options.to;
|
|
245
|
+
const from = this.applyFromName(options.from ?? this.defaultFrom, resolvedFromName);
|
|
246
|
+
return await this.sendRaw({
|
|
247
|
+
to: toRecipients,
|
|
248
|
+
from,
|
|
249
|
+
subject: template.subject,
|
|
250
|
+
html: rendered.html,
|
|
251
|
+
text: plaintextBody,
|
|
252
|
+
cc: options.cc,
|
|
253
|
+
bcc: options.bcc,
|
|
254
|
+
replyTo: options.replyTo,
|
|
255
|
+
attachments: options.attachments,
|
|
256
|
+
headers: options.headers,
|
|
257
|
+
tags: options.tags,
|
|
258
|
+
metadata: options.metadata
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* @deprecated Use {@link sendMail} instead.
|
|
263
|
+
*/
|
|
264
|
+
async send(options) {
|
|
265
|
+
return await this.sendMail(options);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Sends a bulk email using a strongly-typed template DTO.
|
|
269
|
+
*
|
|
270
|
+
* The transport delivers an individual copy to each recipient, so
|
|
271
|
+
* recipients are not exposed to one another. Per-recipient personalization
|
|
272
|
+
* is available via {@link sendBulkKnownTemplate}.
|
|
273
|
+
*
|
|
274
|
+
* Example:
|
|
275
|
+
* ```ts
|
|
276
|
+
* await mailer.sendBulkMail({
|
|
277
|
+
* to: ['a@example.com', 'b@example.com'],
|
|
278
|
+
* template: myDto,
|
|
279
|
+
* });
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
async sendBulkMail(options) {
|
|
283
|
+
const transport = this.transport;
|
|
284
|
+
if (typeof transport.sendBulk !== "function") throw new ConfigurationError("MailKit: The configured transport does not support bulk sending (sendBulk).");
|
|
285
|
+
const template = options.template;
|
|
286
|
+
const resolvedFromName = await this.resolveFromName(template);
|
|
287
|
+
const rendered = await this.renderer.render(template);
|
|
288
|
+
const plaintextBody = template.noHtmlMessage ?? rendered.text;
|
|
289
|
+
const from = this.applyFromName(options.from ?? this.defaultFrom, resolvedFromName);
|
|
290
|
+
return await transport.sendBulk({
|
|
291
|
+
to: options.to,
|
|
292
|
+
from,
|
|
293
|
+
subject: template.subject,
|
|
294
|
+
html: rendered.html,
|
|
295
|
+
text: plaintextBody,
|
|
296
|
+
cc: options.cc,
|
|
297
|
+
bcc: options.bcc,
|
|
298
|
+
replyTo: options.replyTo,
|
|
299
|
+
attachments: options.attachments,
|
|
300
|
+
headers: options.headers,
|
|
301
|
+
tags: options.tags,
|
|
302
|
+
metadata: options.metadata,
|
|
303
|
+
trackClicks: options.trackClicks,
|
|
304
|
+
trackOpens: options.trackOpens
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Sends a single email using a provider-side template.
|
|
309
|
+
*/
|
|
310
|
+
async sendKnownTemplate(options) {
|
|
311
|
+
const transport = this.transport;
|
|
312
|
+
if (typeof transport.sendKnownTemplate !== "function") throw new ConfigurationError("MailKit: The configured transport does not support template sending (sendKnownTemplate).");
|
|
313
|
+
return await transport.sendKnownTemplate({
|
|
314
|
+
...options,
|
|
315
|
+
from: options.from ?? this.defaultFrom
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Sends a bulk email using a provider-side template with optional
|
|
320
|
+
* per-recipient merge data.
|
|
321
|
+
*/
|
|
322
|
+
async sendBulkKnownTemplate(options) {
|
|
323
|
+
const transport = this.transport;
|
|
324
|
+
if (typeof transport.sendBulkKnownTemplate !== "function") throw new ConfigurationError("MailKit: The configured transport does not support bulk template sending (sendBulkKnownTemplate).");
|
|
325
|
+
return await transport.sendBulkKnownTemplate({
|
|
326
|
+
...options,
|
|
327
|
+
from: options.from ?? this.defaultFrom
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async resolveFromName(template, explicitFromName) {
|
|
331
|
+
let resolved = explicitFromName ?? template.fromName;
|
|
332
|
+
if (!resolved && this.brandNameResolver) resolved = await this.brandNameResolver();
|
|
333
|
+
if (!resolved) resolved = this.defaultBrandName;
|
|
334
|
+
return resolved;
|
|
335
|
+
}
|
|
336
|
+
applyFromName(from, fromName) {
|
|
337
|
+
if (fromName && from) return {
|
|
338
|
+
address: normalizeRecipient(from).address,
|
|
339
|
+
name: fromName
|
|
340
|
+
};
|
|
341
|
+
return from;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Dispatches a raw, pre-rendered email directly through the transport.
|
|
345
|
+
* If `options.text` is omitted, plain text is automatically derived from `options.html`.
|
|
346
|
+
*/
|
|
347
|
+
async sendRaw(options) {
|
|
348
|
+
const from = options.from ?? this.defaultFrom;
|
|
349
|
+
const text = options.text ?? (options.html ? htmlToPlainText(options.html) : void 0);
|
|
350
|
+
return await this.transport.send({
|
|
351
|
+
...options,
|
|
352
|
+
from,
|
|
353
|
+
text
|
|
354
|
+
});
|
|
355
|
+
}
|
|
337
356
|
};
|
|
357
|
+
/**
|
|
358
|
+
* Convenience factory to create a MailKit client instance.
|
|
359
|
+
*/
|
|
338
360
|
function createMailKit(options) {
|
|
339
|
-
|
|
361
|
+
return new MailKit(options);
|
|
340
362
|
}
|
|
341
363
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/transports/cpaas/cpaas-sender.ts
|
|
366
|
+
/**
|
|
367
|
+
* Zoho CPaaS (formerly ZeptoMail) transactional email HTTP transport adapter.
|
|
368
|
+
*
|
|
369
|
+
* Uses the standard global `fetch` API for zero-dependency, universal edge
|
|
370
|
+
* compatibility (Node.js, Bun, Deno, Cloudflare Workers).
|
|
371
|
+
*
|
|
372
|
+
* Supports:
|
|
373
|
+
* - `send` — single raw email (`POST /v1.1/email`)
|
|
374
|
+
* - `sendBulk` — batch raw email (`POST /v1.1/email/batch`)
|
|
375
|
+
* - `sendKnownTemplate` — single template email (`POST /v1.1/email/template`)
|
|
376
|
+
* - `sendBulkKnownTemplate` — batch template email (`POST /v1.1/email/template/batch`)
|
|
377
|
+
*/
|
|
378
|
+
var CpaasHttpSender = class {
|
|
379
|
+
name = "cpaas";
|
|
380
|
+
apiBase;
|
|
381
|
+
authHeader;
|
|
382
|
+
defaultFrom;
|
|
383
|
+
preferTemplateAlias;
|
|
384
|
+
fetchImpl;
|
|
385
|
+
constructor(config) {
|
|
386
|
+
if (!config.apiToken || typeof config.apiToken !== "string") throw new ConfigurationError("CpaasHttpSender: Missing or invalid apiToken.");
|
|
387
|
+
if (!config.from) throw new ConfigurationError("CpaasHttpSender: Missing required default from address.");
|
|
388
|
+
const host = (config.apiHost || "cpaas.zoho.com").replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
389
|
+
this.apiBase = `https://${host}/v1.1`;
|
|
390
|
+
const token = config.apiToken.trim();
|
|
391
|
+
this.authHeader = token.startsWith("Zoho-") ? token : `Zoho-enczapikey ${token}`;
|
|
392
|
+
this.defaultFrom = normalizeRecipient(config.from);
|
|
393
|
+
if (!this.defaultFrom.address) throw new ConfigurationError("CpaasHttpSender: Configured from address must not be empty.");
|
|
394
|
+
this.preferTemplateAlias = config.preferTemplateAlias ?? false;
|
|
395
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
396
|
+
if (typeof fetchImpl !== "function") throw new ConfigurationError("CpaasHttpSender: No global fetch implementation found. Pass a custom fetch to CpaasHttpConfig if running on Node < 18.");
|
|
397
|
+
this.fetchImpl = fetchImpl.bind(globalThis);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Dispatches a single raw email using Zoho CPaaS API.
|
|
401
|
+
*/
|
|
402
|
+
async send(options) {
|
|
403
|
+
const payload = this.buildSendPayload(options);
|
|
404
|
+
return await this.post(`${this.apiBase}/email`, payload);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Dispatches a batch of a single raw email content to many recipients.
|
|
408
|
+
* The provider delivers an individual copy to each recipient.
|
|
409
|
+
*/
|
|
410
|
+
async sendBulk(options) {
|
|
411
|
+
const payload = this.buildSendPayload(options);
|
|
412
|
+
return await this.post(`${this.apiBase}/email/batch`, payload);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Dispatches a single email using a provider-side template.
|
|
416
|
+
*/
|
|
417
|
+
async sendKnownTemplate(options) {
|
|
418
|
+
const payload = this.buildTemplatePayload(options);
|
|
419
|
+
return await this.post(`${this.apiBase}/email/template`, payload);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Dispatches a batch of emails using a provider-side template, with
|
|
423
|
+
* optional per-recipient merge data.
|
|
424
|
+
*/
|
|
425
|
+
async sendBulkKnownTemplate(options) {
|
|
426
|
+
const payload = this.buildBatchTemplatePayload(options);
|
|
427
|
+
return await this.post(`${this.apiBase}/email/template/batch`, payload);
|
|
428
|
+
}
|
|
429
|
+
buildSendPayload(options) {
|
|
430
|
+
const toRecipients = normalizeRecipients(options.to);
|
|
431
|
+
if (toRecipients.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
|
|
432
|
+
const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
|
|
433
|
+
const payload = {
|
|
434
|
+
from: {
|
|
435
|
+
address: resolvedFrom.address,
|
|
436
|
+
name: resolvedFrom.name
|
|
437
|
+
},
|
|
438
|
+
to: toRecipients.map((r) => ({ email_address: {
|
|
439
|
+
address: r.address,
|
|
440
|
+
name: r.name || ""
|
|
441
|
+
} })),
|
|
442
|
+
subject: options.subject,
|
|
443
|
+
htmlbody: options.html,
|
|
444
|
+
textbody: options.text
|
|
445
|
+
};
|
|
446
|
+
this.applyCommon(payload, options);
|
|
447
|
+
return payload;
|
|
448
|
+
}
|
|
449
|
+
buildTemplatePayload(options) {
|
|
450
|
+
const toRecipients = normalizeRecipients(options.to);
|
|
451
|
+
if (toRecipients.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
|
|
452
|
+
const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
|
|
453
|
+
const payload = {
|
|
454
|
+
...this.templateRef(options.templateId),
|
|
455
|
+
from: {
|
|
456
|
+
address: resolvedFrom.address,
|
|
457
|
+
name: resolvedFrom.name
|
|
458
|
+
},
|
|
459
|
+
to: toRecipients.map((r) => ({ email_address: {
|
|
460
|
+
address: r.address,
|
|
461
|
+
name: r.name || ""
|
|
462
|
+
} }))
|
|
463
|
+
};
|
|
464
|
+
if (options.mergeInfo) payload.merge_info = options.mergeInfo;
|
|
465
|
+
this.applyCommon(payload, options);
|
|
466
|
+
return payload;
|
|
467
|
+
}
|
|
468
|
+
buildBatchTemplatePayload(options) {
|
|
469
|
+
if (!options.to || options.to.length === 0) throw new TransportError("CpaasHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
|
|
470
|
+
const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
|
|
471
|
+
const payload = {
|
|
472
|
+
...this.templateRef(options.templateId),
|
|
473
|
+
from: {
|
|
474
|
+
address: resolvedFrom.address,
|
|
475
|
+
name: resolvedFrom.name
|
|
476
|
+
},
|
|
477
|
+
to: options.to.map((r) => ({
|
|
478
|
+
email_address: {
|
|
479
|
+
address: r.address,
|
|
480
|
+
name: r.name || ""
|
|
481
|
+
},
|
|
482
|
+
...r.mergeInfo ? { merge_info: r.mergeInfo } : {}
|
|
483
|
+
}))
|
|
484
|
+
};
|
|
485
|
+
this.applyCommon(payload, options);
|
|
486
|
+
return payload;
|
|
487
|
+
}
|
|
488
|
+
templateRef(templateId) {
|
|
489
|
+
return this.preferTemplateAlias ? { template_alias: templateId } : { template_key: templateId };
|
|
490
|
+
}
|
|
491
|
+
applyCommon(payload, options) {
|
|
492
|
+
if (options.cc) {
|
|
493
|
+
const ccList = normalizeRecipients(options.cc);
|
|
494
|
+
if (ccList.length > 0) payload.cc = ccList.map((r) => ({ email_address: {
|
|
495
|
+
address: r.address,
|
|
496
|
+
name: r.name || ""
|
|
497
|
+
} }));
|
|
498
|
+
}
|
|
499
|
+
if (options.bcc) {
|
|
500
|
+
const bccList = normalizeRecipients(options.bcc);
|
|
501
|
+
if (bccList.length > 0) payload.bcc = bccList.map((r) => ({ email_address: {
|
|
502
|
+
address: r.address,
|
|
503
|
+
name: r.name || ""
|
|
504
|
+
} }));
|
|
505
|
+
}
|
|
506
|
+
if (options.replyTo) {
|
|
507
|
+
const replyToList = normalizeRecipients(options.replyTo);
|
|
508
|
+
if (replyToList.length > 0) payload.reply_to = replyToList.map((r) => ({
|
|
509
|
+
address: r.address,
|
|
510
|
+
name: r.name
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
if (options.attachments && options.attachments.length > 0) payload.attachments = options.attachments.map((att) => this.toAttachmentPayload(att));
|
|
514
|
+
if (options.trackClicks !== void 0) payload.track_clicks = options.trackClicks;
|
|
515
|
+
if (options.trackOpens !== void 0) payload.track_opens = options.trackOpens;
|
|
516
|
+
if (options.bounceAddress !== void 0) payload.bounce_address = options.bounceAddress;
|
|
517
|
+
if (options.clientReference !== void 0) payload.client_reference = options.clientReference;
|
|
518
|
+
}
|
|
519
|
+
toAttachmentPayload(att) {
|
|
520
|
+
let contentBase64;
|
|
521
|
+
if (typeof att.content === "string") contentBase64 = att.content;
|
|
522
|
+
else if (typeof Buffer !== "undefined") contentBase64 = Buffer.from(att.content).toString("base64");
|
|
523
|
+
else {
|
|
524
|
+
let binary = "";
|
|
525
|
+
const bytes = new Uint8Array(att.content);
|
|
526
|
+
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
527
|
+
contentBase64 = btoa(binary);
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
name: att.filename,
|
|
531
|
+
content: contentBase64,
|
|
532
|
+
mime_type: att.mimeType || att.contentType || "application/octet-stream",
|
|
533
|
+
cid: att.cid
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
async post(url, payload) {
|
|
537
|
+
let response;
|
|
538
|
+
try {
|
|
539
|
+
response = await this.fetchImpl(url, {
|
|
540
|
+
method: "POST",
|
|
541
|
+
headers: {
|
|
542
|
+
Accept: "application/json",
|
|
543
|
+
"Content-Type": "application/json",
|
|
544
|
+
Authorization: this.authHeader
|
|
545
|
+
},
|
|
546
|
+
body: JSON.stringify(payload)
|
|
547
|
+
});
|
|
548
|
+
} catch (err) {
|
|
549
|
+
throw new TransportError(`CpaasHttpSender: Network request failed: ${err instanceof Error ? err.message : String(err)}`, {
|
|
550
|
+
transportName: this.name,
|
|
551
|
+
cause: err
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const responseText = await response.text();
|
|
555
|
+
let responseData;
|
|
556
|
+
try {
|
|
557
|
+
responseData = responseText ? JSON.parse(responseText) : {};
|
|
558
|
+
} catch {
|
|
559
|
+
responseData = { raw: responseText };
|
|
560
|
+
}
|
|
561
|
+
if (!response.ok) {
|
|
562
|
+
const errorMessage = responseData?.data?.message || responseData?.error?.message || responseData?.message || `HTTP ${response.status}: ${responseText || response.statusText}`;
|
|
563
|
+
throw new TransportError(`CpaasHttpSender: Failed to send email - ${errorMessage}`, {
|
|
564
|
+
transportName: this.name,
|
|
565
|
+
statusCode: response.status,
|
|
566
|
+
responseBody: responseText,
|
|
567
|
+
rawResponse: responseData
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
success: true,
|
|
572
|
+
messageId: responseData?.data?.[0]?.message_id,
|
|
573
|
+
rawResponse: responseData,
|
|
574
|
+
response: responseData
|
|
575
|
+
};
|
|
576
|
+
}
|
|
486
577
|
};
|
|
487
578
|
|
|
488
|
-
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/transports/memory/memory-sender.ts
|
|
581
|
+
/**
|
|
582
|
+
* In-memory email sender for automated testing, local development, and CI environments.
|
|
583
|
+
* Captures all dispatched emails and provides inspection methods.
|
|
584
|
+
*/
|
|
489
585
|
var MemoryEmailSender = class {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
586
|
+
name = "memory";
|
|
587
|
+
sentMails = [];
|
|
588
|
+
sentBulkMails = [];
|
|
589
|
+
sentTemplateMails = [];
|
|
590
|
+
sentBulkTemplateMails = [];
|
|
591
|
+
nextId = 1;
|
|
592
|
+
result(prefix) {
|
|
593
|
+
const id = this.nextId++;
|
|
594
|
+
const messageId = prefix ? `mem-${prefix}-${id}-${Date.now()}` : `mem-${id}-${Date.now()}`;
|
|
595
|
+
const rawResponse = { id: messageId };
|
|
596
|
+
return {
|
|
597
|
+
success: true,
|
|
598
|
+
messageId,
|
|
599
|
+
rawResponse,
|
|
600
|
+
response: rawResponse
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async send(options) {
|
|
604
|
+
this.sentMails.push({ ...options });
|
|
605
|
+
return this.result();
|
|
606
|
+
}
|
|
607
|
+
async sendBulk(options) {
|
|
608
|
+
this.sentBulkMails.push({ ...options });
|
|
609
|
+
return this.result("bulk");
|
|
610
|
+
}
|
|
611
|
+
async sendKnownTemplate(options) {
|
|
612
|
+
this.sentTemplateMails.push({ ...options });
|
|
613
|
+
return this.result("tpl");
|
|
614
|
+
}
|
|
615
|
+
async sendBulkKnownTemplate(options) {
|
|
616
|
+
this.sentBulkTemplateMails.push({ ...options });
|
|
617
|
+
return this.result("bulk-tpl");
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Returns a copy of all single emails sent through this transport instance.
|
|
621
|
+
*/
|
|
622
|
+
getSentMails() {
|
|
623
|
+
return [...this.sentMails];
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Returns the most recent single email sent, or undefined if none have been sent.
|
|
627
|
+
*/
|
|
628
|
+
getLastMail() {
|
|
629
|
+
return this.sentMails.length > 0 ? { ...this.sentMails[this.sentMails.length - 1] } : void 0;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Returns a copy of all bulk emails sent through this transport instance.
|
|
633
|
+
*/
|
|
634
|
+
getSentBulkMails() {
|
|
635
|
+
return [...this.sentBulkMails];
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Returns a copy of all single template emails sent through this transport instance.
|
|
639
|
+
*/
|
|
640
|
+
getSentTemplateMails() {
|
|
641
|
+
return [...this.sentTemplateMails];
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Returns a copy of all bulk template emails sent through this transport instance.
|
|
645
|
+
*/
|
|
646
|
+
getSentBulkTemplateMails() {
|
|
647
|
+
return [...this.sentBulkTemplateMails];
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Clears all recorded emails.
|
|
651
|
+
*/
|
|
652
|
+
clear() {
|
|
653
|
+
this.sentMails = [];
|
|
654
|
+
this.sentBulkMails = [];
|
|
655
|
+
this.sentTemplateMails = [];
|
|
656
|
+
this.sentBulkTemplateMails = [];
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Returns the total count of single emails sent.
|
|
660
|
+
*/
|
|
661
|
+
count() {
|
|
662
|
+
return this.sentMails.length;
|
|
663
|
+
}
|
|
526
664
|
};
|
|
527
665
|
|
|
528
|
-
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/transports/console/console-sender.ts
|
|
668
|
+
/**
|
|
669
|
+
* Console logger transport that prints dispatched emails to stdout/stderr.
|
|
670
|
+
* Ideal for development and debugging.
|
|
671
|
+
*/
|
|
529
672
|
var ConsoleEmailSender = class {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
673
|
+
name = "console";
|
|
674
|
+
log;
|
|
675
|
+
includeHtml;
|
|
676
|
+
nextId = 1;
|
|
677
|
+
constructor(options = {}) {
|
|
678
|
+
this.log = options.logger ?? console.log;
|
|
679
|
+
this.includeHtml = options.includeHtml ?? false;
|
|
680
|
+
}
|
|
681
|
+
async send(options) {
|
|
682
|
+
return this.logDispatch({
|
|
683
|
+
kind: "EMAIL",
|
|
684
|
+
to: options.to,
|
|
685
|
+
from: options.from,
|
|
686
|
+
subject: options.subject,
|
|
687
|
+
text: options.text,
|
|
688
|
+
html: options.html
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
async sendBulk(options) {
|
|
692
|
+
return this.logDispatch({
|
|
693
|
+
kind: "BULK EMAIL",
|
|
694
|
+
to: options.to,
|
|
695
|
+
from: options.from,
|
|
696
|
+
subject: options.subject,
|
|
697
|
+
text: options.text,
|
|
698
|
+
html: options.html
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async sendKnownTemplate(options) {
|
|
702
|
+
return this.logDispatch({
|
|
703
|
+
kind: "TEMPLATE EMAIL",
|
|
704
|
+
to: options.to,
|
|
705
|
+
from: options.from,
|
|
706
|
+
templateId: options.templateId
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
async sendBulkKnownTemplate(options) {
|
|
710
|
+
return this.logDispatch({
|
|
711
|
+
kind: "BULK TEMPLATE EMAIL",
|
|
712
|
+
to: options.to,
|
|
713
|
+
from: options.from,
|
|
714
|
+
templateId: options.templateId
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
logDispatch(entry) {
|
|
718
|
+
const to = normalizeRecipients(entry.to).map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
|
|
719
|
+
const from = entry.from ? JSON.stringify(entry.from) : "(default)";
|
|
720
|
+
const messageId = `console-${this.nextId++}-${Date.now()}`;
|
|
721
|
+
const separator = "─".repeat(60);
|
|
722
|
+
const lines = [
|
|
723
|
+
`\n┌${separator}┐`,
|
|
724
|
+
`│ [ts-mailkit:console] DISPATCHED ${entry.kind} (${messageId})`,
|
|
725
|
+
`├${separator}┤`,
|
|
726
|
+
`│ From: ${from}`,
|
|
727
|
+
`│ To: ${to}`
|
|
728
|
+
];
|
|
729
|
+
if (entry.subject !== void 0) lines.push(`│ Subject: ${entry.subject}`);
|
|
730
|
+
if (entry.templateId !== void 0) lines.push(`│ Template: ${entry.templateId}`);
|
|
731
|
+
lines.push(`├${separator}┤`);
|
|
732
|
+
if (entry.text) {
|
|
733
|
+
lines.push("│ Text Body:");
|
|
734
|
+
for (const line of entry.text.split("\n")) lines.push(`│ ${line}`);
|
|
735
|
+
}
|
|
736
|
+
if (this.includeHtml && entry.html) {
|
|
737
|
+
lines.push("│ HTML Body:");
|
|
738
|
+
for (const line of entry.html.split("\n")) lines.push(`│ ${line}`);
|
|
739
|
+
}
|
|
740
|
+
lines.push(`└${separator}┘\n`);
|
|
741
|
+
this.log(lines.join("\n"));
|
|
742
|
+
const rawResponse = { logged: true };
|
|
743
|
+
return {
|
|
744
|
+
success: true,
|
|
745
|
+
messageId,
|
|
746
|
+
rawResponse,
|
|
747
|
+
response: rawResponse
|
|
748
|
+
};
|
|
749
|
+
}
|
|
574
750
|
};
|
|
575
751
|
|
|
576
|
-
|
|
577
|
-
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/templates/factory.ts
|
|
754
|
+
const DEFAULT_SECURITY_MESSAGE = "If you did not request this, please ignore this email or contact support if you have concerns.";
|
|
578
755
|
function defaultFooter(brandName) {
|
|
579
|
-
|
|
756
|
+
return `© ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}. All rights reserved.`;
|
|
580
757
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
758
|
+
/**
|
|
759
|
+
* Factory for creating pre-populated, type-safe email template DTOs.
|
|
760
|
+
*/
|
|
761
|
+
var EmailTemplateFactory = class EmailTemplateFactory {
|
|
762
|
+
defaultBrandName;
|
|
763
|
+
defaultSecurityMessage;
|
|
764
|
+
footerResolver;
|
|
765
|
+
defaultLogoUrl;
|
|
766
|
+
constructor(config = {}) {
|
|
767
|
+
this.defaultBrandName = config.brandName || "Application";
|
|
768
|
+
this.defaultSecurityMessage = config.securityMessage || DEFAULT_SECURITY_MESSAGE;
|
|
769
|
+
this.defaultLogoUrl = config.logoUrl;
|
|
770
|
+
if (typeof config.footer === "function") this.footerResolver = config.footer;
|
|
771
|
+
else if (typeof config.footer === "string") {
|
|
772
|
+
const fixedFooter = config.footer;
|
|
773
|
+
this.footerResolver = () => fixedFooter;
|
|
774
|
+
} else this.footerResolver = defaultFooter;
|
|
775
|
+
}
|
|
776
|
+
resolveBrand(override) {
|
|
777
|
+
return override || this.defaultBrandName;
|
|
778
|
+
}
|
|
779
|
+
buildFooter(brandName) {
|
|
780
|
+
return this.footerResolver(brandName);
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Welcome / email-verification email sent after registration.
|
|
784
|
+
*/
|
|
785
|
+
createEmailVerification(params) {
|
|
786
|
+
const { username, verificationUrl, otp, brandName } = params;
|
|
787
|
+
const brand = this.resolveBrand(brandName);
|
|
788
|
+
const body = `Hi ${username},<br/>Thank you for signing up. Use the code below or click the button to verify your email address.`;
|
|
789
|
+
return {
|
|
790
|
+
subject: "Verify Your Email Address",
|
|
791
|
+
header: `Welcome to ${brand}!`,
|
|
792
|
+
body,
|
|
793
|
+
logoUrl: this.defaultLogoUrl,
|
|
794
|
+
otpCode: otp,
|
|
795
|
+
actionUrl: verificationUrl,
|
|
796
|
+
actionText: "Verify Email",
|
|
797
|
+
extraMessage: this.defaultSecurityMessage,
|
|
798
|
+
footer: this.buildFooter(brand),
|
|
799
|
+
userName: username,
|
|
800
|
+
fromName: brand,
|
|
801
|
+
noHtmlMessage: htmlToPlainText(body) + `\n\nYour verification code is: ${otp}\n\nAlternatively, verify here:\n${verificationUrl}`
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Password-reset email with an optional OTP code and action link.
|
|
806
|
+
*/
|
|
807
|
+
createPasswordReset(params) {
|
|
808
|
+
const { username, resetUrl, otp = "", brandName } = params;
|
|
809
|
+
const brand = this.resolveBrand(brandName);
|
|
810
|
+
const body = `Hi ${username},<br/>We received a request to reset your password. Use the OTP code below or click the button to proceed.`;
|
|
811
|
+
const plainTextOtp = otp ? `\n\nYour OTP code is: ${otp}` : "";
|
|
812
|
+
return {
|
|
813
|
+
subject: "Password Reset Request",
|
|
814
|
+
header: "Reset Your Password",
|
|
815
|
+
body,
|
|
816
|
+
logoUrl: this.defaultLogoUrl,
|
|
817
|
+
otpCode: otp || void 0,
|
|
818
|
+
actionUrl: resetUrl,
|
|
819
|
+
actionText: "Reset Password",
|
|
820
|
+
extraMessage: this.defaultSecurityMessage,
|
|
821
|
+
footer: this.buildFooter(brand),
|
|
822
|
+
userName: username,
|
|
823
|
+
fromName: brand,
|
|
824
|
+
noHtmlMessage: htmlToPlainText(body) + plainTextOtp + `\n\nAlternatively, you can use the following link to reset your password:\n${resetUrl}`
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Generic plain message email (no action button, no OTP).
|
|
829
|
+
*/
|
|
830
|
+
createSimpleMessage(params) {
|
|
831
|
+
const { subject, header, body, brandName } = params;
|
|
832
|
+
const brand = this.resolveBrand(brandName);
|
|
833
|
+
return {
|
|
834
|
+
subject,
|
|
835
|
+
header,
|
|
836
|
+
body,
|
|
837
|
+
logoUrl: this.defaultLogoUrl,
|
|
838
|
+
footer: this.buildFooter(brand),
|
|
839
|
+
fromName: brand,
|
|
840
|
+
noHtmlMessage: htmlToPlainText(body)
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Newsletter / announcement email with an optional hero image and CTA button.
|
|
845
|
+
*/
|
|
846
|
+
createNewsLetter(params) {
|
|
847
|
+
const { subject, header, body, imageUrl, actionUrl, actionText, brandName, logoUrl } = params;
|
|
848
|
+
const brand = this.resolveBrand(brandName);
|
|
849
|
+
let noHtmlMessage = htmlToPlainText(body);
|
|
850
|
+
if (actionUrl) noHtmlMessage += `\n\n${actionText || "Learn more"}: ${actionUrl}`;
|
|
851
|
+
return {
|
|
852
|
+
subject,
|
|
853
|
+
header,
|
|
854
|
+
body,
|
|
855
|
+
logoUrl: logoUrl || this.defaultLogoUrl,
|
|
856
|
+
imageUrl,
|
|
857
|
+
actionUrl,
|
|
858
|
+
actionText,
|
|
859
|
+
footer: this.buildFooter(brand),
|
|
860
|
+
fromName: brand,
|
|
861
|
+
noHtmlMessage
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Two-factor authentication email with OTP code and optional magic link.
|
|
866
|
+
*/
|
|
867
|
+
create2FAEmail(params) {
|
|
868
|
+
const { username, otp, magicLink = "", brandName } = params;
|
|
869
|
+
const brand = this.resolveBrand(brandName);
|
|
870
|
+
const body = `Hi ${username},<br/>Use the code below or click the button to complete your login.`;
|
|
871
|
+
const plainLink = magicLink ? `\n\nAlternatively, verify here:\n${magicLink}` : "";
|
|
872
|
+
return {
|
|
873
|
+
subject: "Your Login Verification Code",
|
|
874
|
+
header: "Two-Factor Authentication",
|
|
875
|
+
body,
|
|
876
|
+
logoUrl: this.defaultLogoUrl,
|
|
877
|
+
otpCode: otp,
|
|
878
|
+
actionUrl: magicLink || void 0,
|
|
879
|
+
actionText: magicLink ? "Verify Login" : void 0,
|
|
880
|
+
extraMessage: this.defaultSecurityMessage,
|
|
881
|
+
footer: this.buildFooter(brand),
|
|
882
|
+
userName: username,
|
|
883
|
+
fromName: brand,
|
|
884
|
+
noHtmlMessage: htmlToPlainText(body) + `\n\nYour verification code is: ${otp}` + plainLink
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Magic link sign-in email (no OTP, button only).
|
|
889
|
+
*/
|
|
890
|
+
createMagicLinkEmail(params) {
|
|
891
|
+
const { username, magicLink, brandName } = params;
|
|
892
|
+
const brand = this.resolveBrand(brandName);
|
|
893
|
+
const body = `Hi ${username},<br/>Click the button below to sign in. No password needed.`;
|
|
894
|
+
return {
|
|
895
|
+
subject: "Your Magic Link",
|
|
896
|
+
header: `Sign In to ${brand}`,
|
|
897
|
+
body,
|
|
898
|
+
logoUrl: this.defaultLogoUrl,
|
|
899
|
+
actionUrl: magicLink,
|
|
900
|
+
actionText: "Sign In",
|
|
901
|
+
extraMessage: this.defaultSecurityMessage,
|
|
902
|
+
footer: this.buildFooter(brand),
|
|
903
|
+
userName: username,
|
|
904
|
+
fromName: brand,
|
|
905
|
+
noHtmlMessage: htmlToPlainText(body) + `\n\nSign in here:\n${magicLink}`
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
static _defaultInstance;
|
|
909
|
+
static get defaultInstance() {
|
|
910
|
+
if (!this._defaultInstance) this._defaultInstance = new EmailTemplateFactory();
|
|
911
|
+
return this._defaultInstance;
|
|
912
|
+
}
|
|
913
|
+
static createEmailVerification(params) {
|
|
914
|
+
return this.defaultInstance.createEmailVerification(params);
|
|
915
|
+
}
|
|
916
|
+
static createPasswordReset(params) {
|
|
917
|
+
return this.defaultInstance.createPasswordReset(params);
|
|
918
|
+
}
|
|
919
|
+
static createSimpleMessage(params) {
|
|
920
|
+
return this.defaultInstance.createSimpleMessage(params);
|
|
921
|
+
}
|
|
922
|
+
static createNewsLetter(params) {
|
|
923
|
+
return this.defaultInstance.createNewsLetter(params);
|
|
924
|
+
}
|
|
925
|
+
static create2FAEmail(params) {
|
|
926
|
+
return this.defaultInstance.create2FAEmail(params);
|
|
927
|
+
}
|
|
928
|
+
static createMagicLinkEmail(params) {
|
|
929
|
+
return this.defaultInstance.createMagicLinkEmail(params);
|
|
930
|
+
}
|
|
931
|
+
};
|
|
725
932
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
extraMessage: this.defaultSecurityMessage,
|
|
744
|
-
footer: this.buildFooter(brand),
|
|
745
|
-
userName: username,
|
|
746
|
-
fromName: brand,
|
|
747
|
-
noHtmlMessage: htmlToPlainText(body) + `
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/templates/template-builder.ts
|
|
935
|
+
/**
|
|
936
|
+
* Builds an upload-ready HTML template from the canonical {@link EmailTemplateDto}.
|
|
937
|
+
*
|
|
938
|
+
* The output can be uploaded to a messaging platform (Zoho CPaaS, SendGrid, etc.)
|
|
939
|
+
* as a provider-side template.
|
|
940
|
+
*/
|
|
941
|
+
function buildHTMLTemplate(dto, options = {}) {
|
|
942
|
+
return buildHtml(dto, options);
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Builds an upload-ready plain-text template from the canonical {@link EmailTemplateDto}.
|
|
946
|
+
*/
|
|
947
|
+
function buildPlainTextTemplate(dto, _options) {
|
|
948
|
+
return buildPlainText(dto);
|
|
949
|
+
}
|
|
748
950
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
static create2FAEmail(params) {
|
|
774
|
-
return this.defaultInstance.create2FAEmail(params);
|
|
775
|
-
}
|
|
776
|
-
static createMagicLinkEmail(params) {
|
|
777
|
-
return this.defaultInstance.createMagicLinkEmail(params);
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
781
|
-
0 && (module.exports = {
|
|
782
|
-
ConfigurationError,
|
|
783
|
-
ConsoleEmailSender,
|
|
784
|
-
DefaultEmailRenderer,
|
|
785
|
-
EmailTemplateFactory,
|
|
786
|
-
MailKit,
|
|
787
|
-
MailKitError,
|
|
788
|
-
MemoryEmailSender,
|
|
789
|
-
TemplateError,
|
|
790
|
-
TransportError,
|
|
791
|
-
ZeptoMailHttpSender,
|
|
792
|
-
buildHtml,
|
|
793
|
-
buildPlainText,
|
|
794
|
-
createMailKit,
|
|
795
|
-
decodeHtmlEntities,
|
|
796
|
-
htmlToPlainText,
|
|
797
|
-
isValidHttpUrl,
|
|
798
|
-
normalizeRecipient,
|
|
799
|
-
normalizeRecipients,
|
|
800
|
-
parseEmailAddress
|
|
801
|
-
});
|
|
951
|
+
//#endregion
|
|
952
|
+
exports.ConfigurationError = ConfigurationError;
|
|
953
|
+
exports.ConsoleEmailSender = ConsoleEmailSender;
|
|
954
|
+
exports.CpaasHttpSender = CpaasHttpSender;
|
|
955
|
+
exports.DefaultEmailRenderer = DefaultEmailRenderer;
|
|
956
|
+
exports.EmailTemplateFactory = EmailTemplateFactory;
|
|
957
|
+
exports.MailKit = MailKit;
|
|
958
|
+
exports.MailKitError = MailKitError;
|
|
959
|
+
exports.MemoryEmailSender = MemoryEmailSender;
|
|
960
|
+
exports.TemplateError = TemplateError;
|
|
961
|
+
exports.TransportError = TransportError;
|
|
962
|
+
exports.ZeptoMailHttpSender = CpaasHttpSender;
|
|
963
|
+
exports.buildHTMLTemplate = buildHTMLTemplate;
|
|
964
|
+
exports.buildHtml = buildHtml;
|
|
965
|
+
exports.buildPlainText = buildPlainText;
|
|
966
|
+
exports.buildPlainTextTemplate = buildPlainTextTemplate;
|
|
967
|
+
exports.createMailKit = createMailKit;
|
|
968
|
+
exports.decodeHtmlEntities = decodeHtmlEntities;
|
|
969
|
+
exports.htmlToPlainText = htmlToPlainText;
|
|
970
|
+
exports.isValidHttpUrl = isValidHttpUrl;
|
|
971
|
+
exports.normalizeRecipient = normalizeRecipient;
|
|
972
|
+
exports.normalizeRecipients = normalizeRecipients;
|
|
973
|
+
exports.parseEmailAddress = parseEmailAddress;
|
|
802
974
|
//# sourceMappingURL=index.cjs.map
|