@dotpress/mailer 0.0.0-stage → 2.0.0-alpha.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/LICENSE +21 -0
- package/README.md +491 -2
- package/dist/aws-ses.d.ts +30 -0
- package/dist/aws-ses.js +108 -0
- package/dist/aws-ses.js.map +1 -0
- package/dist/brevo.d.ts +23 -0
- package/dist/brevo.js +115 -0
- package/dist/brevo.js.map +1 -0
- package/dist/chunk-2ZABFOZQ.js +15 -0
- package/dist/chunk-2ZABFOZQ.js.map +1 -0
- package/dist/chunk-FRMKUAF4.js +88 -0
- package/dist/chunk-FRMKUAF4.js.map +1 -0
- package/dist/chunk-GOAXQ2GE.js +28 -0
- package/dist/chunk-GOAXQ2GE.js.map +1 -0
- package/dist/chunk-QYVN5N3B.js +221 -0
- package/dist/chunk-QYVN5N3B.js.map +1 -0
- package/dist/http-B35l9W1d.d.ts +9 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +649 -0
- package/dist/index.js.map +1 -0
- package/dist/mailer-CmQCJ0qc.d.ts +339 -0
- package/dist/mailgun.d.ts +31 -0
- package/dist/mailgun.js +100 -0
- package/dist/mailgun.js.map +1 -0
- package/dist/mailjet.d.ts +28 -0
- package/dist/mailjet.js +104 -0
- package/dist/mailjet.js.map +1 -0
- package/dist/plugin.d.ts +33 -0
- package/dist/plugin.js +21 -0
- package/dist/plugin.js.map +1 -0
- package/dist/postmark.d.ts +27 -0
- package/dist/postmark.js +149 -0
- package/dist/postmark.js.map +1 -0
- package/dist/resend.d.ts +31 -0
- package/dist/resend.js +92 -0
- package/dist/resend.js.map +1 -0
- package/dist/scaleway.d.ts +31 -0
- package/dist/scaleway.js +113 -0
- package/dist/scaleway.js.map +1 -0
- package/dist/sendgrid.d.ts +26 -0
- package/dist/sendgrid.js +121 -0
- package/dist/sendgrid.js.map +1 -0
- package/dist/smtp.d.ts +45 -0
- package/dist/smtp.js +61 -0
- package/dist/smtp.js.map +1 -0
- package/package.json +139 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MailDataError,
|
|
3
|
+
MailMessageError,
|
|
4
|
+
MailRenderError,
|
|
5
|
+
MailSendError,
|
|
6
|
+
MailerError,
|
|
7
|
+
checkKeys,
|
|
8
|
+
formatAddress,
|
|
9
|
+
parseAddress,
|
|
10
|
+
parseEnvelope,
|
|
11
|
+
parseRecipients,
|
|
12
|
+
toSubject
|
|
13
|
+
} from "./chunk-QYVN5N3B.js";
|
|
14
|
+
|
|
15
|
+
// src/render.ts
|
|
16
|
+
import mustache from "mustache";
|
|
17
|
+
|
|
18
|
+
// src/locales.ts
|
|
19
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20
|
+
var collectLocales = (prefix, entries) => {
|
|
21
|
+
let locales;
|
|
22
|
+
let reference = "";
|
|
23
|
+
for (const [label, value] of entries) {
|
|
24
|
+
if (!isRecord(value)) {
|
|
25
|
+
throw new TypeError(
|
|
26
|
+
`${prefix}: ${label} must be an object of texts by locale.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const keys = Object.keys(value).sort();
|
|
30
|
+
if (keys.length === 0) {
|
|
31
|
+
throw new TypeError(`${prefix}: ${label} has no locale.`);
|
|
32
|
+
}
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
if (typeof value[key] !== "string") {
|
|
35
|
+
throw new TypeError(`${prefix}: ${label}.${key} must be a string.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (locales === void 0) {
|
|
39
|
+
locales = keys;
|
|
40
|
+
reference = label;
|
|
41
|
+
} else if (keys.join() !== locales.join()) {
|
|
42
|
+
throw new TypeError(
|
|
43
|
+
`${prefix}: ${label} has the locales ${keys.join(", ")}, but ${reference} has ${locales.join(", ")}. Every text needs the same locales.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return locales ?? [];
|
|
48
|
+
};
|
|
49
|
+
var translationEntries = (prefix, translations) => {
|
|
50
|
+
if (translations === void 0) return [];
|
|
51
|
+
if (!isRecord(translations)) {
|
|
52
|
+
throw new TypeError(`${prefix}: translations must be an object.`);
|
|
53
|
+
}
|
|
54
|
+
return Object.entries(translations).map(([key, value]) => [
|
|
55
|
+
`translations.${key}`,
|
|
56
|
+
value
|
|
57
|
+
]);
|
|
58
|
+
};
|
|
59
|
+
var baseOf = (locale) => locale.split(/[-_]/)[0] ?? locale;
|
|
60
|
+
var resolveLocale = (available, requested, defaultLocale) => {
|
|
61
|
+
const candidates = [requested, defaultLocale].flatMap(
|
|
62
|
+
(locale) => locale ? [locale, baseOf(locale)] : []
|
|
63
|
+
);
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
const lower = candidate.toLowerCase();
|
|
66
|
+
const match = available.find((locale) => locale.toLowerCase() === lower);
|
|
67
|
+
if (match !== void 0) return match;
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/template.ts
|
|
73
|
+
var layouts = /* @__PURE__ */ new WeakMap();
|
|
74
|
+
var templates = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
var definitionOf = (template) => templates.get(template);
|
|
76
|
+
var LAYOUT_OPTIONS = /* @__PURE__ */ new Set(["translations", "mjml", "html"]);
|
|
77
|
+
var TEMPLATE_OPTIONS = /* @__PURE__ */ new Set([
|
|
78
|
+
"name",
|
|
79
|
+
"layout",
|
|
80
|
+
"schema",
|
|
81
|
+
"subject",
|
|
82
|
+
"translations",
|
|
83
|
+
"text",
|
|
84
|
+
"mjml",
|
|
85
|
+
"html"
|
|
86
|
+
]);
|
|
87
|
+
var checkOptions = (prefix, options, allowed) => {
|
|
88
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
89
|
+
throw new TypeError(`${prefix}: expected an object.`);
|
|
90
|
+
}
|
|
91
|
+
for (const key of Object.keys(options)) {
|
|
92
|
+
if (!allowed.has(key)) {
|
|
93
|
+
throw new TypeError(`${prefix}: unknown option "${key}".`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return options;
|
|
97
|
+
};
|
|
98
|
+
var formatOf = (prefix, options, isValid, expected) => {
|
|
99
|
+
const formats = ["mjml", "html"].filter(
|
|
100
|
+
(format2) => options[format2] !== void 0
|
|
101
|
+
);
|
|
102
|
+
const [format] = formats;
|
|
103
|
+
if (format === void 0 || formats.length > 1) {
|
|
104
|
+
throw new TypeError(`${prefix}: set exactly one of mjml and html.`);
|
|
105
|
+
}
|
|
106
|
+
if (!isValid(options[format])) {
|
|
107
|
+
throw new TypeError(`${prefix}: ${format} must be ${expected}.`);
|
|
108
|
+
}
|
|
109
|
+
return format;
|
|
110
|
+
};
|
|
111
|
+
var isSource = (source) => typeof source === "string" || typeof source === "function";
|
|
112
|
+
var defineLayout = (options) => {
|
|
113
|
+
const prefix = "Invalid mail layout";
|
|
114
|
+
const values = checkOptions(prefix, options, LAYOUT_OPTIONS);
|
|
115
|
+
const format = formatOf(
|
|
116
|
+
prefix,
|
|
117
|
+
values,
|
|
118
|
+
(source) => typeof source === "function",
|
|
119
|
+
"a function that receives the template content"
|
|
120
|
+
);
|
|
121
|
+
const locales = collectLocales(
|
|
122
|
+
prefix,
|
|
123
|
+
translationEntries(prefix, values.translations)
|
|
124
|
+
);
|
|
125
|
+
const layout = Object.freeze({
|
|
126
|
+
format,
|
|
127
|
+
locales: Object.freeze(locales)
|
|
128
|
+
});
|
|
129
|
+
layouts.set(layout, {
|
|
130
|
+
format,
|
|
131
|
+
translations: values.translations ?? {},
|
|
132
|
+
render: values[format]
|
|
133
|
+
});
|
|
134
|
+
return layout;
|
|
135
|
+
};
|
|
136
|
+
var defineTemplate = (options) => {
|
|
137
|
+
const name = options?.name;
|
|
138
|
+
const prefix = typeof name === "string" && name !== "" ? `Invalid mail template "${name}"` : "Invalid mail template";
|
|
139
|
+
const values = checkOptions(prefix, options, TEMPLATE_OPTIONS);
|
|
140
|
+
if (typeof name !== "string" || name === "") {
|
|
141
|
+
throw new TypeError(`${prefix}: name must be a non-empty string.`);
|
|
142
|
+
}
|
|
143
|
+
const format = formatOf(prefix, values, isSource, "a string or a function");
|
|
144
|
+
const { layout, schema, subject, text } = values;
|
|
145
|
+
let layoutDefinition;
|
|
146
|
+
if (layout !== void 0) {
|
|
147
|
+
layoutDefinition = layouts.get(layout);
|
|
148
|
+
if (!layoutDefinition) {
|
|
149
|
+
throw new TypeError(
|
|
150
|
+
`${prefix}: layout must be created by defineLayout().`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (layoutDefinition.format !== format) {
|
|
154
|
+
throw new TypeError(
|
|
155
|
+
`${prefix}: the template is in ${format}, but its layout is in ${layoutDefinition.format}.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (schema !== void 0 && typeof schema?.safeParseAsync !== "function") {
|
|
160
|
+
throw new TypeError(`${prefix}: schema must be a Zod schema.`);
|
|
161
|
+
}
|
|
162
|
+
if (typeof subject !== "string" && typeof subject !== "object") {
|
|
163
|
+
throw new TypeError(
|
|
164
|
+
`${prefix}: subject must be a string or an object of texts by locale.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (text !== void 0 && !isSource(text)) {
|
|
168
|
+
throw new TypeError(`${prefix}: text must be a string or a function.`);
|
|
169
|
+
}
|
|
170
|
+
const own = collectLocales(prefix, [
|
|
171
|
+
...typeof subject === "object" ? [["subject", subject]] : [],
|
|
172
|
+
...translationEntries(prefix, values.translations)
|
|
173
|
+
]);
|
|
174
|
+
const layoutLocales = layout ? layout.locales : [];
|
|
175
|
+
if (own.length > 0 && layoutLocales.length > 0) {
|
|
176
|
+
const missing = own.filter((locale) => !layoutLocales.includes(locale));
|
|
177
|
+
if (missing.length > 0) {
|
|
178
|
+
throw new TypeError(
|
|
179
|
+
`${prefix}: its layout has no translations in ${missing.join(", ")}.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const locales = own.length > 0 ? own : [...layoutLocales];
|
|
184
|
+
const template = Object.freeze({
|
|
185
|
+
name,
|
|
186
|
+
format,
|
|
187
|
+
locales: Object.freeze(locales)
|
|
188
|
+
});
|
|
189
|
+
templates.set(template, {
|
|
190
|
+
name,
|
|
191
|
+
format,
|
|
192
|
+
locales,
|
|
193
|
+
source: values[format],
|
|
194
|
+
layout: layoutDefinition,
|
|
195
|
+
schema,
|
|
196
|
+
subject,
|
|
197
|
+
translations: values.translations ?? {},
|
|
198
|
+
text,
|
|
199
|
+
cache: /* @__PURE__ */ new Map()
|
|
200
|
+
});
|
|
201
|
+
return template;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/render.ts
|
|
205
|
+
var HTML_ENTITIES = {
|
|
206
|
+
"&": "&",
|
|
207
|
+
"<": "<",
|
|
208
|
+
">": ">",
|
|
209
|
+
'"': """,
|
|
210
|
+
"'": "'"
|
|
211
|
+
};
|
|
212
|
+
var escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => HTML_ENTITIES[char]);
|
|
213
|
+
var noEscape = (value) => String(value);
|
|
214
|
+
var mjmlModule;
|
|
215
|
+
var htmlToTextModule;
|
|
216
|
+
var loadMjml = () => mjmlModule ??= import("mjml").then(
|
|
217
|
+
(module) => module.default,
|
|
218
|
+
(error) => {
|
|
219
|
+
mjmlModule = void 0;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
var loadHtmlToText = () => htmlToTextModule ??= import("html-to-text").then(
|
|
224
|
+
(module) => module,
|
|
225
|
+
(error) => {
|
|
226
|
+
htmlToTextModule = void 0;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
var isMjmlMissing = (error) => error instanceof Error && (error.code === "ERR_MODULE_NOT_FOUND" && error.message.includes("'mjml'") || isMjmlMissing(error.cause));
|
|
231
|
+
var describeMjmlError = (error) => {
|
|
232
|
+
const issues = error?.errors;
|
|
233
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
234
|
+
return issues.map(
|
|
235
|
+
({ line, tagName, message }) => `line ${line} (${tagName}): ${message}`
|
|
236
|
+
).join("; ");
|
|
237
|
+
}
|
|
238
|
+
return error instanceof Error ? error.message : String(error);
|
|
239
|
+
};
|
|
240
|
+
var compileMjml = async (name, source) => {
|
|
241
|
+
let mjml;
|
|
242
|
+
try {
|
|
243
|
+
mjml = await loadMjml();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
throw new MailRenderError(
|
|
246
|
+
name,
|
|
247
|
+
isMjmlMissing(error) ? "MJML is not installed. Install the mjml package, or use html templates." : "MJML could not be loaded.",
|
|
248
|
+
{ cause: error }
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
const { html } = await mjml(source, {
|
|
253
|
+
validationLevel: "strict",
|
|
254
|
+
ignoreIncludes: true
|
|
255
|
+
});
|
|
256
|
+
return html;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throw new MailRenderError(
|
|
259
|
+
name,
|
|
260
|
+
`invalid MJML: ${describeMjmlError(error)}`,
|
|
261
|
+
{ cause: error }
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var toText = async (html) => {
|
|
266
|
+
const { convert } = await loadHtmlToText();
|
|
267
|
+
return convert(html, {
|
|
268
|
+
wordwrap: 78,
|
|
269
|
+
selectors: [
|
|
270
|
+
{ selector: "img", format: "skip" },
|
|
271
|
+
// Preview text and other content hidden from readers.
|
|
272
|
+
{ selector: '[style*="display:none"]', format: "skip" },
|
|
273
|
+
{ selector: '[style*="display: none"]', format: "skip" },
|
|
274
|
+
{ selector: "a", options: { hideLinkHrefIfSameAsText: true } }
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
var translator = (name, owner, translations, locale) => (key) => {
|
|
279
|
+
const entry = Object.hasOwn(translations, key) ? translations[key] : void 0;
|
|
280
|
+
const text = entry?.[locale];
|
|
281
|
+
if (text === void 0) {
|
|
282
|
+
throw new MailRenderError(
|
|
283
|
+
name,
|
|
284
|
+
`unknown translation key "${key}" in the ${owner}.`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return text;
|
|
288
|
+
};
|
|
289
|
+
var produce = (name, owner, source, context) => {
|
|
290
|
+
if (typeof source === "string") return source;
|
|
291
|
+
let result;
|
|
292
|
+
try {
|
|
293
|
+
result = source(context);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
if (error instanceof MailRenderError) throw error;
|
|
296
|
+
throw new MailRenderError(name, `the ${owner} source function failed.`, {
|
|
297
|
+
cause: error
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
if (typeof result !== "string") {
|
|
301
|
+
throw new MailRenderError(
|
|
302
|
+
name,
|
|
303
|
+
`the ${owner} source function must return a string.`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return result;
|
|
307
|
+
};
|
|
308
|
+
var contextOf = (definition, locale) => ({
|
|
309
|
+
locale,
|
|
310
|
+
t: translator(definition.name, "template", definition.translations, locale)
|
|
311
|
+
});
|
|
312
|
+
var compile = async (definition, locale) => {
|
|
313
|
+
const { name, layout } = definition;
|
|
314
|
+
let source = produce(
|
|
315
|
+
name,
|
|
316
|
+
"template",
|
|
317
|
+
definition.source,
|
|
318
|
+
contextOf(definition, locale)
|
|
319
|
+
);
|
|
320
|
+
if (layout) {
|
|
321
|
+
source = produce(name, "layout", layout.render, {
|
|
322
|
+
locale,
|
|
323
|
+
t: translator(name, "layout", layout.translations, locale),
|
|
324
|
+
content: source
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return definition.format === "mjml" ? compileMjml(name, source) : source;
|
|
328
|
+
};
|
|
329
|
+
var compiled = (definition, locale) => {
|
|
330
|
+
const key = locale ?? "";
|
|
331
|
+
let html = definition.cache.get(key);
|
|
332
|
+
if (!html) {
|
|
333
|
+
html = compile(definition, locale);
|
|
334
|
+
definition.cache.set(key, html);
|
|
335
|
+
html.catch(() => definition.cache.delete(key));
|
|
336
|
+
}
|
|
337
|
+
return html;
|
|
338
|
+
};
|
|
339
|
+
var renderMustache = (name, part, source, data, escape) => {
|
|
340
|
+
try {
|
|
341
|
+
return mustache.render(source, data, {}, { escape });
|
|
342
|
+
} catch (error) {
|
|
343
|
+
throw new MailRenderError(
|
|
344
|
+
name,
|
|
345
|
+
`invalid Mustache in the ${part}: ${error.message}`,
|
|
346
|
+
{ cause: error }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var parseData = async (definition, data) => {
|
|
351
|
+
const { name, schema } = definition;
|
|
352
|
+
if (!schema) {
|
|
353
|
+
if (data !== void 0) {
|
|
354
|
+
throw new MailDataError(name, [
|
|
355
|
+
{ path: [], message: "the template has no schema and accepts no data" }
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
const result = await schema.safeParseAsync(data);
|
|
361
|
+
if (!result.success) {
|
|
362
|
+
throw new MailDataError(
|
|
363
|
+
name,
|
|
364
|
+
result.error.issues.map(({ path, message }) => ({
|
|
365
|
+
path: path.map((key) => typeof key === "symbol" ? String(key) : key),
|
|
366
|
+
message
|
|
367
|
+
}))
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const parsed = result.data;
|
|
371
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
372
|
+
throw new MailDataError(name, [
|
|
373
|
+
{ path: [], message: "the schema must produce an object" }
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
376
|
+
return parsed;
|
|
377
|
+
};
|
|
378
|
+
var subjectSource = (definition, locale) => {
|
|
379
|
+
const { subject } = definition;
|
|
380
|
+
if (typeof subject === "string") return subject;
|
|
381
|
+
return subject[locale];
|
|
382
|
+
};
|
|
383
|
+
var renderMail = async (template, options) => {
|
|
384
|
+
const definition = definitionOf(template);
|
|
385
|
+
if (!definition) {
|
|
386
|
+
throw new TypeError(
|
|
387
|
+
"renderMail(): expected a template created by defineTemplate()."
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const { name, locales } = definition;
|
|
391
|
+
const { locale: requested, defaultLocale } = options ?? {};
|
|
392
|
+
const data = await parseData(definition, options?.data);
|
|
393
|
+
let locale;
|
|
394
|
+
if (locales.length > 0) {
|
|
395
|
+
locale = resolveLocale(locales, requested, defaultLocale);
|
|
396
|
+
if (locale === void 0) {
|
|
397
|
+
const asked = [requested, defaultLocale].filter(Boolean);
|
|
398
|
+
throw new MailRenderError(
|
|
399
|
+
name,
|
|
400
|
+
asked.length > 0 ? `no translations for ${asked.join(" or ")} (available: ${locales.join(", ")}).` : `no locale requested and no default locale (available: ${locales.join(", ")}).`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const html = renderMustache(
|
|
405
|
+
name,
|
|
406
|
+
"HTML",
|
|
407
|
+
await compiled(definition, locale),
|
|
408
|
+
data,
|
|
409
|
+
escapeHtml
|
|
410
|
+
);
|
|
411
|
+
const subject = toSubject(
|
|
412
|
+
renderMustache(
|
|
413
|
+
name,
|
|
414
|
+
"subject",
|
|
415
|
+
subjectSource(definition, locale),
|
|
416
|
+
data,
|
|
417
|
+
noEscape
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
const text = definition.text ? renderMustache(
|
|
421
|
+
name,
|
|
422
|
+
"text",
|
|
423
|
+
produce(name, "text", definition.text, contextOf(definition, locale)),
|
|
424
|
+
data,
|
|
425
|
+
noEscape
|
|
426
|
+
) : await toText(html);
|
|
427
|
+
return { template: name, locale, subject, html, text };
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// src/mailer.ts
|
|
431
|
+
var MAILER_OPTIONS = /* @__PURE__ */ new Set([
|
|
432
|
+
"provider",
|
|
433
|
+
"from",
|
|
434
|
+
"replyTo",
|
|
435
|
+
"defaultLocale",
|
|
436
|
+
"logger"
|
|
437
|
+
]);
|
|
438
|
+
var ENVELOPE_OPTIONS = [
|
|
439
|
+
"to",
|
|
440
|
+
"cc",
|
|
441
|
+
"bcc",
|
|
442
|
+
"replyTo",
|
|
443
|
+
"from",
|
|
444
|
+
"attachments",
|
|
445
|
+
"headers",
|
|
446
|
+
"tags",
|
|
447
|
+
"metadata",
|
|
448
|
+
"subject"
|
|
449
|
+
];
|
|
450
|
+
var SEND_OPTIONS = /* @__PURE__ */ new Set([...ENVELOPE_OPTIONS, "locale", "data"]);
|
|
451
|
+
var MESSAGE_OPTIONS = /* @__PURE__ */ new Set([...ENVELOPE_OPTIONS, "html", "text"]);
|
|
452
|
+
var checkLogger = (logger) => {
|
|
453
|
+
if (typeof logger?.info !== "function") {
|
|
454
|
+
throw new TypeError("Invalid mailer logger: expected an info() method.");
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
var freeze = (message) => {
|
|
458
|
+
for (const value of Object.values(message)) {
|
|
459
|
+
if (typeof value === "object" && value !== null) Object.freeze(value);
|
|
460
|
+
}
|
|
461
|
+
return Object.freeze(message);
|
|
462
|
+
};
|
|
463
|
+
var createMailer = (options) => {
|
|
464
|
+
const values = checkKeys("Invalid mailer options", options, MAILER_OPTIONS);
|
|
465
|
+
const { provider, defaultLocale, logger } = values;
|
|
466
|
+
if (typeof provider?.name !== "string" || provider.name === "" || typeof provider.send !== "function") {
|
|
467
|
+
throw new TypeError(
|
|
468
|
+
"Invalid mailer options: provider must have a name and a send() method."
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (defaultLocale !== void 0 && (typeof defaultLocale !== "string" || defaultLocale === "")) {
|
|
472
|
+
throw new TypeError(
|
|
473
|
+
"Invalid mailer options: defaultLocale must be a non-empty string."
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
if (logger !== void 0) checkLogger(logger);
|
|
477
|
+
if (values.from === void 0) {
|
|
478
|
+
throw new TypeError("Invalid mailer options: from is required.");
|
|
479
|
+
}
|
|
480
|
+
const defaults = {
|
|
481
|
+
from: parseAddress(values.from, "from"),
|
|
482
|
+
replyTo: parseRecipients(values.replyTo, "replyTo")
|
|
483
|
+
};
|
|
484
|
+
const create = (logger2) => {
|
|
485
|
+
const deliver = async (envelope, content) => {
|
|
486
|
+
const message = freeze({ ...envelope, ...content });
|
|
487
|
+
let result;
|
|
488
|
+
try {
|
|
489
|
+
result = await provider.send(message);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof MailerError) throw error;
|
|
492
|
+
throw new MailSendError(
|
|
493
|
+
provider.name,
|
|
494
|
+
error instanceof Error ? error.message : String(error),
|
|
495
|
+
{ cause: error }
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const messageId = typeof result?.messageId === "string" ? result.messageId : void 0;
|
|
499
|
+
logger2?.info(
|
|
500
|
+
{
|
|
501
|
+
provider: provider.name,
|
|
502
|
+
template: message.template,
|
|
503
|
+
messageId,
|
|
504
|
+
recipients: message.to.length + message.cc.length + message.bcc.length
|
|
505
|
+
},
|
|
506
|
+
"Mail sent"
|
|
507
|
+
);
|
|
508
|
+
return { messageId };
|
|
509
|
+
};
|
|
510
|
+
const render = (template, renderOptions) => renderMail(template, {
|
|
511
|
+
defaultLocale,
|
|
512
|
+
...renderOptions
|
|
513
|
+
});
|
|
514
|
+
const mailer = {
|
|
515
|
+
provider,
|
|
516
|
+
render,
|
|
517
|
+
send: async (template, sendOptions) => {
|
|
518
|
+
const { subject, locale, data, ...envelopeOptions } = checkKeys(
|
|
519
|
+
"mailer.send()",
|
|
520
|
+
sendOptions,
|
|
521
|
+
SEND_OPTIONS
|
|
522
|
+
);
|
|
523
|
+
if (subject !== void 0 && typeof subject !== "string") {
|
|
524
|
+
throw new TypeError("mailer.send(): subject must be a string.");
|
|
525
|
+
}
|
|
526
|
+
const envelope = parseEnvelope(envelopeOptions, defaults);
|
|
527
|
+
const mail = await render(template, { locale, data });
|
|
528
|
+
return deliver(envelope, {
|
|
529
|
+
subject: subject === void 0 ? mail.subject : toSubject(subject),
|
|
530
|
+
html: mail.html,
|
|
531
|
+
text: mail.text,
|
|
532
|
+
template: mail.template,
|
|
533
|
+
locale: mail.locale
|
|
534
|
+
});
|
|
535
|
+
},
|
|
536
|
+
sendMessage: async (messageOptions) => {
|
|
537
|
+
const { subject, html, text, ...envelopeOptions } = checkKeys(
|
|
538
|
+
"mailer.sendMessage()",
|
|
539
|
+
messageOptions,
|
|
540
|
+
MESSAGE_OPTIONS
|
|
541
|
+
);
|
|
542
|
+
if (typeof subject !== "string") {
|
|
543
|
+
throw new TypeError("mailer.sendMessage(): subject must be a string.");
|
|
544
|
+
}
|
|
545
|
+
for (const [key, value] of [
|
|
546
|
+
["html", html],
|
|
547
|
+
["text", text]
|
|
548
|
+
]) {
|
|
549
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
550
|
+
throw new TypeError(
|
|
551
|
+
`mailer.sendMessage(): ${key} must be a string.`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (html === void 0 && text === void 0) {
|
|
556
|
+
throw new TypeError("mailer.sendMessage(): set html, text, or both.");
|
|
557
|
+
}
|
|
558
|
+
const envelope = parseEnvelope(envelopeOptions, defaults);
|
|
559
|
+
return deliver(envelope, {
|
|
560
|
+
subject: toSubject(subject),
|
|
561
|
+
html,
|
|
562
|
+
text: text ?? await toText(html),
|
|
563
|
+
template: void 0,
|
|
564
|
+
locale: void 0
|
|
565
|
+
});
|
|
566
|
+
},
|
|
567
|
+
withLogger: (other) => {
|
|
568
|
+
checkLogger(other);
|
|
569
|
+
return create(other);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
return Object.freeze(mailer);
|
|
573
|
+
};
|
|
574
|
+
return create(logger);
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/providers/console.ts
|
|
578
|
+
var list = (addresses) => addresses.map(formatAddress).join(", ");
|
|
579
|
+
var createConsoleProvider = (options = {}) => {
|
|
580
|
+
const { body = false, write = (output) => console.log(output) } = options;
|
|
581
|
+
return {
|
|
582
|
+
name: "console",
|
|
583
|
+
send: async (message) => {
|
|
584
|
+
const lines = [
|
|
585
|
+
`[mail] ${message.template ?? "message"}${message.locale ? ` (${message.locale})` : ""}`,
|
|
586
|
+
`From: ${formatAddress(message.from)}`,
|
|
587
|
+
`To: ${list(message.to)}`
|
|
588
|
+
];
|
|
589
|
+
for (const [label, addresses] of [
|
|
590
|
+
["Cc", message.cc],
|
|
591
|
+
["Bcc", message.bcc],
|
|
592
|
+
["Reply-To", message.replyTo]
|
|
593
|
+
]) {
|
|
594
|
+
if (addresses.length > 0) lines.push(`${label}: ${list(addresses)}`);
|
|
595
|
+
}
|
|
596
|
+
lines.push(`Subject: ${message.subject}`);
|
|
597
|
+
if (message.attachments.length > 0) {
|
|
598
|
+
lines.push(
|
|
599
|
+
`Attachments: ${message.attachments.map(({ filename }) => filename).join(", ")}`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (message.tags.length > 0) {
|
|
603
|
+
lines.push(`Tags: ${message.tags.join(", ")}`);
|
|
604
|
+
}
|
|
605
|
+
const metadata = Object.entries(message.metadata);
|
|
606
|
+
if (metadata.length > 0) {
|
|
607
|
+
lines.push(
|
|
608
|
+
`Metadata: ${metadata.map(([key, value]) => `${key}=${value}`).join(", ")}`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (body) lines.push("", message.text);
|
|
612
|
+
write(lines.join("\n"));
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// src/providers/memory.ts
|
|
618
|
+
var createMemoryProvider = () => {
|
|
619
|
+
const sent = [];
|
|
620
|
+
let count = 0;
|
|
621
|
+
return {
|
|
622
|
+
name: "memory",
|
|
623
|
+
sent,
|
|
624
|
+
send: async (message) => {
|
|
625
|
+
sent.push(message);
|
|
626
|
+
count += 1;
|
|
627
|
+
return { messageId: `memory-${count}` };
|
|
628
|
+
},
|
|
629
|
+
last: () => sent.at(-1),
|
|
630
|
+
clear: () => {
|
|
631
|
+
sent.length = 0;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
};
|
|
635
|
+
export {
|
|
636
|
+
MailDataError,
|
|
637
|
+
MailMessageError,
|
|
638
|
+
MailRenderError,
|
|
639
|
+
MailSendError,
|
|
640
|
+
MailerError,
|
|
641
|
+
createConsoleProvider,
|
|
642
|
+
createMailer,
|
|
643
|
+
createMemoryProvider,
|
|
644
|
+
defineLayout,
|
|
645
|
+
defineTemplate,
|
|
646
|
+
formatAddress,
|
|
647
|
+
renderMail
|
|
648
|
+
};
|
|
649
|
+
//# sourceMappingURL=index.js.map
|