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.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/dist/core/client.d.ts +128 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/errors.d.ts +34 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/types.d.ts +63 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.cjs +794 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +750 -0
- package/dist/index.js.map +1 -0
- package/dist/renderers/default/default-renderer.d.ts +36 -0
- package/dist/renderers/default/default-renderer.d.ts.map +1 -0
- package/dist/renderers/renderer.interface.d.ts +18 -0
- package/dist/renderers/renderer.interface.d.ts.map +1 -0
- package/dist/renderers/utils/html-to-plaintext.d.ts +10 -0
- package/dist/renderers/utils/html-to-plaintext.d.ts.map +1 -0
- package/dist/templates/dto/index.d.ts +99 -0
- package/dist/templates/dto/index.d.ts.map +1 -0
- package/dist/templates/factory.d.ts +60 -0
- package/dist/templates/factory.d.ts.map +1 -0
- package/dist/transports/console/console-sender.d.ts +26 -0
- package/dist/transports/console/console-sender.d.ts.map +1 -0
- package/dist/transports/memory/memory-sender.d.ts +29 -0
- package/dist/transports/memory/memory-sender.d.ts.map +1 -0
- package/dist/transports/sender.interface.d.ts +20 -0
- package/dist/transports/sender.interface.d.ts.map +1 -0
- package/dist/transports/zeptomail/types.d.ts +47 -0
- package/dist/transports/zeptomail/types.d.ts.map +1 -0
- package/dist/transports/zeptomail/zeptomail-sender.d.ts +27 -0
- package/dist/transports/zeptomail/zeptomail-sender.d.ts.map +1 -0
- 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(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").replace(/©/gi, "\xA9").replace(/®/gi, "\xAE").replace(/™/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 `© ${(/* @__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
|