ts-mailkit 2.0.0 → 2.0.1

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/dist/index.cjs CHANGED
@@ -1,802 +1,703 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
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
- if (!input || typeof input !== "string") {
48
- return { address: "" };
49
- }
50
- const trimmed = input.trim();
51
- const match = trimmed.match(/^(?:["']?([^"']+)["']?\s+)?<?([^\s<>@]+@[^\s<>@]+)>?$/);
52
- if (!match) {
53
- return { address: trimmed };
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
- if (typeof recipient === "string") {
62
- return { address: recipient.trim() };
63
- }
64
- if ("email" in recipient) {
65
- return {
66
- address: (recipient.email ?? "").trim(),
67
- name: recipient.name?.trim()
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
- if (!recipients) return [];
77
- const list = Array.isArray(recipients) ? recipients : [recipients];
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
- // src/core/errors.ts
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
- constructor(message) {
84
- super(message);
85
- this.name = "MailKitError";
86
- Object.setPrototypeOf(this, new.target.prototype);
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
- constructor(message) {
91
- super(message);
92
- this.name = "ConfigurationError";
93
- Object.setPrototypeOf(this, new.target.prototype);
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
- transportName;
98
- statusCode;
99
- responseBody;
100
- cause;
101
- constructor(message, options) {
102
- super(message);
103
- this.name = "TransportError";
104
- this.transportName = options.transportName;
105
- this.statusCode = options.statusCode;
106
- this.responseBody = options.responseBody;
107
- this.cause = options.cause;
108
- Object.setPrototypeOf(this, new.target.prototype);
109
- }
68
+ transportName;
69
+ statusCode;
70
+ responseBody;
71
+ cause;
72
+ constructor(message, options) {
73
+ super(message);
74
+ this.name = "TransportError";
75
+ this.transportName = options.transportName;
76
+ this.statusCode = options.statusCode;
77
+ this.responseBody = options.responseBody;
78
+ this.cause = options.cause;
79
+ Object.setPrototypeOf(this, new.target.prototype);
80
+ }
110
81
  };
82
+ /**
83
+ * Thrown when template rendering or DTO validation fails.
84
+ */
111
85
  var TemplateError = class extends MailKitError {
112
- constructor(message) {
113
- super(message);
114
- this.name = "TemplateError";
115
- Object.setPrototypeOf(this, new.target.prototype);
116
- }
86
+ constructor(message) {
87
+ super(message);
88
+ this.name = "TemplateError";
89
+ Object.setPrototypeOf(this, new.target.prototype);
90
+ }
117
91
  };
118
92
 
119
- // src/renderers/utils/html-to-plaintext.ts
93
+ //#endregion
94
+ //#region src/renderers/utils/html-to-plaintext.ts
95
+ /**
96
+ * Decodes common HTML entities into their plain text equivalents.
97
+ */
120
98
  function decodeHtmlEntities(text) {
121
- return text.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&copy;/gi, "\xA9").replace(/&reg;/gi, "\xAE").replace(/&trade;/gi, "\u2122");
99
+ return text.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, "\"").replace(/&#39;/gi, "'").replace(/&copy;/gi, "©").replace(/&reg;/gi, "®").replace(/&trade;/gi, "™");
122
100
  }
101
+ /**
102
+ * Strips HTML tags and normalizes whitespace into readable plain text.
103
+ * Converts <br>, <p>, and <div> into appropriate line breaks.
104
+ */
123
105
  function htmlToPlainText(html) {
124
- if (!html) return "";
125
- return decodeHtmlEntities(
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();
106
+ if (!html) return "";
107
+ 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
108
  }
129
109
 
130
- // src/renderers/default/default-renderer.ts
110
+ //#endregion
111
+ //#region src/renderers/default/default-renderer.ts
112
+ /**
113
+ * Validates whether a string is a valid HTTP or HTTPS URL.
114
+ */
131
115
  function isValidHttpUrl(urlString) {
132
- if (!urlString || typeof urlString !== "string") return false;
133
- try {
134
- const url = new URL(urlString.trim());
135
- return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0;
136
- } catch {
137
- return false;
138
- }
116
+ if (!urlString || typeof urlString !== "string") return false;
117
+ try {
118
+ const url = new URL(urlString.trim());
119
+ return (url.protocol === "http:" || url.protocol === "https:") && url.hostname.length > 0;
120
+ } catch {
121
+ return false;
122
+ }
139
123
  }
124
+ /**
125
+ * Builds a plain-text fallback string from an email DTO.
126
+ */
140
127
  function buildPlainText(dto) {
141
- if (dto.noHtmlMessage) {
142
- return dto.noHtmlMessage;
143
- }
144
- const divider = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
145
- const parts = [];
146
- if (dto.header) {
147
- parts.push(dto.header, divider);
148
- }
149
- if (dto.body) {
150
- parts.push(htmlToPlainText(dto.body));
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");
128
+ if (dto.noHtmlMessage) return dto.noHtmlMessage;
129
+ const divider = "──────────────────────────────";
130
+ const parts = [];
131
+ if (dto.header) parts.push(dto.header, divider);
132
+ if (dto.body) parts.push(htmlToPlainText(dto.body));
133
+ if (dto.otpCode) parts.push(`Verification Code: ${dto.otpCode}`);
134
+ if (dto.actionText && dto.actionUrl) parts.push(`${dto.actionText}: ${dto.actionUrl}`);
135
+ if (dto.extraMessage) parts.push(htmlToPlainText(dto.extraMessage));
136
+ if (dto.footer) parts.push(`${divider}\n${htmlToPlainText(dto.footer)}`);
137
+ return parts.join("\n\n");
166
138
  }
139
+ /**
140
+ * Builds a responsive, styled HTML email document from an email DTO.
141
+ */
167
142
  function buildHtml(dto, options = {}) {
168
- const primaryColor = options.primaryColor || "#4f46e5";
169
- const fontFamily = options.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
170
- const maxWidth = options.maxWidth || "600px";
171
- const parts = [
172
- `<!DOCTYPE html>`,
173
- `<html lang="en">`,
174
- `<head>`,
175
- `<meta charset="UTF-8">`,
176
- `<meta name="viewport" content="width=device-width, initial-scale=1.0">`,
177
- `<title>${dto.subject}</title>`,
178
- `</head>`,
179
- `<body style="margin:0;padding:24px 16px;background-color:#f9fafb;font-family:${fontFamily};color:#1f2937;line-height:1.6;">`,
180
- `<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;">`
181
- ];
182
- const logoCandidate = dto.logoUrl ?? options.logoUrl;
183
- if (logoCandidate && isValidHttpUrl(logoCandidate)) {
184
- const validLogo = logoCandidate.trim();
185
- parts.push(
186
- `<div style="margin-bottom:24px;text-align:center;">`,
187
- ` <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;" />`,
188
- `</div>`
189
- );
190
- }
191
- if (dto.imageUrl) {
192
- parts.push(
193
- `<div style="margin-bottom:24px;text-align:center;">`,
194
- ` <img src="${dto.imageUrl}" alt="Header" style="max-width:100%;height:auto;border-radius:6px;" />`,
195
- `</div>`
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");
143
+ const primaryColor = options.primaryColor || "#4f46e5";
144
+ const fontFamily = options.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
145
+ const maxWidth = options.maxWidth || "600px";
146
+ const parts = [
147
+ `<!DOCTYPE html>`,
148
+ `<html lang="en">`,
149
+ `<head>`,
150
+ `<meta charset="UTF-8">`,
151
+ `<meta name="viewport" content="width=device-width, initial-scale=1.0">`,
152
+ `<title>${dto.subject}</title>`,
153
+ `</head>`,
154
+ `<body style="margin:0;padding:24px 16px;background-color:#f9fafb;font-family:${fontFamily};color:#1f2937;line-height:1.6;">`,
155
+ `<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;">`
156
+ ];
157
+ const logoCandidate = dto.logoUrl ?? options.logoUrl;
158
+ if (logoCandidate && isValidHttpUrl(logoCandidate)) {
159
+ const validLogo = logoCandidate.trim();
160
+ 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>`);
161
+ }
162
+ 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>`);
163
+ 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>`);
164
+ if (dto.body) parts.push(`<div style="margin:0 0 20px;font-size:1rem;color:#374151;">${dto.body}</div>`);
165
+ 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>`);
166
+ 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>`);
167
+ if (dto.extraMessage) parts.push(`<div style="margin:20px 0 0;font-size:0.875rem;color:#6b7280;line-height:1.5;">${dto.extraMessage}</div>`);
168
+ 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>`);
169
+ parts.push(`</div>`, `</body>`, `</html>`);
170
+ return parts.join("\n");
232
171
  }
172
+ /**
173
+ * Default HTML and plain-text email renderer for ts-mailkit.
174
+ */
233
175
  var DefaultEmailRenderer = class {
234
- options;
235
- constructor(options = {}) {
236
- this.options = options;
237
- }
238
- render(dto) {
239
- return {
240
- html: buildHtml(dto, this.options),
241
- text: buildPlainText(dto)
242
- };
243
- }
176
+ options;
177
+ constructor(options = {}) {
178
+ this.options = options;
179
+ }
180
+ render(dto) {
181
+ return {
182
+ html: buildHtml(dto, this.options),
183
+ text: buildPlainText(dto)
184
+ };
185
+ }
244
186
  };
245
187
 
246
- // src/core/client.ts
188
+ //#endregion
189
+ //#region src/core/client.ts
190
+ /**
191
+ * Main email client and orchestration service for ts-mailkit.
192
+ */
247
193
  var MailKit = class {
248
- transport;
249
- renderer;
250
- defaultFrom;
251
- defaultBrandName;
252
- brandNameResolver;
253
- constructor(options) {
254
- if (!options.transport) {
255
- throw new ConfigurationError("MailKit: A transport adapter (IEmailSender) is required.");
256
- }
257
- this.transport = options.transport;
258
- this.renderer = options.renderer ?? new DefaultEmailRenderer();
259
- this.defaultFrom = options.defaultFrom;
260
- this.defaultBrandName = options.defaultBrandName;
261
- this.brandNameResolver = options.brandNameResolver;
262
- }
263
- /**
264
- * Returns the underlying transport instance.
265
- */
266
- getTransport() {
267
- return this.transport;
268
- }
269
- /**
270
- * Renders an email DTO to HTML and plain text without dispatching.
271
- */
272
- async render(template) {
273
- return await this.renderer.render(template);
274
- }
275
- /**
276
- * Sends an email using a strongly-typed template DTO.
277
- *
278
- * Example:
279
- * ```ts
280
- * await mailer.send({
281
- * to: 'user@example.com',
282
- * template: myDto,
283
- * });
284
- * ```
285
- */
286
- async send(options) {
287
- const template = options.template;
288
- const recipientName = options.toName ?? template.userName;
289
- let resolvedFromName = options.fromName ?? template.fromName;
290
- if (!resolvedFromName && this.brandNameResolver) {
291
- resolvedFromName = await this.brandNameResolver();
292
- }
293
- if (!resolvedFromName) {
294
- resolvedFromName = this.defaultBrandName;
295
- }
296
- const rendered = await this.renderer.render(template);
297
- const plaintextBody = template.noHtmlMessage ?? rendered.text;
298
- let toRecipients;
299
- if (typeof options.to === "string" && recipientName) {
300
- toRecipients = { address: options.to, name: recipientName };
301
- } else {
302
- toRecipients = options.to;
303
- }
304
- let from = options.from ?? this.defaultFrom;
305
- if (resolvedFromName && from) {
306
- const normalizedFrom = normalizeRecipient(from);
307
- from = { address: normalizedFrom.address, name: resolvedFromName };
308
- }
309
- return await this.sendRaw({
310
- to: toRecipients,
311
- from,
312
- subject: template.subject,
313
- html: rendered.html,
314
- text: plaintextBody,
315
- cc: options.cc,
316
- bcc: options.bcc,
317
- replyTo: options.replyTo,
318
- attachments: options.attachments,
319
- headers: options.headers,
320
- tags: options.tags,
321
- metadata: options.metadata
322
- });
323
- }
324
- /**
325
- * Dispatches a raw, pre-rendered email directly through the transport.
326
- * If `options.text` is omitted, plain text is automatically derived from `options.html`.
327
- */
328
- async sendRaw(options) {
329
- const from = options.from ?? this.defaultFrom;
330
- const text = options.text ?? (options.html ? htmlToPlainText(options.html) : void 0);
331
- return await this.transport.send({
332
- ...options,
333
- from,
334
- text
335
- });
336
- }
194
+ transport;
195
+ renderer;
196
+ defaultFrom;
197
+ defaultBrandName;
198
+ brandNameResolver;
199
+ constructor(options) {
200
+ if (!options.transport) throw new ConfigurationError("MailKit: A transport adapter (IEmailSender) is required.");
201
+ this.transport = options.transport;
202
+ this.renderer = options.renderer ?? new DefaultEmailRenderer();
203
+ this.defaultFrom = options.defaultFrom;
204
+ this.defaultBrandName = options.defaultBrandName;
205
+ this.brandNameResolver = options.brandNameResolver;
206
+ }
207
+ /**
208
+ * Returns the underlying transport instance.
209
+ */
210
+ getTransport() {
211
+ return this.transport;
212
+ }
213
+ /**
214
+ * Renders an email DTO to HTML and plain text without dispatching.
215
+ */
216
+ async render(template) {
217
+ return await this.renderer.render(template);
218
+ }
219
+ /**
220
+ * Sends an email using a strongly-typed template DTO.
221
+ *
222
+ * Example:
223
+ * ```ts
224
+ * await mailer.send({
225
+ * to: 'user@example.com',
226
+ * template: myDto,
227
+ * });
228
+ * ```
229
+ */
230
+ async send(options) {
231
+ const template = options.template;
232
+ const recipientName = options.toName ?? template.userName;
233
+ let resolvedFromName = options.fromName ?? template.fromName;
234
+ if (!resolvedFromName && this.brandNameResolver) resolvedFromName = await this.brandNameResolver();
235
+ if (!resolvedFromName) resolvedFromName = this.defaultBrandName;
236
+ const rendered = await this.renderer.render(template);
237
+ const plaintextBody = template.noHtmlMessage ?? rendered.text;
238
+ let toRecipients;
239
+ if (typeof options.to === "string" && recipientName) toRecipients = {
240
+ address: options.to,
241
+ name: recipientName
242
+ };
243
+ else toRecipients = options.to;
244
+ let from = options.from ?? this.defaultFrom;
245
+ if (resolvedFromName && from) from = {
246
+ address: normalizeRecipient(from).address,
247
+ name: resolvedFromName
248
+ };
249
+ return await this.sendRaw({
250
+ to: toRecipients,
251
+ from,
252
+ subject: template.subject,
253
+ html: rendered.html,
254
+ text: plaintextBody,
255
+ cc: options.cc,
256
+ bcc: options.bcc,
257
+ replyTo: options.replyTo,
258
+ attachments: options.attachments,
259
+ headers: options.headers,
260
+ tags: options.tags,
261
+ metadata: options.metadata
262
+ });
263
+ }
264
+ /**
265
+ * Dispatches a raw, pre-rendered email directly through the transport.
266
+ * If `options.text` is omitted, plain text is automatically derived from `options.html`.
267
+ */
268
+ async sendRaw(options) {
269
+ const from = options.from ?? this.defaultFrom;
270
+ const text = options.text ?? (options.html ? htmlToPlainText(options.html) : void 0);
271
+ return await this.transport.send({
272
+ ...options,
273
+ from,
274
+ text
275
+ });
276
+ }
337
277
  };
278
+ /**
279
+ * Convenience factory to create a MailKit client instance.
280
+ */
338
281
  function createMailKit(options) {
339
- return new MailKit(options);
282
+ return new MailKit(options);
340
283
  }
341
284
 
342
- // src/transports/zeptomail/zeptomail-sender.ts
285
+ //#endregion
286
+ //#region src/transports/zeptomail/zeptomail-sender.ts
287
+ /**
288
+ * ZeptoMail (Zoho Mail transactional email HTTP API) transport adapter.
289
+ *
290
+ * Designed for zero dependencies and universal edge compatibility (Node.js, Bun, Deno, Cloudflare Workers)
291
+ * by utilizing the standard global `fetch` API.
292
+ */
343
293
  var ZeptoMailHttpSender = class {
344
- name = "zeptomail";
345
- apiUrl;
346
- authHeader;
347
- defaultFrom;
348
- fetchImpl;
349
- constructor(config) {
350
- if (!config.apiToken || typeof config.apiToken !== "string") {
351
- throw new ConfigurationError("ZeptoMailHttpSender: Missing or invalid apiToken.");
352
- }
353
- if (!config.from) {
354
- throw new ConfigurationError("ZeptoMailHttpSender: Missing required default from address.");
355
- }
356
- const host = (config.apiHost || "api.zeptomail.com").replace(/^https?:\/\//, "").replace(/\/+$/, "");
357
- this.apiUrl = `https://${host}/v1.1/email`;
358
- const token = config.apiToken.trim();
359
- this.authHeader = token.startsWith("Zoho-") ? token : `Zoho-enczapikey ${token}`;
360
- this.defaultFrom = normalizeRecipient(config.from);
361
- if (!this.defaultFrom.address) {
362
- throw new ConfigurationError("ZeptoMailHttpSender: Configured from address must not be empty.");
363
- }
364
- this.fetchImpl = config.fetch ?? globalThis.fetch;
365
- if (typeof this.fetchImpl !== "function") {
366
- throw new ConfigurationError(
367
- "ZeptoMailHttpSender: No global fetch implementation found. Pass a custom fetch to ZeptoMailHttpConfig if running on Node < 18."
368
- );
369
- }
370
- }
371
- /**
372
- * Dispatches an email using Zoho ZeptoMail API v1.1.
373
- */
374
- async send(options) {
375
- const toRecipients = normalizeRecipients(options.to);
376
- if (toRecipients.length === 0) {
377
- throw new TransportError("ZeptoMailHttpSender: At least one recipient (to) must be specified.", {
378
- transportName: this.name
379
- });
380
- }
381
- const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
382
- const payload = {
383
- from: {
384
- address: resolvedFrom.address,
385
- name: resolvedFrom.name
386
- },
387
- to: toRecipients.map((r) => ({
388
- email_address: {
389
- address: r.address,
390
- name: r.name || ""
391
- }
392
- })),
393
- subject: options.subject,
394
- htmlbody: options.html,
395
- textbody: options.text
396
- };
397
- if (options.cc) {
398
- const ccList = normalizeRecipients(options.cc);
399
- if (ccList.length > 0) {
400
- payload.cc = ccList.map((r) => ({
401
- email_address: { address: r.address, name: r.name || "" }
402
- }));
403
- }
404
- }
405
- if (options.bcc) {
406
- const bccList = normalizeRecipients(options.bcc);
407
- if (bccList.length > 0) {
408
- payload.bcc = bccList.map((r) => ({
409
- email_address: { address: r.address, name: r.name || "" }
410
- }));
411
- }
412
- }
413
- if (options.replyTo) {
414
- const replyToList = normalizeRecipients(options.replyTo);
415
- if (replyToList.length > 0) {
416
- payload.reply_to = replyToList.map((r) => ({
417
- address: r.address,
418
- name: r.name
419
- }));
420
- }
421
- }
422
- if (options.attachments && options.attachments.length > 0) {
423
- payload.attachments = options.attachments.map((att) => {
424
- let contentBase64;
425
- if (typeof att.content === "string") {
426
- contentBase64 = att.content;
427
- } else {
428
- if (typeof Buffer !== "undefined") {
429
- contentBase64 = Buffer.from(att.content).toString("base64");
430
- } else {
431
- let binary = "";
432
- const bytes = new Uint8Array(att.content);
433
- for (let i = 0; i < bytes.byteLength; i++) {
434
- binary += String.fromCharCode(bytes[i]);
435
- }
436
- contentBase64 = btoa(binary);
437
- }
438
- }
439
- return {
440
- name: att.filename,
441
- content: contentBase64,
442
- mime_type: att.mimeType || att.contentType || "application/octet-stream",
443
- cid: att.cid
444
- };
445
- });
446
- }
447
- let response;
448
- try {
449
- response = await this.fetchImpl(this.apiUrl, {
450
- method: "POST",
451
- headers: {
452
- Accept: "application/json",
453
- "Content-Type": "application/json",
454
- Authorization: this.authHeader
455
- },
456
- body: JSON.stringify(payload)
457
- });
458
- } catch (err) {
459
- throw new TransportError(`ZeptoMailHttpSender: Network request failed: ${err instanceof Error ? err.message : String(err)}`, {
460
- transportName: this.name,
461
- cause: err
462
- });
463
- }
464
- const responseText = await response.text();
465
- let responseData;
466
- try {
467
- responseData = responseText ? JSON.parse(responseText) : {};
468
- } catch {
469
- responseData = { raw: responseText };
470
- }
471
- if (!response.ok) {
472
- const errorMessage = responseData?.error?.message || responseData?.message || `HTTP ${response.status}: ${responseText || response.statusText}`;
473
- throw new TransportError(`ZeptoMailHttpSender: Failed to send email - ${errorMessage}`, {
474
- transportName: this.name,
475
- statusCode: response.status,
476
- responseBody: responseText
477
- });
478
- }
479
- const messageId = responseData?.data?.[0]?.message_id;
480
- return {
481
- success: true,
482
- messageId,
483
- response: responseData
484
- };
485
- }
294
+ name = "zeptomail";
295
+ apiUrl;
296
+ authHeader;
297
+ defaultFrom;
298
+ fetchImpl;
299
+ constructor(config) {
300
+ if (!config.apiToken || typeof config.apiToken !== "string") throw new ConfigurationError("ZeptoMailHttpSender: Missing or invalid apiToken.");
301
+ if (!config.from) throw new ConfigurationError("ZeptoMailHttpSender: Missing required default from address.");
302
+ const host = (config.apiHost || "api.zeptomail.com").replace(/^https?:\/\//, "").replace(/\/+$/, "");
303
+ this.apiUrl = `https://${host}/v1.1/email`;
304
+ const token = config.apiToken.trim();
305
+ this.authHeader = token.startsWith("Zoho-") ? token : `Zoho-enczapikey ${token}`;
306
+ this.defaultFrom = normalizeRecipient(config.from);
307
+ if (!this.defaultFrom.address) throw new ConfigurationError("ZeptoMailHttpSender: Configured from address must not be empty.");
308
+ const fetchImpl = config.fetch ?? globalThis.fetch;
309
+ if (typeof fetchImpl !== "function") throw new ConfigurationError("ZeptoMailHttpSender: No global fetch implementation found. Pass a custom fetch to ZeptoMailHttpConfig if running on Node < 18.");
310
+ this.fetchImpl = fetchImpl.bind(globalThis);
311
+ }
312
+ /**
313
+ * Dispatches an email using Zoho ZeptoMail API v1.1.
314
+ */
315
+ async send(options) {
316
+ const toRecipients = normalizeRecipients(options.to);
317
+ if (toRecipients.length === 0) throw new TransportError("ZeptoMailHttpSender: At least one recipient (to) must be specified.", { transportName: this.name });
318
+ const resolvedFrom = options.from ? normalizeRecipient(options.from) : this.defaultFrom;
319
+ const payload = {
320
+ from: {
321
+ address: resolvedFrom.address,
322
+ name: resolvedFrom.name
323
+ },
324
+ to: toRecipients.map((r) => ({ email_address: {
325
+ address: r.address,
326
+ name: r.name || ""
327
+ } })),
328
+ subject: options.subject,
329
+ htmlbody: options.html,
330
+ textbody: options.text
331
+ };
332
+ if (options.cc) {
333
+ const ccList = normalizeRecipients(options.cc);
334
+ if (ccList.length > 0) payload.cc = ccList.map((r) => ({ email_address: {
335
+ address: r.address,
336
+ name: r.name || ""
337
+ } }));
338
+ }
339
+ if (options.bcc) {
340
+ const bccList = normalizeRecipients(options.bcc);
341
+ if (bccList.length > 0) payload.bcc = bccList.map((r) => ({ email_address: {
342
+ address: r.address,
343
+ name: r.name || ""
344
+ } }));
345
+ }
346
+ if (options.replyTo) {
347
+ const replyToList = normalizeRecipients(options.replyTo);
348
+ if (replyToList.length > 0) payload.reply_to = replyToList.map((r) => ({
349
+ address: r.address,
350
+ name: r.name
351
+ }));
352
+ }
353
+ if (options.attachments && options.attachments.length > 0) payload.attachments = options.attachments.map((att) => {
354
+ let contentBase64;
355
+ if (typeof att.content === "string") contentBase64 = att.content;
356
+ else if (typeof Buffer !== "undefined") contentBase64 = Buffer.from(att.content).toString("base64");
357
+ else {
358
+ let binary = "";
359
+ const bytes = new Uint8Array(att.content);
360
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
361
+ contentBase64 = btoa(binary);
362
+ }
363
+ return {
364
+ name: att.filename,
365
+ content: contentBase64,
366
+ mime_type: att.mimeType || att.contentType || "application/octet-stream",
367
+ cid: att.cid
368
+ };
369
+ });
370
+ let response;
371
+ try {
372
+ response = await this.fetchImpl(this.apiUrl, {
373
+ method: "POST",
374
+ headers: {
375
+ Accept: "application/json",
376
+ "Content-Type": "application/json",
377
+ Authorization: this.authHeader
378
+ },
379
+ body: JSON.stringify(payload)
380
+ });
381
+ } catch (err) {
382
+ throw new TransportError(`ZeptoMailHttpSender: Network request failed: ${err instanceof Error ? err.message : String(err)}`, {
383
+ transportName: this.name,
384
+ cause: err
385
+ });
386
+ }
387
+ const responseText = await response.text();
388
+ let responseData;
389
+ try {
390
+ responseData = responseText ? JSON.parse(responseText) : {};
391
+ } catch {
392
+ responseData = { raw: responseText };
393
+ }
394
+ if (!response.ok) {
395
+ const errorMessage = responseData?.error?.message || responseData?.message || `HTTP ${response.status}: ${responseText || response.statusText}`;
396
+ throw new TransportError(`ZeptoMailHttpSender: Failed to send email - ${errorMessage}`, {
397
+ transportName: this.name,
398
+ statusCode: response.status,
399
+ responseBody: responseText
400
+ });
401
+ }
402
+ return {
403
+ success: true,
404
+ messageId: responseData?.data?.[0]?.message_id,
405
+ response: responseData
406
+ };
407
+ }
486
408
  };
487
409
 
488
- // src/transports/memory/memory-sender.ts
410
+ //#endregion
411
+ //#region src/transports/memory/memory-sender.ts
412
+ /**
413
+ * In-memory email sender for automated testing, local development, and CI environments.
414
+ * Captures all dispatched emails and provides inspection methods.
415
+ */
489
416
  var MemoryEmailSender = class {
490
- name = "memory";
491
- sentMails = [];
492
- nextId = 1;
493
- async send(options) {
494
- this.sentMails.push({ ...options });
495
- const messageId = `mem-${this.nextId++}-${Date.now()}`;
496
- return {
497
- success: true,
498
- messageId,
499
- response: { id: messageId }
500
- };
501
- }
502
- /**
503
- * Returns a copy of all emails sent through this transport instance.
504
- */
505
- getSentMails() {
506
- return [...this.sentMails];
507
- }
508
- /**
509
- * Returns the most recent email sent, or undefined if no emails have been sent.
510
- */
511
- getLastMail() {
512
- return this.sentMails.length > 0 ? { ...this.sentMails[this.sentMails.length - 1] } : void 0;
513
- }
514
- /**
515
- * Clears all recorded sent emails.
516
- */
517
- clear() {
518
- this.sentMails = [];
519
- }
520
- /**
521
- * Returns the total count of sent emails.
522
- */
523
- count() {
524
- return this.sentMails.length;
525
- }
417
+ name = "memory";
418
+ sentMails = [];
419
+ nextId = 1;
420
+ async send(options) {
421
+ this.sentMails.push({ ...options });
422
+ const messageId = `mem-${this.nextId++}-${Date.now()}`;
423
+ return {
424
+ success: true,
425
+ messageId,
426
+ response: { id: messageId }
427
+ };
428
+ }
429
+ /**
430
+ * Returns a copy of all emails sent through this transport instance.
431
+ */
432
+ getSentMails() {
433
+ return [...this.sentMails];
434
+ }
435
+ /**
436
+ * Returns the most recent email sent, or undefined if no emails have been sent.
437
+ */
438
+ getLastMail() {
439
+ return this.sentMails.length > 0 ? { ...this.sentMails[this.sentMails.length - 1] } : void 0;
440
+ }
441
+ /**
442
+ * Clears all recorded sent emails.
443
+ */
444
+ clear() {
445
+ this.sentMails = [];
446
+ }
447
+ /**
448
+ * Returns the total count of sent emails.
449
+ */
450
+ count() {
451
+ return this.sentMails.length;
452
+ }
526
453
  };
527
454
 
528
- // src/transports/console/console-sender.ts
455
+ //#endregion
456
+ //#region src/transports/console/console-sender.ts
457
+ /**
458
+ * Console logger transport that prints dispatched emails to stdout/stderr.
459
+ * Ideal for development and debugging.
460
+ */
529
461
  var ConsoleEmailSender = class {
530
- name = "console";
531
- log;
532
- includeHtml;
533
- nextId = 1;
534
- constructor(options = {}) {
535
- this.log = options.logger ?? console.log;
536
- this.includeHtml = options.includeHtml ?? false;
537
- }
538
- async send(options) {
539
- const to = normalizeRecipients(options.to).map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
540
- const from = options.from ? JSON.stringify(options.from) : "(default)";
541
- const messageId = `console-${this.nextId++}-${Date.now()}`;
542
- const separator = "\u2500".repeat(60);
543
- const lines = [
544
- `
545
- \u250C${separator}\u2510`,
546
- `\u2502 [ts-mailkit:console] DISPATCHED EMAIL (${messageId})`,
547
- `\u251C${separator}\u2524`,
548
- `\u2502 From: ${from}`,
549
- `\u2502 To: ${to}`,
550
- `\u2502 Subject: ${options.subject}`,
551
- `\u251C${separator}\u2524`
552
- ];
553
- if (options.text) {
554
- lines.push("\u2502 Text Body:");
555
- for (const line of options.text.split("\n")) {
556
- lines.push(`\u2502 ${line}`);
557
- }
558
- }
559
- if (this.includeHtml && options.html) {
560
- lines.push("\u2502 HTML Body:");
561
- for (const line of options.html.split("\n")) {
562
- lines.push(`\u2502 ${line}`);
563
- }
564
- }
565
- lines.push(`\u2514${separator}\u2518
566
- `);
567
- this.log(lines.join("\n"));
568
- return {
569
- success: true,
570
- messageId,
571
- response: { logged: true }
572
- };
573
- }
462
+ name = "console";
463
+ log;
464
+ includeHtml;
465
+ nextId = 1;
466
+ constructor(options = {}) {
467
+ this.log = options.logger ?? console.log;
468
+ this.includeHtml = options.includeHtml ?? false;
469
+ }
470
+ async send(options) {
471
+ const to = normalizeRecipients(options.to).map((r) => r.name ? `${r.name} <${r.address}>` : r.address).join(", ");
472
+ const from = options.from ? JSON.stringify(options.from) : "(default)";
473
+ const messageId = `console-${this.nextId++}-${Date.now()}`;
474
+ const separator = "─".repeat(60);
475
+ const lines = [
476
+ `\n┌${separator}┐`,
477
+ `│ [ts-mailkit:console] DISPATCHED EMAIL (${messageId})`,
478
+ `├${separator}┤`,
479
+ `│ From: ${from}`,
480
+ `│ To: ${to}`,
481
+ `│ Subject: ${options.subject}`,
482
+ `├${separator}┤`
483
+ ];
484
+ if (options.text) {
485
+ lines.push("│ Text Body:");
486
+ for (const line of options.text.split("\n")) lines.push(`│ ${line}`);
487
+ }
488
+ if (this.includeHtml && options.html) {
489
+ lines.push("│ HTML Body:");
490
+ for (const line of options.html.split("\n")) lines.push(`│ ${line}`);
491
+ }
492
+ lines.push(`└${separator}┘\n`);
493
+ this.log(lines.join("\n"));
494
+ return {
495
+ success: true,
496
+ messageId,
497
+ response: { logged: true }
498
+ };
499
+ }
574
500
  };
575
501
 
576
- // src/templates/factory.ts
577
- var DEFAULT_SECURITY_MESSAGE = "If you did not request this, please ignore this email or contact support if you have concerns.";
502
+ //#endregion
503
+ //#region src/templates/factory.ts
504
+ const DEFAULT_SECURITY_MESSAGE = "If you did not request this, please ignore this email or contact support if you have concerns.";
578
505
  function defaultFooter(brandName) {
579
- return `&copy; ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}. All rights reserved.`;
506
+ return `&copy; ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}. All rights reserved.`;
580
507
  }
581
- var EmailTemplateFactory = class _EmailTemplateFactory {
582
- defaultBrandName;
583
- defaultSecurityMessage;
584
- footerResolver;
585
- defaultLogoUrl;
586
- constructor(config = {}) {
587
- this.defaultBrandName = config.brandName || "Application";
588
- this.defaultSecurityMessage = config.securityMessage || DEFAULT_SECURITY_MESSAGE;
589
- this.defaultLogoUrl = config.logoUrl;
590
- if (typeof config.footer === "function") {
591
- this.footerResolver = config.footer;
592
- } else if (typeof config.footer === "string") {
593
- const fixedFooter = config.footer;
594
- this.footerResolver = () => fixedFooter;
595
- } else {
596
- this.footerResolver = defaultFooter;
597
- }
598
- }
599
- resolveBrand(override) {
600
- return override || this.defaultBrandName;
601
- }
602
- buildFooter(brandName) {
603
- return this.footerResolver(brandName);
604
- }
605
- /**
606
- * Welcome / email-verification email sent after registration.
607
- */
608
- createEmailVerification(params) {
609
- const { username, verificationUrl, otp, brandName } = params;
610
- const brand = this.resolveBrand(brandName);
611
- const body = `Hi ${username},<br/>Thank you for signing up. Use the code below or click the button to verify your email address.`;
612
- return {
613
- subject: "Verify Your Email Address",
614
- header: `Welcome to ${brand}!`,
615
- body,
616
- logoUrl: this.defaultLogoUrl,
617
- otpCode: otp,
618
- actionUrl: verificationUrl,
619
- actionText: "Verify Email",
620
- extraMessage: this.defaultSecurityMessage,
621
- footer: this.buildFooter(brand),
622
- userName: username,
623
- fromName: brand,
624
- noHtmlMessage: htmlToPlainText(body) + `
625
-
626
- Your verification code is: ${otp}
627
-
628
- Alternatively, verify here:
629
- ${verificationUrl}`
630
- };
631
- }
632
- /**
633
- * Password-reset email with an optional OTP code and action link.
634
- */
635
- createPasswordReset(params) {
636
- const { username, resetUrl, otp = "", brandName } = params;
637
- const brand = this.resolveBrand(brandName);
638
- const body = `Hi ${username},<br/>We received a request to reset your password. Use the OTP code below or click the button to proceed.`;
639
- const plainTextOtp = otp ? `
640
-
641
- Your OTP code is: ${otp}` : "";
642
- return {
643
- subject: "Password Reset Request",
644
- header: "Reset Your Password",
645
- body,
646
- logoUrl: this.defaultLogoUrl,
647
- otpCode: otp || void 0,
648
- actionUrl: resetUrl,
649
- actionText: "Reset Password",
650
- extraMessage: this.defaultSecurityMessage,
651
- footer: this.buildFooter(brand),
652
- userName: username,
653
- fromName: brand,
654
- noHtmlMessage: htmlToPlainText(body) + plainTextOtp + `
655
-
656
- Alternatively, you can use the following link to reset your password:
657
- ${resetUrl}`
658
- };
659
- }
660
- /**
661
- * Generic plain message email (no action button, no OTP).
662
- */
663
- createSimpleMessage(params) {
664
- const { subject, header, body, brandName } = params;
665
- const brand = this.resolveBrand(brandName);
666
- return {
667
- subject,
668
- header,
669
- body,
670
- logoUrl: this.defaultLogoUrl,
671
- footer: this.buildFooter(brand),
672
- fromName: brand,
673
- noHtmlMessage: htmlToPlainText(body)
674
- };
675
- }
676
- /**
677
- * Newsletter / announcement email with an optional hero image and CTA button.
678
- */
679
- createNewsLetter(params) {
680
- const { subject, header, body, imageUrl, actionUrl, actionText, brandName, logoUrl } = params;
681
- const brand = this.resolveBrand(brandName);
682
- let noHtmlMessage = htmlToPlainText(body);
683
- if (actionUrl) {
684
- noHtmlMessage += `
685
-
686
- ${actionText || "Learn more"}: ${actionUrl}`;
687
- }
688
- return {
689
- subject,
690
- header,
691
- body,
692
- logoUrl: logoUrl || this.defaultLogoUrl,
693
- imageUrl,
694
- actionUrl,
695
- actionText,
696
- footer: this.buildFooter(brand),
697
- fromName: brand,
698
- noHtmlMessage
699
- };
700
- }
701
- /**
702
- * Two-factor authentication email with OTP code and optional magic link.
703
- */
704
- create2FAEmail(params) {
705
- const { username, otp, magicLink = "", brandName } = params;
706
- const brand = this.resolveBrand(brandName);
707
- const body = `Hi ${username},<br/>Use the code below or click the button to complete your login.`;
708
- const plainLink = magicLink ? `
709
-
710
- Alternatively, verify here:
711
- ${magicLink}` : "";
712
- return {
713
- subject: "Your Login Verification Code",
714
- header: "Two-Factor Authentication",
715
- body,
716
- logoUrl: this.defaultLogoUrl,
717
- otpCode: otp,
718
- actionUrl: magicLink || void 0,
719
- actionText: magicLink ? "Verify Login" : void 0,
720
- extraMessage: this.defaultSecurityMessage,
721
- footer: this.buildFooter(brand),
722
- userName: username,
723
- fromName: brand,
724
- noHtmlMessage: htmlToPlainText(body) + `
725
-
726
- Your verification code is: ${otp}` + plainLink
727
- };
728
- }
729
- /**
730
- * Magic link sign-in email (no OTP, button only).
731
- */
732
- createMagicLinkEmail(params) {
733
- const { username, magicLink, brandName } = params;
734
- const brand = this.resolveBrand(brandName);
735
- const body = `Hi ${username},<br/>Click the button below to sign in. No password needed.`;
736
- return {
737
- subject: "Your Magic Link",
738
- header: `Sign In to ${brand}`,
739
- body,
740
- logoUrl: this.defaultLogoUrl,
741
- actionUrl: magicLink,
742
- actionText: "Sign In",
743
- extraMessage: this.defaultSecurityMessage,
744
- footer: this.buildFooter(brand),
745
- userName: username,
746
- fromName: brand,
747
- noHtmlMessage: htmlToPlainText(body) + `
748
-
749
- Sign in here:
750
- ${magicLink}`
751
- };
752
- }
753
- // ─── Default Static Instance ──────────────────────────────────────────────
754
- static _defaultInstance;
755
- static get defaultInstance() {
756
- if (!this._defaultInstance) {
757
- this._defaultInstance = new _EmailTemplateFactory();
758
- }
759
- return this._defaultInstance;
760
- }
761
- static createEmailVerification(params) {
762
- return this.defaultInstance.createEmailVerification(params);
763
- }
764
- static createPasswordReset(params) {
765
- return this.defaultInstance.createPasswordReset(params);
766
- }
767
- static createSimpleMessage(params) {
768
- return this.defaultInstance.createSimpleMessage(params);
769
- }
770
- static createNewsLetter(params) {
771
- return this.defaultInstance.createNewsLetter(params);
772
- }
773
- static create2FAEmail(params) {
774
- return this.defaultInstance.create2FAEmail(params);
775
- }
776
- static createMagicLinkEmail(params) {
777
- return this.defaultInstance.createMagicLinkEmail(params);
778
- }
508
+ /**
509
+ * Factory for creating pre-populated, type-safe email template DTOs.
510
+ */
511
+ var EmailTemplateFactory = class EmailTemplateFactory {
512
+ defaultBrandName;
513
+ defaultSecurityMessage;
514
+ footerResolver;
515
+ defaultLogoUrl;
516
+ constructor(config = {}) {
517
+ this.defaultBrandName = config.brandName || "Application";
518
+ this.defaultSecurityMessage = config.securityMessage || DEFAULT_SECURITY_MESSAGE;
519
+ this.defaultLogoUrl = config.logoUrl;
520
+ if (typeof config.footer === "function") this.footerResolver = config.footer;
521
+ else if (typeof config.footer === "string") {
522
+ const fixedFooter = config.footer;
523
+ this.footerResolver = () => fixedFooter;
524
+ } else this.footerResolver = defaultFooter;
525
+ }
526
+ resolveBrand(override) {
527
+ return override || this.defaultBrandName;
528
+ }
529
+ buildFooter(brandName) {
530
+ return this.footerResolver(brandName);
531
+ }
532
+ /**
533
+ * Welcome / email-verification email sent after registration.
534
+ */
535
+ createEmailVerification(params) {
536
+ const { username, verificationUrl, otp, brandName } = params;
537
+ const brand = this.resolveBrand(brandName);
538
+ const body = `Hi ${username},<br/>Thank you for signing up. Use the code below or click the button to verify your email address.`;
539
+ return {
540
+ subject: "Verify Your Email Address",
541
+ header: `Welcome to ${brand}!`,
542
+ body,
543
+ logoUrl: this.defaultLogoUrl,
544
+ otpCode: otp,
545
+ actionUrl: verificationUrl,
546
+ actionText: "Verify Email",
547
+ extraMessage: this.defaultSecurityMessage,
548
+ footer: this.buildFooter(brand),
549
+ userName: username,
550
+ fromName: brand,
551
+ noHtmlMessage: htmlToPlainText(body) + `\n\nYour verification code is: ${otp}\n\nAlternatively, verify here:\n${verificationUrl}`
552
+ };
553
+ }
554
+ /**
555
+ * Password-reset email with an optional OTP code and action link.
556
+ */
557
+ createPasswordReset(params) {
558
+ const { username, resetUrl, otp = "", brandName } = params;
559
+ const brand = this.resolveBrand(brandName);
560
+ const body = `Hi ${username},<br/>We received a request to reset your password. Use the OTP code below or click the button to proceed.`;
561
+ const plainTextOtp = otp ? `\n\nYour OTP code is: ${otp}` : "";
562
+ return {
563
+ subject: "Password Reset Request",
564
+ header: "Reset Your Password",
565
+ body,
566
+ logoUrl: this.defaultLogoUrl,
567
+ otpCode: otp || void 0,
568
+ actionUrl: resetUrl,
569
+ actionText: "Reset Password",
570
+ extraMessage: this.defaultSecurityMessage,
571
+ footer: this.buildFooter(brand),
572
+ userName: username,
573
+ fromName: brand,
574
+ noHtmlMessage: htmlToPlainText(body) + plainTextOtp + `\n\nAlternatively, you can use the following link to reset your password:\n${resetUrl}`
575
+ };
576
+ }
577
+ /**
578
+ * Generic plain message email (no action button, no OTP).
579
+ */
580
+ createSimpleMessage(params) {
581
+ const { subject, header, body, brandName } = params;
582
+ const brand = this.resolveBrand(brandName);
583
+ return {
584
+ subject,
585
+ header,
586
+ body,
587
+ logoUrl: this.defaultLogoUrl,
588
+ footer: this.buildFooter(brand),
589
+ fromName: brand,
590
+ noHtmlMessage: htmlToPlainText(body)
591
+ };
592
+ }
593
+ /**
594
+ * Newsletter / announcement email with an optional hero image and CTA button.
595
+ */
596
+ createNewsLetter(params) {
597
+ const { subject, header, body, imageUrl, actionUrl, actionText, brandName, logoUrl } = params;
598
+ const brand = this.resolveBrand(brandName);
599
+ let noHtmlMessage = htmlToPlainText(body);
600
+ if (actionUrl) noHtmlMessage += `\n\n${actionText || "Learn more"}: ${actionUrl}`;
601
+ return {
602
+ subject,
603
+ header,
604
+ body,
605
+ logoUrl: logoUrl || this.defaultLogoUrl,
606
+ imageUrl,
607
+ actionUrl,
608
+ actionText,
609
+ footer: this.buildFooter(brand),
610
+ fromName: brand,
611
+ noHtmlMessage
612
+ };
613
+ }
614
+ /**
615
+ * Two-factor authentication email with OTP code and optional magic link.
616
+ */
617
+ create2FAEmail(params) {
618
+ const { username, otp, magicLink = "", brandName } = params;
619
+ const brand = this.resolveBrand(brandName);
620
+ const body = `Hi ${username},<br/>Use the code below or click the button to complete your login.`;
621
+ const plainLink = magicLink ? `\n\nAlternatively, verify here:\n${magicLink}` : "";
622
+ return {
623
+ subject: "Your Login Verification Code",
624
+ header: "Two-Factor Authentication",
625
+ body,
626
+ logoUrl: this.defaultLogoUrl,
627
+ otpCode: otp,
628
+ actionUrl: magicLink || void 0,
629
+ actionText: magicLink ? "Verify Login" : void 0,
630
+ extraMessage: this.defaultSecurityMessage,
631
+ footer: this.buildFooter(brand),
632
+ userName: username,
633
+ fromName: brand,
634
+ noHtmlMessage: htmlToPlainText(body) + `\n\nYour verification code is: ${otp}` + plainLink
635
+ };
636
+ }
637
+ /**
638
+ * Magic link sign-in email (no OTP, button only).
639
+ */
640
+ createMagicLinkEmail(params) {
641
+ const { username, magicLink, brandName } = params;
642
+ const brand = this.resolveBrand(brandName);
643
+ const body = `Hi ${username},<br/>Click the button below to sign in. No password needed.`;
644
+ return {
645
+ subject: "Your Magic Link",
646
+ header: `Sign In to ${brand}`,
647
+ body,
648
+ logoUrl: this.defaultLogoUrl,
649
+ actionUrl: magicLink,
650
+ actionText: "Sign In",
651
+ extraMessage: this.defaultSecurityMessage,
652
+ footer: this.buildFooter(brand),
653
+ userName: username,
654
+ fromName: brand,
655
+ noHtmlMessage: htmlToPlainText(body) + `\n\nSign in here:\n${magicLink}`
656
+ };
657
+ }
658
+ static _defaultInstance;
659
+ static get defaultInstance() {
660
+ if (!this._defaultInstance) this._defaultInstance = new EmailTemplateFactory();
661
+ return this._defaultInstance;
662
+ }
663
+ static createEmailVerification(params) {
664
+ return this.defaultInstance.createEmailVerification(params);
665
+ }
666
+ static createPasswordReset(params) {
667
+ return this.defaultInstance.createPasswordReset(params);
668
+ }
669
+ static createSimpleMessage(params) {
670
+ return this.defaultInstance.createSimpleMessage(params);
671
+ }
672
+ static createNewsLetter(params) {
673
+ return this.defaultInstance.createNewsLetter(params);
674
+ }
675
+ static create2FAEmail(params) {
676
+ return this.defaultInstance.create2FAEmail(params);
677
+ }
678
+ static createMagicLinkEmail(params) {
679
+ return this.defaultInstance.createMagicLinkEmail(params);
680
+ }
779
681
  };
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
- });
682
+
683
+ //#endregion
684
+ exports.ConfigurationError = ConfigurationError;
685
+ exports.ConsoleEmailSender = ConsoleEmailSender;
686
+ exports.DefaultEmailRenderer = DefaultEmailRenderer;
687
+ exports.EmailTemplateFactory = EmailTemplateFactory;
688
+ exports.MailKit = MailKit;
689
+ exports.MailKitError = MailKitError;
690
+ exports.MemoryEmailSender = MemoryEmailSender;
691
+ exports.TemplateError = TemplateError;
692
+ exports.TransportError = TransportError;
693
+ exports.ZeptoMailHttpSender = ZeptoMailHttpSender;
694
+ exports.buildHtml = buildHtml;
695
+ exports.buildPlainText = buildPlainText;
696
+ exports.createMailKit = createMailKit;
697
+ exports.decodeHtmlEntities = decodeHtmlEntities;
698
+ exports.htmlToPlainText = htmlToPlainText;
699
+ exports.isValidHttpUrl = isValidHttpUrl;
700
+ exports.normalizeRecipient = normalizeRecipient;
701
+ exports.normalizeRecipients = normalizeRecipients;
702
+ exports.parseEmailAddress = parseEmailAddress;
802
703
  //# sourceMappingURL=index.cjs.map