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