@dotpress/mailer 2.0.0-alpha.3 → 2.0.0-alpha.5
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/aws-ses.js +2 -2
- package/dist/brevo.js +2 -2
- package/dist/{chunk-3EMC5BO6.js → chunk-7JTF345R.js} +2 -2
- package/dist/{chunk-GOAXQ2GE.js → chunk-R2VGIBJO.js} +2 -2
- package/dist/{chunk-QYVN5N3B.js → chunk-T5AWVUNP.js} +1 -1
- package/dist/chunk-T5AWVUNP.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +27 -14
- package/dist/index.js.map +1 -1
- package/dist/{mailer-CmQCJ0qc.d.ts → mailer-8D9aDUz5.d.ts} +38 -14
- package/dist/mailgun.js +2 -2
- package/dist/mailjet.js +2 -2
- package/dist/plugin.js +5 -2
- package/dist/plugin.js.map +1 -1
- package/dist/postmark.js +2 -2
- package/dist/resend.js +2 -2
- package/dist/scaleway.js +2 -2
- package/dist/sendgrid.js +2 -2
- package/dist/smtp.d.ts +7 -2
- package/dist/smtp.js +16 -5
- package/dist/smtp.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-QYVN5N3B.js.map +0 -1
- /package/dist/{chunk-3EMC5BO6.js.map → chunk-7JTF345R.js.map} +0 -0
- /package/dist/{chunk-GOAXQ2GE.js.map → chunk-R2VGIBJO.js.map} +0 -0
package/dist/aws-ses.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
keyValueTags
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-R2VGIBJO.js";
|
|
4
4
|
import {
|
|
5
5
|
base64,
|
|
6
6
|
utf8
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
MailSendError,
|
|
10
10
|
checkKeys,
|
|
11
11
|
formatAddress
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-T5AWVUNP.js";
|
|
13
13
|
|
|
14
14
|
// src/providers/aws-ses.ts
|
|
15
15
|
import {
|
package/dist/brevo.js
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
readJson,
|
|
6
6
|
responseError,
|
|
7
7
|
stringOf
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7JTF345R.js";
|
|
9
9
|
import {
|
|
10
10
|
base64
|
|
11
11
|
} from "./chunk-4FKEUJSR.js";
|
|
12
12
|
import {
|
|
13
13
|
MailMessageError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/brevo.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.brevo.com";
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
MailMessageError,
|
|
6
6
|
MailSendError,
|
|
7
7
|
checkKeys
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-T5AWVUNP.js";
|
|
9
9
|
|
|
10
10
|
// src/providers/http.ts
|
|
11
11
|
var checkHttpOptions = (label, options, required, defaultBaseUrl, optional = []) => {
|
|
@@ -85,4 +85,4 @@ export {
|
|
|
85
85
|
checkSingleReplyTo,
|
|
86
86
|
checkNoInline
|
|
87
87
|
};
|
|
88
|
-
//# sourceMappingURL=chunk-
|
|
88
|
+
//# sourceMappingURL=chunk-7JTF345R.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MailMessageError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-T5AWVUNP.js";
|
|
4
4
|
|
|
5
5
|
// src/providers/key-value-tags.ts
|
|
6
6
|
var VALUE = /^[A-Za-z0-9_-]{1,256}$/;
|
|
@@ -25,4 +25,4 @@ var keyValueTags = (message, provider) => {
|
|
|
25
25
|
export {
|
|
26
26
|
keyValueTags
|
|
27
27
|
};
|
|
28
|
-
//# sourceMappingURL=chunk-
|
|
28
|
+
//# sourceMappingURL=chunk-R2VGIBJO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/message.ts"],"sourcesContent":["/** Base class of the errors thrown by `@dotpress/mailer`. */\nexport class MailerError extends Error {\n override name = 'MailerError'\n}\n\n/** Problem found in the data passed to a template. */\nexport interface MailDataIssue {\n /** Path of the invalid value, empty for the data itself. */\n readonly path: readonly (string | number)[]\n readonly message: string\n}\n\n/** The data passed to a template does not match its schema. */\nexport class MailDataError extends MailerError {\n override name = 'MailDataError'\n\n constructor(\n /** Name of the template. */\n readonly template: string,\n readonly issues: readonly MailDataIssue[]\n ) {\n const details = issues\n .map(({ path, message }) =>\n path.length > 0 ? `${path.join('.')}: ${message}` : message\n )\n .join('; ')\n super(`Invalid data for mail template \"${template}\": ${details}`)\n }\n}\n\n/** A template could not be rendered (MJML, translations, Mustache). */\nexport class MailRenderError extends MailerError {\n override name = 'MailRenderError'\n\n constructor(\n /** Name of the template. */\n readonly template: string,\n message: string,\n options?: ErrorOptions\n ) {\n super(`Cannot render mail template \"${template}\": ${message}`, options)\n }\n}\n\n/** Invalid message: address, attachment, header or option. */\nexport class MailMessageError extends MailerError {\n override name = 'MailMessageError'\n\n constructor(message: string) {\n super(`Invalid mail message: ${message}`)\n }\n}\n\n/** Options of `MailSendError`. */\nexport interface MailSendErrorOptions extends ErrorOptions {\n /** HTTP status of the provider API, if any. */\n status?: number | undefined\n}\n\n/** The provider failed to send a message. */\nexport class MailSendError extends MailerError {\n override name = 'MailSendError'\n /** HTTP status of the provider API, if any. */\n readonly status: number | undefined\n\n constructor(\n /** Name of the provider. */\n readonly provider: string,\n message: string,\n options: MailSendErrorOptions = {}\n ) {\n const { status, ...errorOptions } = options\n super(`Mail not sent by the ${provider} provider: ${message}`, errorOptions)\n this.status = status\n }\n}\n","import { MailMessageError } from './errors.js'\n\n/** An email address, normalized. */\nexport interface Address {\n /** Display name, like `Ada Lovelace`. */\n readonly name?: string\n /** Email address, like `ada@example.com`. */\n readonly address: string\n}\n\n/**\n * An email address: `'ada@example.com'`, `'Ada Lovelace <ada@example.com>'`\n * or `{ name: 'Ada Lovelace', address: 'ada@example.com' }`.\n */\nexport type MailAddress = string | Address\n\n/** One or more email addresses. */\nexport type MailRecipients = MailAddress | readonly MailAddress[]\n\n/** A file to attach to a message, given to `send()` and `sendMessage()`. */\nexport interface MailAttachmentInput {\n /** File name shown by email clients. */\n readonly filename: string\n /** Binary content, or text content (UTF-8). */\n readonly content: Uint8Array | string\n /** MIME type, like `application/pdf`. Guessed by the provider if missing. */\n readonly contentType?: string | undefined\n /**\n * Makes the attachment inline: the HTML version references it with\n * `cid:` (`<img src=\"cid:logo\">`).\n */\n readonly contentId?: string | undefined\n}\n\n/**\n * A file attached to a message, as given to providers: validated. Its\n * input, `MailAttachmentInput`, may accept more forms later.\n */\nexport interface MailAttachment {\n /** File name shown by email clients. */\n readonly filename: string\n /** Binary content, or text content (UTF-8). */\n readonly content: Uint8Array | string\n /** MIME type, like `application/pdf`. Guessed by the provider if missing. */\n readonly contentType?: string\n /**\n * Makes the attachment inline: the HTML version references it with\n * `cid:` (`<img src=\"cid:logo\">`).\n */\n readonly contentId?: string\n}\n\n/** Message given to providers. */\nexport interface MailMessage {\n readonly from: Address\n /** At least one address. */\n readonly to: readonly Address[]\n readonly cc: readonly Address[]\n readonly bcc: readonly Address[]\n readonly replyTo: readonly Address[]\n /** Single line. */\n readonly subject: string\n /** HTML version; undefined for a text-only message. */\n readonly html: string | undefined\n readonly text: string\n readonly attachments: readonly MailAttachment[]\n /** Additional headers, like `List-Unsubscribe`. */\n readonly headers: Readonly<Record<string, string>>\n /** Labels to group messages in the statistics of the provider. */\n readonly tags: readonly string[]\n /** Key/value pairs returned by the webhooks of the provider. */\n readonly metadata: Readonly<Record<string, string>>\n /** Name of the template, if the message was rendered from one. */\n readonly template: string | undefined\n /** Rendered locale, if any. */\n readonly locale: string | undefined\n}\n\n/** Recipients and options shared by every message. */\nexport interface EnvelopeOptions {\n to: MailRecipients\n cc?: MailRecipients | undefined\n bcc?: MailRecipients | undefined\n /** Defaults to the `replyTo` option of the mailer. */\n replyTo?: MailRecipients | undefined\n /** Defaults to the `from` option of the mailer. */\n from?: MailAddress | undefined\n attachments?: readonly MailAttachmentInput[] | undefined\n /** Additional headers, like `List-Unsubscribe`. */\n headers?: Readonly<Record<string, string>> | undefined\n /**\n * Labels to group messages in the statistics of the provider: letters,\n * digits, `_` and `-`. Ignored by providers without tags.\n */\n tags?: readonly string[] | undefined\n /**\n * Key/value pairs returned by the webhooks of the provider, to link an\n * event to the app. Keys: letters, digits, `_` and `-`. Ignored by\n * providers without metadata.\n */\n metadata?: Readonly<Record<string, string>> | undefined\n}\n\n/** Envelope of a message, validated. */\nexport interface Envelope {\n readonly from: Address\n readonly to: readonly Address[]\n readonly cc: readonly Address[]\n readonly bcc: readonly Address[]\n readonly replyTo: readonly Address[]\n readonly attachments: readonly MailAttachment[]\n readonly headers: Readonly<Record<string, string>>\n readonly tags: readonly string[]\n readonly metadata: Readonly<Record<string, string>>\n}\n\nconst fail = (message: string): never => {\n throw new MailMessageError(message)\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n// Pragmatic check: one @, no spaces, nor characters that break headers.\nconst EMAIL = /^[^\\s@<>()[\\]\\\\,;:\"]+@[^\\s@<>()[\\]\\\\,;:\"]+$/\nconst NAMED = /^([\\s\\S]*?)\\s*<([^<>]*)>$/\nconst LINE_BREAK = /[\\r\\n]/\nconst TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\n// The strictest rule of the providers (SES, Resend): valid everywhere.\nconst TAG = /^[A-Za-z0-9_-]{1,256}$/\n\n// Set by the mailer or the provider.\nconst RESERVED_HEADERS = new Set([\n 'from',\n 'to',\n 'cc',\n 'bcc',\n 'reply-to',\n 'subject',\n 'date',\n 'message-id',\n 'mime-version',\n 'content-type',\n 'content-transfer-encoding',\n])\n\n/** Normalizes and checks an address. */\nexport const parseAddress = (value: unknown, label: string): Address => {\n let name: unknown\n let address: unknown\n if (typeof value === 'string') {\n const named = NAMED.exec(value.trim())\n if (named) {\n // Optional quotes around the name.\n name = (named[1] as string).replace(/^\"(.*)\"$/, '$1').trim()\n address = (named[2] as string).trim()\n } else {\n address = value.trim()\n }\n } else if (isRecord(value)) {\n ;({ name, address } = value)\n if (name !== undefined && typeof name !== 'string') {\n fail(`${label}.name must be a string.`)\n }\n } else {\n fail(`${label} must be an email address.`)\n }\n\n if (typeof address !== 'string' || !EMAIL.test(address)) {\n fail(`${label} is not an email address: ${JSON.stringify(address)}.`)\n }\n if (typeof name === 'string' && LINE_BREAK.test(name)) {\n fail(`${label} has a line break in its name.`)\n }\n return name\n ? { name: name as string, address: address as string }\n : { address: address as string }\n}\n\n/** Normalizes and checks a list of addresses. */\nexport const parseRecipients = (value: unknown, label: string): Address[] => {\n if (value === undefined) return []\n const list: unknown[] = Array.isArray(value) ? value : [value]\n return list.map((item, index) =>\n parseAddress(item, Array.isArray(value) ? `${label}[${index}]` : label)\n )\n}\n\nconst parseAttachments = (value: unknown): MailAttachment[] => {\n if (value === undefined) return []\n if (!Array.isArray(value)) fail('attachments must be a list.')\n\n return (value as unknown[]).map((attachment, index) => {\n const label = `attachments[${index}]`\n if (!isRecord(attachment)) fail(`${label} must be an object.`)\n const { filename, content, contentType, contentId } = attachment as Record<\n string,\n unknown\n >\n if (typeof filename !== 'string' || filename === '') {\n fail(`${label}.filename must be a non-empty string.`)\n }\n if (!(content instanceof Uint8Array) && typeof content !== 'string') {\n fail(`${label}.content must be a Uint8Array or a string.`)\n }\n for (const [key, field] of [\n ['contentType', contentType],\n ['contentId', contentId],\n ] as const) {\n if (\n field !== undefined &&\n (typeof field !== 'string' || field === '' || LINE_BREAK.test(field))\n ) {\n fail(`${label}.${key} must be a non-empty string on one line.`)\n }\n }\n return {\n filename: filename as string,\n content: content as Uint8Array | string,\n ...(contentType === undefined\n ? {}\n : { contentType: contentType as string }),\n ...(contentId === undefined ? {} : { contentId: contentId as string }),\n }\n })\n}\n\nconst parseHeaders = (value: unknown): Record<string, string> => {\n if (value === undefined) return {}\n if (!isRecord(value)) fail('headers must be an object.')\n\n const headers: Record<string, string> = {}\n for (const [name, header] of Object.entries(value as object)) {\n if (!TOKEN.test(name)) fail(`\"${name}\" is not a header name.`)\n if (RESERVED_HEADERS.has(name.toLowerCase())) {\n fail(`the ${name} header is set by the mailer.`)\n }\n if (typeof header !== 'string' || LINE_BREAK.test(header)) {\n fail(`the ${name} header must be a string on one line.`)\n }\n headers[name] = header as string\n }\n return headers\n}\n\nconst parseTags = (value: unknown): string[] => {\n if (value === undefined) return []\n if (!Array.isArray(value)) fail('tags must be a list.')\n const tags = new Set<string>()\n for (const [index, tag] of (value as unknown[]).entries()) {\n if (typeof tag !== 'string' || !TAG.test(tag)) {\n fail(\n `tags[${index}] must have 1 to 256 letters, digits, _ or -: ${JSON.stringify(tag)}.`\n )\n }\n tags.add(tag as string)\n }\n return [...tags]\n}\n\nconst parseMetadata = (value: unknown): Record<string, string> => {\n if (value === undefined) return {}\n if (!isRecord(value)) fail('metadata must be an object.')\n\n const metadata: Record<string, string> = {}\n for (const [key, item] of Object.entries(value as object)) {\n if (!TAG.test(key)) {\n fail(\n `metadata keys must have 1 to 256 letters, digits, _ or -: ${JSON.stringify(key)}.`\n )\n }\n if (typeof item !== 'string' || LINE_BREAK.test(item)) {\n fail(`metadata.${key} must be a string on one line.`)\n }\n metadata[key] = item as string\n }\n return metadata\n}\n\n/** Single-line subject. */\nexport const toSubject = (subject: string) =>\n subject.replace(/\\s*[\\r\\n]+\\s*/g, ' ').trim()\n\n/**\n * Validates the envelope options of a message. `defaults` provides the\n * sender and the reply-to addresses of the mailer.\n */\nexport const parseEnvelope = (\n options: EnvelopeOptions,\n defaults: { from: Address; replyTo: readonly Address[] }\n): Envelope => {\n const to = parseRecipients(options.to, 'to')\n if (to.length === 0) fail('to needs at least one address.')\n\n return {\n from:\n options.from === undefined\n ? defaults.from\n : parseAddress(options.from, 'from'),\n to,\n cc: parseRecipients(options.cc, 'cc'),\n bcc: parseRecipients(options.bcc, 'bcc'),\n replyTo:\n options.replyTo === undefined\n ? defaults.replyTo\n : parseRecipients(options.replyTo, 'replyTo'),\n attachments: parseAttachments(options.attachments),\n headers: parseHeaders(options.headers),\n tags: parseTags(options.tags),\n metadata: parseMetadata(options.metadata),\n }\n}\n\n/** Rejects unknown options. */\nexport const checkKeys = (\n label: string,\n options: unknown,\n allowed: ReadonlySet<string>\n): Record<string, unknown> => {\n if (!isRecord(options)) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.has(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n return options\n}\n\n/**\n * Address as a header value: `\"Ada Lovelace\" <ada@example.com>`, or the\n * address alone. For providers that take strings.\n */\nexport const formatAddress = ({ name, address }: Address) =>\n name ? `\"${name.replace(/([\"\\\\])/g, '\\\\$1')}\" <${address}>` : address\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MailProvider, a as MailMessage } from './mailer-
|
|
2
|
-
export { A as Address,
|
|
1
|
+
import { M as MailProvider, a as MailMessage } from './mailer-8D9aDUz5.js';
|
|
2
|
+
export { A as Address, E as EnvelopeOptions, L as LayoutContext, b as LayoutOptions, c as Localized, d as MailAddress, e as MailAttachment, f as MailAttachmentInput, g as MailFormat, h as MailLayout, i as MailRecipients, j as MailTemplate, k as Mailer, l as MailerContext, m as MailerLogger, n as MailerOptions, o as MessageOptions, P as ProviderResult, R as RenderMailOptions, p as RenderedMail, S as SendMailOptions, q as SendResult, T as TemplateData, r as TemplateOptions, s as TemplateSource, t as TranslateContext, u as Translations, v as createMailer, w as defineLayout, x as defineTemplate, y as formatAddress, z as renderMail } from './mailer-8D9aDUz5.js';
|
|
3
3
|
export { H as HttpProviderOptions } from './http-B35l9W1d.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parseEnvelope,
|
|
11
11
|
parseRecipients,
|
|
12
12
|
toSubject
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-T5AWVUNP.js";
|
|
14
14
|
|
|
15
15
|
// src/render.ts
|
|
16
16
|
import mustache from "mustache";
|
|
@@ -110,7 +110,7 @@ var formatOf = (prefix, options, isValid, expected) => {
|
|
|
110
110
|
};
|
|
111
111
|
var isSource = (source) => typeof source === "string" || typeof source === "function";
|
|
112
112
|
var defineLayout = (options) => {
|
|
113
|
-
const prefix = "
|
|
113
|
+
const prefix = "defineLayout()";
|
|
114
114
|
const values = checkOptions(prefix, options, LAYOUT_OPTIONS);
|
|
115
115
|
const format = formatOf(
|
|
116
116
|
prefix,
|
|
@@ -135,7 +135,7 @@ var defineLayout = (options) => {
|
|
|
135
135
|
};
|
|
136
136
|
var defineTemplate = (options) => {
|
|
137
137
|
const name = options?.name;
|
|
138
|
-
const prefix = typeof name === "string" && name !== "" ? `
|
|
138
|
+
const prefix = typeof name === "string" && name !== "" ? `defineTemplate("${name}")` : "defineTemplate()";
|
|
139
139
|
const values = checkOptions(prefix, options, TEMPLATE_OPTIONS);
|
|
140
140
|
if (typeof name !== "string" || name === "") {
|
|
141
141
|
throw new TypeError(`${prefix}: name must be a non-empty string.`);
|
|
@@ -449,9 +449,13 @@ var ENVELOPE_OPTIONS = [
|
|
|
449
449
|
];
|
|
450
450
|
var SEND_OPTIONS = /* @__PURE__ */ new Set([...ENVELOPE_OPTIONS, "locale", "data"]);
|
|
451
451
|
var MESSAGE_OPTIONS = /* @__PURE__ */ new Set([...ENVELOPE_OPTIONS, "html", "text"]);
|
|
452
|
-
var checkLogger = (logger) => {
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
var checkLogger = (label, logger) => {
|
|
453
|
+
for (const level of ["info", "warn", "error"]) {
|
|
454
|
+
if (typeof logger?.[level] !== "function") {
|
|
455
|
+
throw new TypeError(
|
|
456
|
+
`${label}: logger must have info(), warn() and error() methods.`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
455
459
|
}
|
|
456
460
|
};
|
|
457
461
|
var freeze = (message) => {
|
|
@@ -461,21 +465,21 @@ var freeze = (message) => {
|
|
|
461
465
|
return Object.freeze(message);
|
|
462
466
|
};
|
|
463
467
|
var createMailer = (options) => {
|
|
464
|
-
const values = checkKeys("
|
|
468
|
+
const values = checkKeys("createMailer()", options, MAILER_OPTIONS);
|
|
465
469
|
const { provider, defaultLocale, logger } = values;
|
|
466
470
|
if (typeof provider?.name !== "string" || provider.name === "" || typeof provider.send !== "function") {
|
|
467
471
|
throw new TypeError(
|
|
468
|
-
"
|
|
472
|
+
"createMailer(): provider must have a name and a send() method."
|
|
469
473
|
);
|
|
470
474
|
}
|
|
471
475
|
if (defaultLocale !== void 0 && (typeof defaultLocale !== "string" || defaultLocale === "")) {
|
|
472
476
|
throw new TypeError(
|
|
473
|
-
"
|
|
477
|
+
"createMailer(): defaultLocale must be a non-empty string."
|
|
474
478
|
);
|
|
475
479
|
}
|
|
476
|
-
if (logger !== void 0) checkLogger(logger);
|
|
480
|
+
if (logger !== void 0) checkLogger("createMailer()", logger);
|
|
477
481
|
if (values.from === void 0) {
|
|
478
|
-
throw new TypeError("
|
|
482
|
+
throw new TypeError("createMailer(): from is required.");
|
|
479
483
|
}
|
|
480
484
|
const defaults = {
|
|
481
485
|
from: parseAddress(values.from, "from"),
|
|
@@ -564,9 +568,18 @@ var createMailer = (options) => {
|
|
|
564
568
|
locale: void 0
|
|
565
569
|
});
|
|
566
570
|
},
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
571
|
+
withContext: (context = {}) => {
|
|
572
|
+
if (typeof context !== "object" || context === null) {
|
|
573
|
+
throw new TypeError("withContext(): expected an object.");
|
|
574
|
+
}
|
|
575
|
+
for (const key of Object.keys(context)) {
|
|
576
|
+
if (key !== "logger") {
|
|
577
|
+
throw new TypeError(`withContext(): unknown option "${key}".`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (context.logger === void 0) return create(logger2);
|
|
581
|
+
checkLogger("withContext()", context.logger);
|
|
582
|
+
return create(context.logger);
|
|
570
583
|
}
|
|
571
584
|
};
|
|
572
585
|
return Object.freeze(mailer);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/render.ts","../src/locales.ts","../src/template.ts","../src/mailer.ts","../src/providers/console.ts","../src/providers/memory.ts"],"sourcesContent":["import mustache from 'mustache'\nimport { MailDataError, MailRenderError } from './errors.js'\nimport { resolveLocale, type Translations } from './locales.js'\nimport { toSubject } from './message.js'\nimport {\n definitionOf,\n type MailTemplate,\n type TemplateDefinition,\n type TranslateContext,\n} from './template.js'\n\n/** `data` option: required when the template has a schema. */\nexport type DataOption<TData> = [TData] extends [undefined]\n ? { data?: undefined }\n : { data: TData }\n\n/** Options of `renderMail()`. */\nexport type RenderMailOptions<TData> = {\n /**\n * Locale to render, like `fr-CA`. Falls back to its base language (`fr`),\n * then to `defaultLocale`.\n */\n locale?: string | undefined\n /** Locale used when the requested one is not available. */\n defaultLocale?: string | undefined\n} & DataOption<TData>\n\n/** A rendered email. */\nexport interface RenderedMail {\n /** Name of the template. */\n readonly template: string\n /** Rendered locale; undefined for a template without translations. */\n readonly locale: string | undefined\n readonly subject: string\n readonly html: string\n readonly text: string\n}\n\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n}\n\n/**\n * HTML escaping of `{{ }}` values. Unlike the default escaping of Mustache,\n * `/`, `=` and `` ` `` are kept: many email clients do not decode them in\n * links.\n */\nconst escapeHtml = (value: unknown) =>\n String(value).replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char] as string)\n\nconst noEscape = (value: unknown) => String(value)\n\ntype Mjml = typeof import('mjml')\ntype HtmlToText = typeof import('html-to-text')\n\nlet mjmlModule: Promise<Mjml> | undefined\nlet htmlToTextModule: Promise<HtmlToText> | undefined\n\n/** MJML, loaded on the first MJML render. */\nconst loadMjml = () =>\n (mjmlModule ??= import('mjml').then(\n (module) => module.default,\n (error: unknown) => {\n mjmlModule = undefined\n throw error\n }\n ))\n\n/** html-to-text, loaded on the first text conversion. */\nconst loadHtmlToText = () =>\n (htmlToTextModule ??= import('html-to-text').then(\n (module) => module,\n (error: unknown) => {\n htmlToTextModule = undefined\n throw error\n }\n ))\n\n/** Whether loading failed because the mjml package is missing. */\nconst isMjmlMissing = (error: unknown): boolean =>\n error instanceof Error &&\n (((error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND' &&\n error.message.includes(\"'mjml'\")) ||\n isMjmlMissing(error.cause))\n\ninterface MjmlIssue {\n line?: number\n tagName?: string\n message?: string\n}\n\n/** Message of an MJML failure, without the file paths of MJML messages. */\nconst describeMjmlError = (error: unknown) => {\n const issues = (error as { errors?: unknown } | null)?.errors\n if (Array.isArray(issues) && issues.length > 0) {\n return (issues as MjmlIssue[])\n .map(\n ({ line, tagName, message }) => `line ${line} (${tagName}): ${message}`\n )\n .join('; ')\n }\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Compiles MJML to HTML, strict validation. */\nconst compileMjml = async (name: string, source: string) => {\n let mjml: Mjml\n try {\n mjml = await loadMjml()\n } catch (error) {\n throw new MailRenderError(\n name,\n isMjmlMissing(error)\n ? 'MJML is not installed. Install the mjml package, or use html templates.'\n : 'MJML could not be loaded.',\n { cause: error }\n )\n }\n try {\n const { html } = await mjml(source, {\n validationLevel: 'strict',\n ignoreIncludes: true,\n })\n return html\n } catch (error) {\n throw new MailRenderError(\n name,\n `invalid MJML: ${describeMjmlError(error)}`,\n { cause: error }\n )\n }\n}\n\n/** Converts an HTML version to text. */\nexport const toText = async (html: string) => {\n const { convert } = await loadHtmlToText()\n return convert(html, {\n wordwrap: 78,\n selectors: [\n { selector: 'img', format: 'skip' },\n // Preview text and other content hidden from readers.\n { selector: '[style*=\"display:none\"]', format: 'skip' },\n { selector: '[style*=\"display: none\"]', format: 'skip' },\n { selector: 'a', options: { hideLinkHrefIfSameAsText: true } },\n ],\n })\n}\n\n/** `t()` of a set of translations, in a locale. */\nconst translator =\n (\n name: string,\n owner: string,\n translations: Translations,\n locale: string | undefined\n ) =>\n (key: string): string => {\n const entry = Object.hasOwn(translations, key)\n ? translations[key]\n : undefined\n // Without locale, there are no translations: entry is undefined.\n const text = entry?.[locale as string]\n if (text === undefined) {\n throw new MailRenderError(\n name,\n `unknown translation key \"${key}\" in the ${owner}.`\n )\n }\n return text\n }\n\n/** Calls a source function; errors other than render errors are wrapped. */\nconst produce = <C>(\n name: string,\n owner: string,\n source: string | ((context: C) => string),\n context: C\n): string => {\n if (typeof source === 'string') return source\n let result: unknown\n try {\n result = source(context)\n } catch (error) {\n if (error instanceof MailRenderError) throw error\n throw new MailRenderError(name, `the ${owner} source function failed.`, {\n cause: error,\n })\n }\n if (typeof result !== 'string') {\n throw new MailRenderError(\n name,\n `the ${owner} source function must return a string.`\n )\n }\n return result\n}\n\nconst contextOf = (\n definition: TemplateDefinition,\n locale: string | undefined\n): TranslateContext => ({\n locale,\n t: translator(definition.name, 'template', definition.translations, locale),\n})\n\n/** HTML of a template in a locale, before Mustache. */\nconst compile = async (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const { name, layout } = definition\n let source = produce(\n name,\n 'template',\n definition.source,\n contextOf(definition, locale)\n )\n if (layout) {\n source = produce(name, 'layout', layout.render, {\n locale,\n t: translator(name, 'layout', layout.translations, locale),\n content: source,\n })\n }\n return definition.format === 'mjml' ? compileMjml(name, source) : source\n}\n\n/** Cached `compile()`: the result only depends on the locale. */\nconst compiled = (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const key = locale ?? ''\n let html = definition.cache.get(key)\n if (!html) {\n html = compile(definition, locale)\n definition.cache.set(key, html)\n // A failure is not cached: the next render tries again.\n html.catch(() => definition.cache.delete(key))\n }\n return html\n}\n\nconst renderMustache = (\n name: string,\n part: string,\n source: string,\n data: object,\n escape: (value: unknown) => string\n) => {\n try {\n return mustache.render(source, data, {}, { escape })\n } catch (error) {\n throw new MailRenderError(\n name,\n `invalid Mustache in the ${part}: ${(error as Error).message}`,\n { cause: error }\n )\n }\n}\n\nconst parseData = async (\n definition: TemplateDefinition,\n data: unknown\n): Promise<object> => {\n const { name, schema } = definition\n if (!schema) {\n if (data !== undefined) {\n throw new MailDataError(name, [\n { path: [], message: 'the template has no schema and accepts no data' },\n ])\n }\n return {}\n }\n\n const result = await schema.safeParseAsync(data)\n if (!result.success) {\n throw new MailDataError(\n name,\n result.error.issues.map(({ path, message }) => ({\n path: path.map((key) => (typeof key === 'symbol' ? String(key) : key)),\n message,\n }))\n )\n }\n const parsed: unknown = result.data\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new MailDataError(name, [\n { path: [], message: 'the schema must produce an object' },\n ])\n }\n return parsed\n}\n\nconst subjectSource = (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const { subject } = definition\n if (typeof subject === 'string') return subject\n // The locale is one of the subject locales (checked by defineTemplate).\n return subject[locale as string] as string\n}\n\n/**\n * Renders a template: subject, HTML and text versions. The data is\n * validated by the schema of the template.\n *\n * ```ts\n * const { subject, html, text } = await renderMail(welcome, {\n * locale: 'fr-CA',\n * defaultLocale: 'en',\n * data: { firstName: 'Ada', url: 'https://app.example.com' },\n * })\n * ```\n */\nexport const renderMail = async <TData>(\n template: MailTemplate<TData>,\n options: RenderMailOptions<NoInfer<TData>>\n): Promise<RenderedMail> => {\n const definition = definitionOf(template)\n if (!definition) {\n throw new TypeError(\n 'renderMail(): expected a template created by defineTemplate().'\n )\n }\n const { name, locales } = definition\n const { locale: requested, defaultLocale } = options ?? {}\n\n const data = await parseData(definition, options?.data)\n\n let locale: string | undefined\n if (locales.length > 0) {\n locale = resolveLocale(locales, requested, defaultLocale)\n if (locale === undefined) {\n const asked = [requested, defaultLocale].filter(Boolean)\n throw new MailRenderError(\n name,\n asked.length > 0\n ? `no translations for ${asked.join(' or ')} (available: ${locales.join(', ')}).`\n : `no locale requested and no default locale (available: ${locales.join(', ')}).`\n )\n }\n }\n\n const html = renderMustache(\n name,\n 'HTML',\n await compiled(definition, locale),\n data,\n escapeHtml\n )\n\n const subject = toSubject(\n renderMustache(\n name,\n 'subject',\n subjectSource(definition, locale),\n data,\n noEscape\n )\n )\n\n const text = definition.text\n ? renderMustache(\n name,\n 'text',\n produce(name, 'text', definition.text, contextOf(definition, locale)),\n data,\n noEscape\n )\n : await toText(html)\n\n return { template: name, locale, subject, html, text }\n}\n","/**\n * Translations of a template or layout: one entry per key, with a text for\n * each locale. Every entry must have the same locales.\n *\n * ```ts\n * { greeting: { en: 'Hello', fr: 'Bonjour' } }\n * ```\n */\nexport type Translations<L extends string = string> = Readonly<\n Record<string, Readonly<Record<L, string>>>\n>\n\n/** A text for each locale, like `{ en: 'Welcome', fr: 'Bienvenue' }`. */\nexport type Localized<L extends string = string> = Readonly<Record<L, string>>\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Locales of a set of localized texts, checked: each entry must be an object\n * of strings, with the same locales as the others. Throws a `TypeError`\n * prefixed with `prefix`.\n */\nexport const collectLocales = (\n prefix: string,\n entries: readonly (readonly [label: string, value: unknown])[]\n): string[] => {\n let locales: string[] | undefined\n let reference = ''\n\n for (const [label, value] of entries) {\n if (!isRecord(value)) {\n throw new TypeError(\n `${prefix}: ${label} must be an object of texts by locale.`\n )\n }\n const keys = Object.keys(value).sort()\n if (keys.length === 0) {\n throw new TypeError(`${prefix}: ${label} has no locale.`)\n }\n for (const key of keys) {\n if (typeof value[key] !== 'string') {\n throw new TypeError(`${prefix}: ${label}.${key} must be a string.`)\n }\n }\n\n if (locales === undefined) {\n locales = keys\n reference = label\n } else if (keys.join() !== locales.join()) {\n throw new TypeError(\n `${prefix}: ${label} has the locales ${keys.join(', ')}, but ${reference} has ${locales.join(', ')}. Every text needs the same locales.`\n )\n }\n }\n return locales ?? []\n}\n\n/** Entries of a translations object, for `collectLocales`. */\nexport const translationEntries = (\n prefix: string,\n translations: unknown\n): [string, unknown][] => {\n if (translations === undefined) return []\n if (!isRecord(translations)) {\n throw new TypeError(`${prefix}: translations must be an object.`)\n }\n return Object.entries(translations).map(([key, value]) => [\n `translations.${key}`,\n value,\n ])\n}\n\n/** `fr` for `fr-CA` or `fr_CA`. */\nconst baseOf = (locale: string) => locale.split(/[-_]/)[0] ?? locale\n\n/**\n * Locale to render: the requested one, its base language (`fr-CA` → `fr`),\n * then the default locale and its base language. Locales are compared\n * case-insensitively. Undefined if no candidate is available.\n */\nexport const resolveLocale = (\n available: readonly string[],\n requested: string | undefined,\n defaultLocale: string | undefined\n): string | undefined => {\n const candidates = [requested, defaultLocale].flatMap((locale) =>\n locale ? [locale, baseOf(locale)] : []\n )\n for (const candidate of candidates) {\n const lower = candidate.toLowerCase()\n const match = available.find((locale) => locale.toLowerCase() === lower)\n if (match !== undefined) return match\n }\n return undefined\n}\n","import type { z } from 'zod'\nimport {\n collectLocales,\n translationEntries,\n type Localized,\n type Translations,\n} from './locales.js'\n\n/** Source format of a template or layout. */\nexport type MailFormat = 'mjml' | 'html'\n\n/** Context of the functions that produce a template or layout source. */\nexport interface TranslateContext {\n /** Locale being rendered; undefined for a template without translations. */\n readonly locale: string | undefined\n /** Text of a translation key in this locale. Throws for an unknown key. */\n t(key: string): string\n}\n\n/** Context of a layout: its translations, and the content of the template. */\nexport interface LayoutContext extends TranslateContext {\n /** Source of the template, in the format of the layout. */\n readonly content: string\n}\n\n/** A source, or a function that produces it in the rendered locale. */\nexport type Source = string | ((context: TranslateContext) => string)\n\ntype LayoutSource =\n | { mjml: (context: LayoutContext) => string; html?: never }\n | { html: (context: LayoutContext) => string; mjml?: never }\n\n/** Options of `defineLayout()`. */\nexport type LayoutOptions<L extends string = string> = {\n /** Texts of the layout, read with `t()`. */\n translations?: Translations<L>\n} & LayoutSource\n\n/** Layout created by `defineLayout()`. */\nexport interface MailLayout<L extends string = string> {\n readonly format: MailFormat\n /** Locales of the layout translations; empty without translations. */\n readonly locales: readonly string[]\n /**\n * Type-level only: the locales the layout can render.\n * @internal\n */\n readonly __locales?: (locale: L) => void\n}\n\ntype TemplateSource =\n { mjml: Source; html?: never } | { html: Source; mjml?: never }\n\n/** Options of `defineTemplate()`. */\nexport type TemplateOptions<\n S extends z.ZodType | undefined = undefined,\n L extends string = string,\n> = {\n /** Name of the template, used in errors and logs. */\n name: string\n /**\n * Layout that receives the template source as `content`. It must have\n * translations in the template locales.\n */\n layout?: MailLayout<NoInfer<string extends L ? never : L>>\n /** Schema of the data. Without it, the template accepts no data. */\n schema?: S\n /**\n * Subject, by locale or the same for all. Rendered by Mustache without\n * HTML escaping: `'Welcome {{firstName}}'`.\n */\n subject: string | Localized<L>\n /** Texts of the template, read with `t()`. */\n translations?: Translations<L>\n /**\n * Text version, rendered by Mustache without HTML escaping. Defaults to a\n * conversion of the HTML version.\n */\n text?: Source\n} & TemplateSource\n\n/** Data accepted by a template, given its schema. */\nexport type DataOf<S> = S extends z.ZodType ? z.input<S> : undefined\n\n/** Template created by `defineTemplate()`. */\nexport interface MailTemplate<TData = undefined> {\n readonly name: string\n readonly format: MailFormat\n /** Locales of the template; empty for a template without translations. */\n readonly locales: readonly string[]\n /**\n * Type-level only: the data of the template.\n * @internal\n */\n readonly __data?: TData\n}\n\n/** Data accepted by a template. */\nexport type TemplateData<T> = T extends MailTemplate<infer D> ? D : never\n\n/** Definition of a layout, used by the renderer. */\nexport interface LayoutDefinition {\n readonly format: MailFormat\n readonly translations: Translations\n readonly render: (context: LayoutContext) => string\n}\n\n/** Definition of a template, used by the renderer. */\nexport interface TemplateDefinition {\n readonly name: string\n readonly format: MailFormat\n readonly locales: readonly string[]\n readonly source: Source\n readonly layout: LayoutDefinition | undefined\n readonly schema: z.ZodType | undefined\n readonly subject: string | Localized\n readonly translations: Translations\n readonly text: Source | undefined\n /** Compiled HTML by locale, before Mustache (see `render.ts`). */\n readonly cache: Map<string, Promise<string>>\n}\n\nconst layouts = new WeakMap<object, LayoutDefinition>()\nconst templates = new WeakMap<object, TemplateDefinition>()\n\n/** Definition of a template, or undefined for another value. */\nexport const definitionOf = (template: unknown) =>\n templates.get(template as object)\n\nconst LAYOUT_OPTIONS = new Set(['translations', 'mjml', 'html'])\nconst TEMPLATE_OPTIONS = new Set([\n 'name',\n 'layout',\n 'schema',\n 'subject',\n 'translations',\n 'text',\n 'mjml',\n 'html',\n])\n\nconst checkOptions = (\n prefix: string,\n options: unknown,\n allowed: ReadonlySet<string>\n): Record<string, unknown> => {\n if (\n typeof options !== 'object' ||\n options === null ||\n Array.isArray(options)\n ) {\n throw new TypeError(`${prefix}: expected an object.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.has(key)) {\n throw new TypeError(`${prefix}: unknown option \"${key}\".`)\n }\n }\n return options as Record<string, unknown>\n}\n\n/** Format of a source: exactly one of `mjml` and `html`. */\nconst formatOf = (\n prefix: string,\n options: Record<string, unknown>,\n isValid: (source: unknown) => boolean,\n expected: string\n): MailFormat => {\n const formats = (['mjml', 'html'] as const).filter(\n (format) => options[format] !== undefined\n )\n const [format] = formats\n if (format === undefined || formats.length > 1) {\n throw new TypeError(`${prefix}: set exactly one of mjml and html.`)\n }\n if (!isValid(options[format])) {\n throw new TypeError(`${prefix}: ${format} must be ${expected}.`)\n }\n return format\n}\n\nconst isSource = (source: unknown) =>\n typeof source === 'string' || typeof source === 'function'\n\n/**\n * Defines a layout: the structure shared by templates (head, header,\n * footer). The layout function receives the template source as `content`,\n * and its own translations through `t()`.\n *\n * ```ts\n * const baseLayout = defineLayout({\n * translations: { footer: { en: 'Sent by Acme', fr: 'Envoyé par Acme' } },\n * mjml: ({ t, content }) => `<mjml><mj-body>${content}…${t('footer')}…</mj-body></mjml>`,\n * })\n * ```\n */\nexport const defineLayout = <L extends string = string>(\n options: LayoutOptions<L>\n): MailLayout<L> => {\n const prefix = 'Invalid mail layout'\n const values = checkOptions(prefix, options, LAYOUT_OPTIONS)\n const format = formatOf(\n prefix,\n values,\n (source) => typeof source === 'function',\n 'a function that receives the template content'\n )\n const locales = collectLocales(\n prefix,\n translationEntries(prefix, values.translations)\n )\n\n const layout: MailLayout<L> = Object.freeze({\n format,\n locales: Object.freeze(locales),\n })\n layouts.set(layout, {\n format,\n translations: (values.translations ?? {}) as Translations,\n render: values[format] as LayoutDefinition['render'],\n })\n return layout\n}\n\n/**\n * Defines an email template. The source (`mjml` or `html`) is rendered in\n * three steps: `t()` inserts the translations, MJML compiles the result\n * (cached by locale), then Mustache inserts the data.\n *\n * ```ts\n * const welcome = defineTemplate({\n * name: 'welcome',\n * layout: baseLayout,\n * schema: z.object({ firstName: z.string(), url: z.url() }),\n * subject: { en: 'Welcome {{firstName}}', fr: 'Bienvenue {{firstName}}' },\n * translations: { cta: { en: 'Get started', fr: 'Commencer' } },\n * mjml: ({ t }) => `<mj-section>…<mj-button href=\"{{{ url }}}\">${t('cta')}</mj-button>…</mj-section>`,\n * })\n * ```\n */\nexport const defineTemplate = <\n S extends z.ZodType | undefined = undefined,\n L extends string = string,\n>(\n options: TemplateOptions<S, L>\n): MailTemplate<DataOf<S>> => {\n const name = (options as { name?: unknown } | null)?.name\n const prefix =\n typeof name === 'string' && name !== ''\n ? `Invalid mail template \"${name}\"`\n : 'Invalid mail template'\n const values = checkOptions(prefix, options, TEMPLATE_OPTIONS)\n if (typeof name !== 'string' || name === '') {\n throw new TypeError(`${prefix}: name must be a non-empty string.`)\n }\n\n const format = formatOf(prefix, values, isSource, 'a string or a function')\n\n const { layout, schema, subject, text } = values\n let layoutDefinition: LayoutDefinition | undefined\n if (layout !== undefined) {\n // WeakMap.get() returns undefined for any other value.\n layoutDefinition = layouts.get(layout as object)\n if (!layoutDefinition) {\n throw new TypeError(\n `${prefix}: layout must be created by defineLayout().`\n )\n }\n if (layoutDefinition.format !== format) {\n throw new TypeError(\n `${prefix}: the template is in ${format}, but its layout is in ${layoutDefinition.format}.`\n )\n }\n }\n if (\n schema !== undefined &&\n typeof (schema as { safeParseAsync?: unknown } | null)?.safeParseAsync !==\n 'function'\n ) {\n throw new TypeError(`${prefix}: schema must be a Zod schema.`)\n }\n if (typeof subject !== 'string' && typeof subject !== 'object') {\n throw new TypeError(\n `${prefix}: subject must be a string or an object of texts by locale.`\n )\n }\n if (text !== undefined && !isSource(text)) {\n throw new TypeError(`${prefix}: text must be a string or a function.`)\n }\n\n const own = collectLocales(prefix, [\n ...(typeof subject === 'object' ? [['subject', subject] as const] : []),\n ...translationEntries(prefix, values.translations),\n ])\n const layoutLocales = layout ? (layout as MailLayout).locales : []\n if (own.length > 0 && layoutLocales.length > 0) {\n const missing = own.filter((locale) => !layoutLocales.includes(locale))\n if (missing.length > 0) {\n throw new TypeError(\n `${prefix}: its layout has no translations in ${missing.join(', ')}.`\n )\n }\n }\n const locales = own.length > 0 ? own : [...layoutLocales]\n\n const template: MailTemplate<DataOf<S>> = Object.freeze({\n name,\n format,\n locales: Object.freeze(locales),\n })\n templates.set(template, {\n name,\n format,\n locales,\n source: values[format] as Source,\n layout: layoutDefinition,\n schema: schema as z.ZodType | undefined,\n subject: subject as string | Localized,\n translations: (values.translations ?? {}) as Translations,\n text: text as Source | undefined,\n cache: new Map(),\n })\n return template\n}\n","import { MailerError, MailSendError } from './errors.js'\nimport {\n checkKeys,\n parseAddress,\n parseEnvelope,\n parseRecipients,\n toSubject,\n type Envelope,\n type EnvelopeOptions,\n type MailAddress,\n type MailMessage,\n type MailRecipients,\n} from './message.js'\nimport {\n renderMail,\n toText,\n type DataOption,\n type RenderedMail,\n type RenderMailOptions,\n} from './render.js'\nimport type { MailTemplate } from './template.js'\n\n/** Result of a provider. */\nexport interface ProviderResult {\n /** Identifier of the message given by the provider, if any. */\n messageId?: string | undefined\n}\n\n/**\n * Sends messages: SMTP server, email API, test outbox… Throw a\n * `MailSendError` on failure, or a `MailMessageError` for a message the\n * provider cannot send; other errors are wrapped in a `MailSendError`.\n */\nexport interface MailProvider {\n /** Name used in errors and logs, like `smtp`. */\n readonly name: string\n send(message: MailMessage): Promise<ProviderResult | void>\n}\n\n/**\n * Logger of the mailer. A Dotpress or Pino logger fits; `mailerPlugin` uses\n * the logger of the request.\n */\nexport interface MailerLogger {\n info(data: object, message: string): void\n}\n\n/** Options of `createMailer()`. */\nexport interface MailerOptions {\n provider: MailProvider\n /** Default sender: `'Acme <noreply@acme.com>'`. */\n from: MailAddress\n /** Default reply-to addresses. */\n replyTo?: MailRecipients | undefined\n /** Locale used when the requested one is not available. */\n defaultLocale?: string | undefined\n /** Logs each sent message (template, provider, message ID). */\n logger?: MailerLogger | undefined\n}\n\n/** Options of `mailer.send()`. */\nexport type SendMailOptions<TData> = EnvelopeOptions & {\n /** Overrides the subject of the template. */\n subject?: string | undefined\n /** Locale to render, like `fr-CA` (see `renderMail()`). */\n locale?: string | undefined\n} & DataOption<TData>\n\n/** Options of `mailer.sendMessage()`: a message rendered elsewhere. */\nexport interface MessageOptions extends EnvelopeOptions {\n subject: string\n /** HTML version. At least one of `html` and `text` is required. */\n html?: string | undefined\n /** Text version. Converted from the HTML version if missing. */\n text?: string | undefined\n}\n\n/** Result of a sent message. */\nexport interface SendResult {\n /** Identifier of the message given by the provider, if any. */\n readonly messageId: string | undefined\n}\n\nexport interface Mailer {\n readonly provider: MailProvider\n /** Renders a template, then sends it. */\n send<TData>(\n template: MailTemplate<TData>,\n options: SendMailOptions<NoInfer<TData>>\n ): Promise<SendResult>\n /**\n * Renders a template without sending it. The default locale is the one\n * of the mailer.\n */\n render<TData>(\n template: MailTemplate<TData>,\n options: RenderMailOptions<NoInfer<TData>>\n ): Promise<RenderedMail>\n /** Sends a message rendered elsewhere. */\n sendMessage(options: MessageOptions): Promise<SendResult>\n /** The same mailer, with another logger. */\n withLogger(logger: MailerLogger): Mailer\n}\n\nconst MAILER_OPTIONS = new Set([\n 'provider',\n 'from',\n 'replyTo',\n 'defaultLocale',\n 'logger',\n])\nconst ENVELOPE_OPTIONS = [\n 'to',\n 'cc',\n 'bcc',\n 'replyTo',\n 'from',\n 'attachments',\n 'headers',\n 'tags',\n 'metadata',\n 'subject',\n]\nconst SEND_OPTIONS = new Set([...ENVELOPE_OPTIONS, 'locale', 'data'])\nconst MESSAGE_OPTIONS = new Set([...ENVELOPE_OPTIONS, 'html', 'text'])\n\nconst checkLogger = (logger: unknown) => {\n if (typeof (logger as MailerLogger | null)?.info !== 'function') {\n throw new TypeError('Invalid mailer logger: expected an info() method.')\n }\n}\n\n/** Freezes a message and its lists. */\nconst freeze = (message: MailMessage): MailMessage => {\n for (const value of Object.values(message)) {\n if (typeof value === 'object' && value !== null) Object.freeze(value)\n }\n return Object.freeze(message)\n}\n\n/**\n * Creates a mailer: renders templates and sends messages through a\n * provider.\n *\n * ```ts\n * const mailer = createMailer({\n * provider: createSmtpProvider({ host: 'smtp.example.com' }),\n * from: 'Acme <noreply@acme.com>',\n * defaultLocale: 'en',\n * })\n * await mailer.send(welcome, { to: user.email, locale: user.locale, data })\n * ```\n */\nexport const createMailer = (options: MailerOptions): Mailer => {\n const values = checkKeys('Invalid mailer options', options, MAILER_OPTIONS)\n const { provider, defaultLocale, logger } = values as Partial<MailerOptions>\n if (\n typeof provider?.name !== 'string' ||\n provider.name === '' ||\n typeof provider.send !== 'function'\n ) {\n throw new TypeError(\n 'Invalid mailer options: provider must have a name and a send() method.'\n )\n }\n if (\n defaultLocale !== undefined &&\n (typeof defaultLocale !== 'string' || defaultLocale === '')\n ) {\n throw new TypeError(\n 'Invalid mailer options: defaultLocale must be a non-empty string.'\n )\n }\n if (logger !== undefined) checkLogger(logger)\n if (values.from === undefined) {\n throw new TypeError('Invalid mailer options: from is required.')\n }\n\n const defaults = {\n from: parseAddress(values.from, 'from'),\n replyTo: parseRecipients(values.replyTo, 'replyTo'),\n }\n\n const create = (logger: MailerLogger | undefined): Mailer => {\n const deliver = async (\n envelope: Envelope,\n content: Pick<\n MailMessage,\n 'subject' | 'html' | 'text' | 'template' | 'locale'\n >\n ): Promise<SendResult> => {\n const message = freeze({ ...envelope, ...content })\n let result: ProviderResult | void\n try {\n result = await provider.send(message)\n } catch (error) {\n // Send errors, and limits of the provider (MailMessageError).\n if (error instanceof MailerError) throw error\n throw new MailSendError(\n provider.name,\n error instanceof Error ? error.message : String(error),\n { cause: error }\n )\n }\n\n const messageId =\n typeof result?.messageId === 'string' ? result.messageId : undefined\n logger?.info(\n {\n provider: provider.name,\n template: message.template,\n messageId,\n recipients:\n message.to.length + message.cc.length + message.bcc.length,\n },\n 'Mail sent'\n )\n return { messageId }\n }\n\n const render: Mailer['render'] = (template, renderOptions) =>\n renderMail(template, {\n defaultLocale,\n ...renderOptions,\n } as never)\n\n const mailer: Mailer = {\n provider,\n\n render,\n\n send: async (template, sendOptions) => {\n const { subject, locale, data, ...envelopeOptions } = checkKeys(\n 'mailer.send()',\n sendOptions,\n SEND_OPTIONS\n ) as unknown as SendMailOptions<unknown>\n if (subject !== undefined && typeof subject !== 'string') {\n throw new TypeError('mailer.send(): subject must be a string.')\n }\n // Checked before rendering: an invalid address fails fast.\n const envelope = parseEnvelope(envelopeOptions, defaults)\n const mail = await render(template, { locale, data } as never)\n return deliver(envelope, {\n subject: subject === undefined ? mail.subject : toSubject(subject),\n html: mail.html,\n text: mail.text,\n template: mail.template,\n locale: mail.locale,\n })\n },\n\n sendMessage: async (messageOptions) => {\n const { subject, html, text, ...envelopeOptions } = checkKeys(\n 'mailer.sendMessage()',\n messageOptions,\n MESSAGE_OPTIONS\n ) as unknown as MessageOptions\n if (typeof subject !== 'string') {\n throw new TypeError('mailer.sendMessage(): subject must be a string.')\n }\n for (const [key, value] of [\n ['html', html],\n ['text', text],\n ] as const) {\n if (value !== undefined && typeof value !== 'string') {\n throw new TypeError(\n `mailer.sendMessage(): ${key} must be a string.`\n )\n }\n }\n if (html === undefined && text === undefined) {\n throw new TypeError('mailer.sendMessage(): set html, text, or both.')\n }\n const envelope = parseEnvelope(envelopeOptions, defaults)\n return deliver(envelope, {\n subject: toSubject(subject),\n html,\n text: text ?? (await toText(html as string)),\n template: undefined,\n locale: undefined,\n })\n },\n\n withLogger: (other) => {\n checkLogger(other)\n return create(other)\n },\n }\n return Object.freeze(mailer)\n }\n\n return create(logger)\n}\n","import type { MailProvider } from '../mailer.js'\nimport { formatAddress, type Address } from '../message.js'\n\n/** Options of `createConsoleProvider()`. */\nexport interface ConsoleProviderOptions {\n /**\n * Also prints the text version. Off by default: messages often contain\n * secrets (password reset links, login codes).\n */\n body?: boolean | undefined\n /** Output function. Defaults to `console.log`. */\n write?: ((output: string) => void) | undefined\n}\n\nconst list = (addresses: readonly Address[]) =>\n addresses.map(formatAddress).join(', ')\n\n/**\n * Creates a provider that prints messages instead of sending them, for\n * development. Never use it as a fallback in production: messages would\n * be lost.\n */\nexport const createConsoleProvider = (\n options: ConsoleProviderOptions = {}\n): MailProvider => {\n const { body = false, write = (output) => console.log(output) } = options\n\n return {\n name: 'console',\n send: async (message) => {\n const lines = [\n `[mail] ${message.template ?? 'message'}${message.locale ? ` (${message.locale})` : ''}`,\n `From: ${formatAddress(message.from)}`,\n `To: ${list(message.to)}`,\n ]\n for (const [label, addresses] of [\n ['Cc', message.cc],\n ['Bcc', message.bcc],\n ['Reply-To', message.replyTo],\n ] as const) {\n if (addresses.length > 0) lines.push(`${label}: ${list(addresses)}`)\n }\n lines.push(`Subject: ${message.subject}`)\n if (message.attachments.length > 0) {\n lines.push(\n `Attachments: ${message.attachments\n .map(({ filename }) => filename)\n .join(', ')}`\n )\n }\n if (message.tags.length > 0) {\n lines.push(`Tags: ${message.tags.join(', ')}`)\n }\n const metadata = Object.entries(message.metadata)\n if (metadata.length > 0) {\n lines.push(\n `Metadata: ${metadata.map(([key, value]) => `${key}=${value}`).join(', ')}`\n )\n }\n if (body) lines.push('', message.text)\n write(lines.join('\\n'))\n },\n }\n}\n","import type { MailProvider } from '../mailer.js'\nimport type { MailMessage } from '../message.js'\n\n/** Provider that keeps messages in memory, for tests. */\nexport interface MemoryProvider extends MailProvider {\n /** Sent messages, oldest first. */\n readonly sent: readonly MailMessage[]\n /** Last sent message. */\n last(): MailMessage | undefined\n /** Forgets the sent messages. */\n clear(): void\n}\n\n/**\n * Creates a provider that keeps messages in memory instead of sending\n * them, for tests.\n *\n * ```ts\n * const outbox = createMemoryProvider()\n * const mailer = createMailer({ provider: outbox, from: 'noreply@example.com' })\n * // …\n * expect(outbox.last()?.subject).toBe('Welcome Ada')\n * ```\n */\nexport const createMemoryProvider = (): MemoryProvider => {\n const sent: MailMessage[] = []\n let count = 0\n\n return {\n name: 'memory',\n sent,\n send: async (message) => {\n sent.push(message)\n count += 1\n return { messageId: `memory-${count}` }\n },\n last: () => sent.at(-1),\n clear: () => {\n sent.length = 0\n },\n }\n}\n"],"mappings":"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","names":["format","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/render.ts","../src/locales.ts","../src/template.ts","../src/mailer.ts","../src/providers/console.ts","../src/providers/memory.ts"],"sourcesContent":["import mustache from 'mustache'\nimport { MailDataError, MailRenderError } from './errors.js'\nimport { resolveLocale, type Translations } from './locales.js'\nimport { toSubject } from './message.js'\nimport {\n definitionOf,\n type MailTemplate,\n type TemplateDefinition,\n type TranslateContext,\n} from './template.js'\n\n/** `data` option: required when the template has a schema. */\nexport type DataOption<TData> = [TData] extends [undefined]\n ? { data?: undefined }\n : { data: TData }\n\n/** Options of `renderMail()`. */\nexport type RenderMailOptions<TData> = {\n /**\n * Locale to render, like `fr-CA`. Falls back to its base language (`fr`),\n * then to `defaultLocale`.\n */\n locale?: string | undefined\n /** Locale used when the requested one is not available. */\n defaultLocale?: string | undefined\n} & DataOption<TData>\n\n/** A rendered email. */\nexport interface RenderedMail {\n /** Name of the template. */\n readonly template: string\n /** Rendered locale; undefined for a template without translations. */\n readonly locale: string | undefined\n readonly subject: string\n readonly html: string\n readonly text: string\n}\n\nconst HTML_ENTITIES: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n}\n\n/**\n * HTML escaping of `{{ }}` values. Unlike the default escaping of Mustache,\n * `/`, `=` and `` ` `` are kept: many email clients do not decode them in\n * links.\n */\nconst escapeHtml = (value: unknown) =>\n String(value).replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char] as string)\n\nconst noEscape = (value: unknown) => String(value)\n\ntype Mjml = typeof import('mjml')\ntype HtmlToText = typeof import('html-to-text')\n\nlet mjmlModule: Promise<Mjml> | undefined\nlet htmlToTextModule: Promise<HtmlToText> | undefined\n\n/** MJML, loaded on the first MJML render. */\nconst loadMjml = () =>\n (mjmlModule ??= import('mjml').then(\n (module) => module.default,\n (error: unknown) => {\n mjmlModule = undefined\n throw error\n }\n ))\n\n/** html-to-text, loaded on the first text conversion. */\nconst loadHtmlToText = () =>\n (htmlToTextModule ??= import('html-to-text').then(\n (module) => module,\n (error: unknown) => {\n htmlToTextModule = undefined\n throw error\n }\n ))\n\n/** Whether loading failed because the mjml package is missing. */\nconst isMjmlMissing = (error: unknown): boolean =>\n error instanceof Error &&\n (((error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND' &&\n error.message.includes(\"'mjml'\")) ||\n isMjmlMissing(error.cause))\n\ninterface MjmlIssue {\n line?: number\n tagName?: string\n message?: string\n}\n\n/** Message of an MJML failure, without the file paths of MJML messages. */\nconst describeMjmlError = (error: unknown) => {\n const issues = (error as { errors?: unknown } | null)?.errors\n if (Array.isArray(issues) && issues.length > 0) {\n return (issues as MjmlIssue[])\n .map(\n ({ line, tagName, message }) => `line ${line} (${tagName}): ${message}`\n )\n .join('; ')\n }\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Compiles MJML to HTML, strict validation. */\nconst compileMjml = async (name: string, source: string) => {\n let mjml: Mjml\n try {\n mjml = await loadMjml()\n } catch (error) {\n throw new MailRenderError(\n name,\n isMjmlMissing(error)\n ? 'MJML is not installed. Install the mjml package, or use html templates.'\n : 'MJML could not be loaded.',\n { cause: error }\n )\n }\n try {\n const { html } = await mjml(source, {\n validationLevel: 'strict',\n ignoreIncludes: true,\n })\n return html\n } catch (error) {\n throw new MailRenderError(\n name,\n `invalid MJML: ${describeMjmlError(error)}`,\n { cause: error }\n )\n }\n}\n\n/** Converts an HTML version to text. */\nexport const toText = async (html: string) => {\n const { convert } = await loadHtmlToText()\n return convert(html, {\n wordwrap: 78,\n selectors: [\n { selector: 'img', format: 'skip' },\n // Preview text and other content hidden from readers.\n { selector: '[style*=\"display:none\"]', format: 'skip' },\n { selector: '[style*=\"display: none\"]', format: 'skip' },\n { selector: 'a', options: { hideLinkHrefIfSameAsText: true } },\n ],\n })\n}\n\n/** `t()` of a set of translations, in a locale. */\nconst translator =\n (\n name: string,\n owner: string,\n translations: Translations,\n locale: string | undefined\n ) =>\n (key: string): string => {\n const entry = Object.hasOwn(translations, key)\n ? translations[key]\n : undefined\n // Without locale, there are no translations: entry is undefined.\n const text = entry?.[locale as string]\n if (text === undefined) {\n throw new MailRenderError(\n name,\n `unknown translation key \"${key}\" in the ${owner}.`\n )\n }\n return text\n }\n\n/** Calls a source function; errors other than render errors are wrapped. */\nconst produce = <C>(\n name: string,\n owner: string,\n source: string | ((context: C) => string),\n context: C\n): string => {\n if (typeof source === 'string') return source\n let result: unknown\n try {\n result = source(context)\n } catch (error) {\n if (error instanceof MailRenderError) throw error\n throw new MailRenderError(name, `the ${owner} source function failed.`, {\n cause: error,\n })\n }\n if (typeof result !== 'string') {\n throw new MailRenderError(\n name,\n `the ${owner} source function must return a string.`\n )\n }\n return result\n}\n\nconst contextOf = (\n definition: TemplateDefinition,\n locale: string | undefined\n): TranslateContext => ({\n locale,\n t: translator(definition.name, 'template', definition.translations, locale),\n})\n\n/** HTML of a template in a locale, before Mustache. */\nconst compile = async (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const { name, layout } = definition\n let source = produce(\n name,\n 'template',\n definition.source,\n contextOf(definition, locale)\n )\n if (layout) {\n source = produce(name, 'layout', layout.render, {\n locale,\n t: translator(name, 'layout', layout.translations, locale),\n content: source,\n })\n }\n return definition.format === 'mjml' ? compileMjml(name, source) : source\n}\n\n/** Cached `compile()`: the result only depends on the locale. */\nconst compiled = (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const key = locale ?? ''\n let html = definition.cache.get(key)\n if (!html) {\n html = compile(definition, locale)\n definition.cache.set(key, html)\n // A failure is not cached: the next render tries again.\n html.catch(() => definition.cache.delete(key))\n }\n return html\n}\n\nconst renderMustache = (\n name: string,\n part: string,\n source: string,\n data: object,\n escape: (value: unknown) => string\n) => {\n try {\n return mustache.render(source, data, {}, { escape })\n } catch (error) {\n throw new MailRenderError(\n name,\n `invalid Mustache in the ${part}: ${(error as Error).message}`,\n { cause: error }\n )\n }\n}\n\nconst parseData = async (\n definition: TemplateDefinition,\n data: unknown\n): Promise<object> => {\n const { name, schema } = definition\n if (!schema) {\n if (data !== undefined) {\n throw new MailDataError(name, [\n { path: [], message: 'the template has no schema and accepts no data' },\n ])\n }\n return {}\n }\n\n const result = await schema.safeParseAsync(data)\n if (!result.success) {\n throw new MailDataError(\n name,\n result.error.issues.map(({ path, message }) => ({\n path: path.map((key) => (typeof key === 'symbol' ? String(key) : key)),\n message,\n }))\n )\n }\n const parsed: unknown = result.data\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new MailDataError(name, [\n { path: [], message: 'the schema must produce an object' },\n ])\n }\n return parsed\n}\n\nconst subjectSource = (\n definition: TemplateDefinition,\n locale: string | undefined\n) => {\n const { subject } = definition\n if (typeof subject === 'string') return subject\n // The locale is one of the subject locales (checked by defineTemplate).\n return subject[locale as string] as string\n}\n\n/**\n * Renders a template: subject, HTML and text versions. The data is\n * validated by the schema of the template.\n *\n * ```ts\n * const { subject, html, text } = await renderMail(welcome, {\n * locale: 'fr-CA',\n * defaultLocale: 'en',\n * data: { firstName: 'Ada', url: 'https://app.example.com' },\n * })\n * ```\n */\nexport const renderMail = async <TData>(\n template: MailTemplate<TData>,\n options: RenderMailOptions<NoInfer<TData>>\n): Promise<RenderedMail> => {\n const definition = definitionOf(template)\n if (!definition) {\n throw new TypeError(\n 'renderMail(): expected a template created by defineTemplate().'\n )\n }\n const { name, locales } = definition\n const { locale: requested, defaultLocale } = options ?? {}\n\n const data = await parseData(definition, options?.data)\n\n let locale: string | undefined\n if (locales.length > 0) {\n locale = resolveLocale(locales, requested, defaultLocale)\n if (locale === undefined) {\n const asked = [requested, defaultLocale].filter(Boolean)\n throw new MailRenderError(\n name,\n asked.length > 0\n ? `no translations for ${asked.join(' or ')} (available: ${locales.join(', ')}).`\n : `no locale requested and no default locale (available: ${locales.join(', ')}).`\n )\n }\n }\n\n const html = renderMustache(\n name,\n 'HTML',\n await compiled(definition, locale),\n data,\n escapeHtml\n )\n\n const subject = toSubject(\n renderMustache(\n name,\n 'subject',\n subjectSource(definition, locale),\n data,\n noEscape\n )\n )\n\n const text = definition.text\n ? renderMustache(\n name,\n 'text',\n produce(name, 'text', definition.text, contextOf(definition, locale)),\n data,\n noEscape\n )\n : await toText(html)\n\n return { template: name, locale, subject, html, text }\n}\n","/**\n * Translations of a template or layout: one entry per key, with a text for\n * each locale. Every entry must have the same locales.\n *\n * ```ts\n * { greeting: { en: 'Hello', fr: 'Bonjour' } }\n * ```\n */\nexport type Translations<L extends string = string> = Readonly<\n Record<string, Readonly<Record<L, string>>>\n>\n\n/** A text for each locale, like `{ en: 'Welcome', fr: 'Bienvenue' }`. */\nexport type Localized<L extends string = string> = Readonly<Record<L, string>>\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * Locales of a set of localized texts, checked: each entry must be an object\n * of strings, with the same locales as the others. Throws a `TypeError`\n * prefixed with `prefix`.\n */\nexport const collectLocales = (\n prefix: string,\n entries: readonly (readonly [label: string, value: unknown])[]\n): string[] => {\n let locales: string[] | undefined\n let reference = ''\n\n for (const [label, value] of entries) {\n if (!isRecord(value)) {\n throw new TypeError(\n `${prefix}: ${label} must be an object of texts by locale.`\n )\n }\n const keys = Object.keys(value).sort()\n if (keys.length === 0) {\n throw new TypeError(`${prefix}: ${label} has no locale.`)\n }\n for (const key of keys) {\n if (typeof value[key] !== 'string') {\n throw new TypeError(`${prefix}: ${label}.${key} must be a string.`)\n }\n }\n\n if (locales === undefined) {\n locales = keys\n reference = label\n } else if (keys.join() !== locales.join()) {\n throw new TypeError(\n `${prefix}: ${label} has the locales ${keys.join(', ')}, but ${reference} has ${locales.join(', ')}. Every text needs the same locales.`\n )\n }\n }\n return locales ?? []\n}\n\n/** Entries of a translations object, for `collectLocales`. */\nexport const translationEntries = (\n prefix: string,\n translations: unknown\n): [string, unknown][] => {\n if (translations === undefined) return []\n if (!isRecord(translations)) {\n throw new TypeError(`${prefix}: translations must be an object.`)\n }\n return Object.entries(translations).map(([key, value]) => [\n `translations.${key}`,\n value,\n ])\n}\n\n/** `fr` for `fr-CA` or `fr_CA`. */\nconst baseOf = (locale: string) => locale.split(/[-_]/)[0] ?? locale\n\n/**\n * Locale to render: the requested one, its base language (`fr-CA` → `fr`),\n * then the default locale and its base language. Locales are compared\n * case-insensitively. Undefined if no candidate is available.\n */\nexport const resolveLocale = (\n available: readonly string[],\n requested: string | undefined,\n defaultLocale: string | undefined\n): string | undefined => {\n const candidates = [requested, defaultLocale].flatMap((locale) =>\n locale ? [locale, baseOf(locale)] : []\n )\n for (const candidate of candidates) {\n const lower = candidate.toLowerCase()\n const match = available.find((locale) => locale.toLowerCase() === lower)\n if (match !== undefined) return match\n }\n return undefined\n}\n","import type { z } from 'zod'\nimport {\n collectLocales,\n translationEntries,\n type Localized,\n type Translations,\n} from './locales.js'\n\n/** Source format of a template or layout. */\nexport type MailFormat = 'mjml' | 'html'\n\n/** Context of the functions that produce a template or layout source. */\nexport interface TranslateContext {\n /** Locale being rendered; undefined for a template without translations. */\n readonly locale: string | undefined\n /** Text of a translation key in this locale. Throws for an unknown key. */\n t(key: string): string\n}\n\n/** Context of a layout: its translations, and the content of the template. */\nexport interface LayoutContext extends TranslateContext {\n /** Source of the template, in the format of the layout. */\n readonly content: string\n}\n\n/** A source, or a function that produces it in the rendered locale. */\nexport type TemplateSource = string | ((context: TranslateContext) => string)\n\ntype LayoutBody =\n | { mjml: (context: LayoutContext) => string; html?: never }\n | { html: (context: LayoutContext) => string; mjml?: never }\n\n/** Options of `defineLayout()`. */\nexport type LayoutOptions<L extends string = string> = {\n /** Texts of the layout, read with `t()`. */\n translations?: Translations<L>\n} & LayoutBody\n\n/** Layout created by `defineLayout()`. */\nexport interface MailLayout<L extends string = string> {\n readonly format: MailFormat\n /** Locales of the layout translations; empty without translations. */\n readonly locales: readonly string[]\n /**\n * Type-level only: the locales the layout can render.\n * @internal\n */\n readonly __locales?: (locale: L) => void\n}\n\ntype TemplateBody =\n | { mjml: TemplateSource; html?: never }\n | { html: TemplateSource; mjml?: never }\n\n/** Options of `defineTemplate()`. */\nexport type TemplateOptions<\n S extends z.ZodType | undefined = undefined,\n L extends string = string,\n> = {\n /** Name of the template, used in errors and logs. */\n name: string\n /**\n * Layout that receives the template source as `content`. It must have\n * translations in the template locales.\n */\n layout?: MailLayout<NoInfer<string extends L ? never : L>>\n /** Schema of the data. Without it, the template accepts no data. */\n schema?: S\n /**\n * Subject, by locale or the same for all. Rendered by Mustache without\n * HTML escaping: `'Welcome {{firstName}}'`.\n */\n subject: string | Localized<L>\n /** Texts of the template, read with `t()`. */\n translations?: Translations<L>\n /**\n * Text version, rendered by Mustache without HTML escaping. Defaults to a\n * conversion of the HTML version.\n */\n text?: TemplateSource\n} & TemplateBody\n\n/** Data accepted by a template, given its schema. */\nexport type DataOf<S> = S extends z.ZodType ? z.input<S> : undefined\n\n/** Template created by `defineTemplate()`. */\nexport interface MailTemplate<TData = undefined> {\n readonly name: string\n readonly format: MailFormat\n /** Locales of the template; empty for a template without translations. */\n readonly locales: readonly string[]\n /**\n * Type-level only: the data of the template.\n * @internal\n */\n readonly __data?: TData\n}\n\n/** Data accepted by a template. */\nexport type TemplateData<T> = T extends MailTemplate<infer D> ? D : never\n\n/** Definition of a layout, used by the renderer. */\nexport interface LayoutDefinition {\n readonly format: MailFormat\n readonly translations: Translations\n readonly render: (context: LayoutContext) => string\n}\n\n/** Definition of a template, used by the renderer. */\nexport interface TemplateDefinition {\n readonly name: string\n readonly format: MailFormat\n readonly locales: readonly string[]\n readonly source: TemplateSource\n readonly layout: LayoutDefinition | undefined\n readonly schema: z.ZodType | undefined\n readonly subject: string | Localized\n readonly translations: Translations\n readonly text: TemplateSource | undefined\n /** Compiled HTML by locale, before Mustache (see `render.ts`). */\n readonly cache: Map<string, Promise<string>>\n}\n\nconst layouts = new WeakMap<object, LayoutDefinition>()\nconst templates = new WeakMap<object, TemplateDefinition>()\n\n/** Definition of a template, or undefined for another value. */\nexport const definitionOf = (template: unknown) =>\n templates.get(template as object)\n\nconst LAYOUT_OPTIONS = new Set(['translations', 'mjml', 'html'])\nconst TEMPLATE_OPTIONS = new Set([\n 'name',\n 'layout',\n 'schema',\n 'subject',\n 'translations',\n 'text',\n 'mjml',\n 'html',\n])\n\nconst checkOptions = (\n prefix: string,\n options: unknown,\n allowed: ReadonlySet<string>\n): Record<string, unknown> => {\n if (\n typeof options !== 'object' ||\n options === null ||\n Array.isArray(options)\n ) {\n throw new TypeError(`${prefix}: expected an object.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.has(key)) {\n throw new TypeError(`${prefix}: unknown option \"${key}\".`)\n }\n }\n return options as Record<string, unknown>\n}\n\n/** Format of a source: exactly one of `mjml` and `html`. */\nconst formatOf = (\n prefix: string,\n options: Record<string, unknown>,\n isValid: (source: unknown) => boolean,\n expected: string\n): MailFormat => {\n const formats = (['mjml', 'html'] as const).filter(\n (format) => options[format] !== undefined\n )\n const [format] = formats\n if (format === undefined || formats.length > 1) {\n throw new TypeError(`${prefix}: set exactly one of mjml and html.`)\n }\n if (!isValid(options[format])) {\n throw new TypeError(`${prefix}: ${format} must be ${expected}.`)\n }\n return format\n}\n\nconst isSource = (source: unknown) =>\n typeof source === 'string' || typeof source === 'function'\n\n/**\n * Defines a layout: the structure shared by templates (head, header,\n * footer). The layout function receives the template source as `content`,\n * and its own translations through `t()`.\n *\n * ```ts\n * const baseLayout = defineLayout({\n * translations: { footer: { en: 'Sent by Acme', fr: 'Envoyé par Acme' } },\n * mjml: ({ t, content }) => `<mjml><mj-body>${content}…${t('footer')}…</mj-body></mjml>`,\n * })\n * ```\n */\nexport const defineLayout = <L extends string = string>(\n options: LayoutOptions<L>\n): MailLayout<L> => {\n const prefix = 'defineLayout()'\n const values = checkOptions(prefix, options, LAYOUT_OPTIONS)\n const format = formatOf(\n prefix,\n values,\n (source) => typeof source === 'function',\n 'a function that receives the template content'\n )\n const locales = collectLocales(\n prefix,\n translationEntries(prefix, values.translations)\n )\n\n const layout: MailLayout<L> = Object.freeze({\n format,\n locales: Object.freeze(locales),\n })\n layouts.set(layout, {\n format,\n translations: (values.translations ?? {}) as Translations,\n render: values[format] as LayoutDefinition['render'],\n })\n return layout\n}\n\n/**\n * Defines an email template. The source (`mjml` or `html`) is rendered in\n * three steps: `t()` inserts the translations, MJML compiles the result\n * (cached by locale), then Mustache inserts the data.\n *\n * ```ts\n * const welcome = defineTemplate({\n * name: 'welcome',\n * layout: baseLayout,\n * schema: z.object({ firstName: z.string(), url: z.url() }),\n * subject: { en: 'Welcome {{firstName}}', fr: 'Bienvenue {{firstName}}' },\n * translations: { cta: { en: 'Get started', fr: 'Commencer' } },\n * mjml: ({ t }) => `<mj-section>…<mj-button href=\"{{{ url }}}\">${t('cta')}</mj-button>…</mj-section>`,\n * })\n * ```\n */\nexport const defineTemplate = <\n S extends z.ZodType | undefined = undefined,\n L extends string = string,\n>(\n options: TemplateOptions<S, L>\n): MailTemplate<DataOf<S>> => {\n const name = (options as { name?: unknown } | null)?.name\n const prefix =\n typeof name === 'string' && name !== ''\n ? `defineTemplate(\"${name}\")`\n : 'defineTemplate()'\n const values = checkOptions(prefix, options, TEMPLATE_OPTIONS)\n if (typeof name !== 'string' || name === '') {\n throw new TypeError(`${prefix}: name must be a non-empty string.`)\n }\n\n const format = formatOf(prefix, values, isSource, 'a string or a function')\n\n const { layout, schema, subject, text } = values\n let layoutDefinition: LayoutDefinition | undefined\n if (layout !== undefined) {\n // WeakMap.get() returns undefined for any other value.\n layoutDefinition = layouts.get(layout as object)\n if (!layoutDefinition) {\n throw new TypeError(\n `${prefix}: layout must be created by defineLayout().`\n )\n }\n if (layoutDefinition.format !== format) {\n throw new TypeError(\n `${prefix}: the template is in ${format}, but its layout is in ${layoutDefinition.format}.`\n )\n }\n }\n if (\n schema !== undefined &&\n typeof (schema as { safeParseAsync?: unknown } | null)?.safeParseAsync !==\n 'function'\n ) {\n throw new TypeError(`${prefix}: schema must be a Zod schema.`)\n }\n if (typeof subject !== 'string' && typeof subject !== 'object') {\n throw new TypeError(\n `${prefix}: subject must be a string or an object of texts by locale.`\n )\n }\n if (text !== undefined && !isSource(text)) {\n throw new TypeError(`${prefix}: text must be a string or a function.`)\n }\n\n const own = collectLocales(prefix, [\n ...(typeof subject === 'object' ? [['subject', subject] as const] : []),\n ...translationEntries(prefix, values.translations),\n ])\n const layoutLocales = layout ? (layout as MailLayout).locales : []\n if (own.length > 0 && layoutLocales.length > 0) {\n const missing = own.filter((locale) => !layoutLocales.includes(locale))\n if (missing.length > 0) {\n throw new TypeError(\n `${prefix}: its layout has no translations in ${missing.join(', ')}.`\n )\n }\n }\n const locales = own.length > 0 ? own : [...layoutLocales]\n\n const template: MailTemplate<DataOf<S>> = Object.freeze({\n name,\n format,\n locales: Object.freeze(locales),\n })\n templates.set(template, {\n name,\n format,\n locales,\n source: values[format] as TemplateSource,\n layout: layoutDefinition,\n schema: schema as z.ZodType | undefined,\n subject: subject as string | Localized,\n translations: (values.translations ?? {}) as Translations,\n text: text as TemplateSource | undefined,\n cache: new Map(),\n })\n return template\n}\n","import { MailerError, MailSendError } from './errors.js'\nimport {\n checkKeys,\n parseAddress,\n parseEnvelope,\n parseRecipients,\n toSubject,\n type Envelope,\n type EnvelopeOptions,\n type MailAddress,\n type MailMessage,\n type MailRecipients,\n} from './message.js'\nimport {\n renderMail,\n toText,\n type DataOption,\n type RenderedMail,\n type RenderMailOptions,\n} from './render.js'\nimport type { MailTemplate } from './template.js'\n\n/** Result of a provider. */\nexport interface ProviderResult {\n /** Identifier of the message given by the provider, if any. */\n messageId?: string | undefined\n}\n\n/**\n * Sends messages: SMTP server, email API, test outbox… Throw a\n * `MailSendError` on failure, or a `MailMessageError` for a message the\n * provider cannot send; other errors are wrapped in a `MailSendError`.\n */\nexport interface MailProvider {\n /** Name used in errors and logs, like `smtp`. */\n readonly name: string\n send(message: MailMessage): Promise<ProviderResult | void>\n}\n\n/**\n * Logger of the mailer. A Dotpress or Pino logger fits; `mailerPlugin` uses\n * the logger of the request.\n */\nexport interface MailerLogger {\n debug?(data: object, message: string): void\n info(data: object, message: string): void\n warn(data: object, message: string): void\n error(data: object, message: string): void\n}\n\n/** Context of a mailer: the logger of a request, for instance. */\nexport interface MailerContext {\n logger?: MailerLogger | undefined\n}\n\n/** Options of `createMailer()`. */\nexport interface MailerOptions {\n provider: MailProvider\n /** Default sender: `'Acme <noreply@acme.com>'`. */\n from: MailAddress\n /** Default reply-to addresses. */\n replyTo?: MailRecipients | undefined\n /** Locale used when the requested one is not available. */\n defaultLocale?: string | undefined\n /** Logs each sent message (template, provider, message ID). */\n logger?: MailerLogger | undefined\n}\n\n/** Options of `mailer.send()`. */\nexport type SendMailOptions<TData> = EnvelopeOptions & {\n /** Overrides the subject of the template. */\n subject?: string | undefined\n /** Locale to render, like `fr-CA` (see `renderMail()`). */\n locale?: string | undefined\n} & DataOption<TData>\n\n/** Options of `mailer.sendMessage()`: a message rendered elsewhere. */\nexport interface MessageOptions extends EnvelopeOptions {\n subject: string\n /** HTML version. At least one of `html` and `text` is required. */\n html?: string | undefined\n /** Text version. Converted from the HTML version if missing. */\n text?: string | undefined\n}\n\n/** Result of a sent message. */\nexport interface SendResult {\n /** Identifier of the message given by the provider, if any. */\n readonly messageId: string | undefined\n}\n\nexport interface Mailer {\n readonly provider: MailProvider\n /** Renders a template, then sends it. */\n send<TData>(\n template: MailTemplate<TData>,\n options: SendMailOptions<NoInfer<TData>>\n ): Promise<SendResult>\n /**\n * Renders a template without sending it. The default locale is the one\n * of the mailer.\n */\n render<TData>(\n template: MailTemplate<TData>,\n options: RenderMailOptions<NoInfer<TData>>\n ): Promise<RenderedMail>\n /** Sends a message rendered elsewhere. */\n sendMessage(options: MessageOptions): Promise<SendResult>\n /** The same mailer, with another logger (`ctx.mailer` is one). */\n withContext(context?: MailerContext): Mailer\n}\n\nconst MAILER_OPTIONS = new Set([\n 'provider',\n 'from',\n 'replyTo',\n 'defaultLocale',\n 'logger',\n])\nconst ENVELOPE_OPTIONS = [\n 'to',\n 'cc',\n 'bcc',\n 'replyTo',\n 'from',\n 'attachments',\n 'headers',\n 'tags',\n 'metadata',\n 'subject',\n]\nconst SEND_OPTIONS = new Set([...ENVELOPE_OPTIONS, 'locale', 'data'])\nconst MESSAGE_OPTIONS = new Set([...ENVELOPE_OPTIONS, 'html', 'text'])\n\nconst checkLogger = (label: string, logger: unknown) => {\n for (const level of ['info', 'warn', 'error'] as const) {\n if (\n typeof (logger as Record<string, unknown> | null)?.[level] !== 'function'\n ) {\n throw new TypeError(\n `${label}: logger must have info(), warn() and error() methods.`\n )\n }\n }\n}\n\n/** Freezes a message and its lists. */\nconst freeze = (message: MailMessage): MailMessage => {\n for (const value of Object.values(message)) {\n if (typeof value === 'object' && value !== null) Object.freeze(value)\n }\n return Object.freeze(message)\n}\n\n/**\n * Creates a mailer: renders templates and sends messages through a\n * provider.\n *\n * ```ts\n * const mailer = createMailer({\n * provider: createSmtpProvider({ host: 'smtp.example.com' }),\n * from: 'Acme <noreply@acme.com>',\n * defaultLocale: 'en',\n * })\n * await mailer.send(welcome, { to: user.email, locale: user.locale, data })\n * ```\n */\nexport const createMailer = (options: MailerOptions): Mailer => {\n const values = checkKeys('createMailer()', options, MAILER_OPTIONS)\n const { provider, defaultLocale, logger } = values as Partial<MailerOptions>\n if (\n typeof provider?.name !== 'string' ||\n provider.name === '' ||\n typeof provider.send !== 'function'\n ) {\n throw new TypeError(\n 'createMailer(): provider must have a name and a send() method.'\n )\n }\n if (\n defaultLocale !== undefined &&\n (typeof defaultLocale !== 'string' || defaultLocale === '')\n ) {\n throw new TypeError(\n 'createMailer(): defaultLocale must be a non-empty string.'\n )\n }\n if (logger !== undefined) checkLogger('createMailer()', logger)\n if (values.from === undefined) {\n throw new TypeError('createMailer(): from is required.')\n }\n\n const defaults = {\n from: parseAddress(values.from, 'from'),\n replyTo: parseRecipients(values.replyTo, 'replyTo'),\n }\n\n const create = (logger: MailerLogger | undefined): Mailer => {\n const deliver = async (\n envelope: Envelope,\n content: Pick<\n MailMessage,\n 'subject' | 'html' | 'text' | 'template' | 'locale'\n >\n ): Promise<SendResult> => {\n const message = freeze({ ...envelope, ...content })\n let result: ProviderResult | void\n try {\n result = await provider.send(message)\n } catch (error) {\n // Send errors, and limits of the provider (MailMessageError).\n if (error instanceof MailerError) throw error\n throw new MailSendError(\n provider.name,\n error instanceof Error ? error.message : String(error),\n { cause: error }\n )\n }\n\n const messageId =\n typeof result?.messageId === 'string' ? result.messageId : undefined\n logger?.info(\n {\n provider: provider.name,\n template: message.template,\n messageId,\n recipients:\n message.to.length + message.cc.length + message.bcc.length,\n },\n 'Mail sent'\n )\n return { messageId }\n }\n\n const render: Mailer['render'] = (template, renderOptions) =>\n renderMail(template, {\n defaultLocale,\n ...renderOptions,\n } as never)\n\n const mailer: Mailer = {\n provider,\n\n render,\n\n send: async (template, sendOptions) => {\n const { subject, locale, data, ...envelopeOptions } = checkKeys(\n 'mailer.send()',\n sendOptions,\n SEND_OPTIONS\n ) as unknown as SendMailOptions<unknown>\n if (subject !== undefined && typeof subject !== 'string') {\n throw new TypeError('mailer.send(): subject must be a string.')\n }\n // Checked before rendering: an invalid address fails fast.\n const envelope = parseEnvelope(envelopeOptions, defaults)\n const mail = await render(template, { locale, data } as never)\n return deliver(envelope, {\n subject: subject === undefined ? mail.subject : toSubject(subject),\n html: mail.html,\n text: mail.text,\n template: mail.template,\n locale: mail.locale,\n })\n },\n\n sendMessage: async (messageOptions) => {\n const { subject, html, text, ...envelopeOptions } = checkKeys(\n 'mailer.sendMessage()',\n messageOptions,\n MESSAGE_OPTIONS\n ) as unknown as MessageOptions\n if (typeof subject !== 'string') {\n throw new TypeError('mailer.sendMessage(): subject must be a string.')\n }\n for (const [key, value] of [\n ['html', html],\n ['text', text],\n ] as const) {\n if (value !== undefined && typeof value !== 'string') {\n throw new TypeError(\n `mailer.sendMessage(): ${key} must be a string.`\n )\n }\n }\n if (html === undefined && text === undefined) {\n throw new TypeError('mailer.sendMessage(): set html, text, or both.')\n }\n const envelope = parseEnvelope(envelopeOptions, defaults)\n return deliver(envelope, {\n subject: toSubject(subject),\n html,\n text: text ?? (await toText(html as string)),\n template: undefined,\n locale: undefined,\n })\n },\n\n withContext: (context: MailerContext = {}) => {\n if (typeof context !== 'object' || context === null) {\n throw new TypeError('withContext(): expected an object.')\n }\n for (const key of Object.keys(context)) {\n if (key !== 'logger') {\n throw new TypeError(`withContext(): unknown option \"${key}\".`)\n }\n }\n if (context.logger === undefined) return create(logger)\n checkLogger('withContext()', context.logger)\n return create(context.logger)\n },\n }\n return Object.freeze(mailer)\n }\n\n return create(logger)\n}\n","import type { MailProvider } from '../mailer.js'\nimport { formatAddress, type Address } from '../message.js'\n\n/** Options of `createConsoleProvider()`. */\nexport interface ConsoleProviderOptions {\n /**\n * Also prints the text version. Off by default: messages often contain\n * secrets (password reset links, login codes).\n */\n body?: boolean | undefined\n /** Output function. Defaults to `console.log`. */\n write?: ((output: string) => void) | undefined\n}\n\nconst list = (addresses: readonly Address[]) =>\n addresses.map(formatAddress).join(', ')\n\n/**\n * Creates a provider that prints messages instead of sending them, for\n * development. Never use it as a fallback in production: messages would\n * be lost.\n */\nexport const createConsoleProvider = (\n options: ConsoleProviderOptions = {}\n): MailProvider => {\n const { body = false, write = (output) => console.log(output) } = options\n\n return {\n name: 'console',\n send: async (message) => {\n const lines = [\n `[mail] ${message.template ?? 'message'}${message.locale ? ` (${message.locale})` : ''}`,\n `From: ${formatAddress(message.from)}`,\n `To: ${list(message.to)}`,\n ]\n for (const [label, addresses] of [\n ['Cc', message.cc],\n ['Bcc', message.bcc],\n ['Reply-To', message.replyTo],\n ] as const) {\n if (addresses.length > 0) lines.push(`${label}: ${list(addresses)}`)\n }\n lines.push(`Subject: ${message.subject}`)\n if (message.attachments.length > 0) {\n lines.push(\n `Attachments: ${message.attachments\n .map(({ filename }) => filename)\n .join(', ')}`\n )\n }\n if (message.tags.length > 0) {\n lines.push(`Tags: ${message.tags.join(', ')}`)\n }\n const metadata = Object.entries(message.metadata)\n if (metadata.length > 0) {\n lines.push(\n `Metadata: ${metadata.map(([key, value]) => `${key}=${value}`).join(', ')}`\n )\n }\n if (body) lines.push('', message.text)\n write(lines.join('\\n'))\n },\n }\n}\n","import type { MailProvider } from '../mailer.js'\nimport type { MailMessage } from '../message.js'\n\n/** Provider that keeps messages in memory, for tests. */\nexport interface MemoryProvider extends MailProvider {\n /** Sent messages, oldest first. */\n readonly sent: readonly MailMessage[]\n /** Last sent message. */\n last(): MailMessage | undefined\n /** Forgets the sent messages. */\n clear(): void\n}\n\n/**\n * Creates a provider that keeps messages in memory instead of sending\n * them, for tests.\n *\n * ```ts\n * const outbox = createMemoryProvider()\n * const mailer = createMailer({ provider: outbox, from: 'noreply@example.com' })\n * // …\n * expect(outbox.last()?.subject).toBe('Welcome Ada')\n * ```\n */\nexport const createMemoryProvider = (): MemoryProvider => {\n const sent: MailMessage[] = []\n let count = 0\n\n return {\n name: 'memory',\n sent,\n send: async (message) => {\n sent.push(message)\n count += 1\n return { messageId: `memory-${count}` }\n },\n last: () => sent.at(-1),\n clear: () => {\n sent.length = 0\n },\n }\n}\n"],"mappings":"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","names":["format","logger"]}
|
|
@@ -26,7 +26,24 @@ interface Address {
|
|
|
26
26
|
type MailAddress = string | Address;
|
|
27
27
|
/** One or more email addresses. */
|
|
28
28
|
type MailRecipients = MailAddress | readonly MailAddress[];
|
|
29
|
-
/** A file
|
|
29
|
+
/** A file to attach to a message, given to `send()` and `sendMessage()`. */
|
|
30
|
+
interface MailAttachmentInput {
|
|
31
|
+
/** File name shown by email clients. */
|
|
32
|
+
readonly filename: string;
|
|
33
|
+
/** Binary content, or text content (UTF-8). */
|
|
34
|
+
readonly content: Uint8Array | string;
|
|
35
|
+
/** MIME type, like `application/pdf`. Guessed by the provider if missing. */
|
|
36
|
+
readonly contentType?: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Makes the attachment inline: the HTML version references it with
|
|
39
|
+
* `cid:` (`<img src="cid:logo">`).
|
|
40
|
+
*/
|
|
41
|
+
readonly contentId?: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A file attached to a message, as given to providers: validated. Its
|
|
45
|
+
* input, `MailAttachmentInput`, may accept more forms later.
|
|
46
|
+
*/
|
|
30
47
|
interface MailAttachment {
|
|
31
48
|
/** File name shown by email clients. */
|
|
32
49
|
readonly filename: string;
|
|
@@ -74,7 +91,7 @@ interface EnvelopeOptions {
|
|
|
74
91
|
replyTo?: MailRecipients | undefined;
|
|
75
92
|
/** Defaults to the `from` option of the mailer. */
|
|
76
93
|
from?: MailAddress | undefined;
|
|
77
|
-
attachments?: readonly
|
|
94
|
+
attachments?: readonly MailAttachmentInput[] | undefined;
|
|
78
95
|
/** Additional headers, like `List-Unsubscribe`. */
|
|
79
96
|
headers?: Readonly<Record<string, string>> | undefined;
|
|
80
97
|
/**
|
|
@@ -110,8 +127,8 @@ interface LayoutContext extends TranslateContext {
|
|
|
110
127
|
readonly content: string;
|
|
111
128
|
}
|
|
112
129
|
/** A source, or a function that produces it in the rendered locale. */
|
|
113
|
-
type
|
|
114
|
-
type
|
|
130
|
+
type TemplateSource = string | ((context: TranslateContext) => string);
|
|
131
|
+
type LayoutBody = {
|
|
115
132
|
mjml: (context: LayoutContext) => string;
|
|
116
133
|
html?: never;
|
|
117
134
|
} | {
|
|
@@ -122,7 +139,7 @@ type LayoutSource = {
|
|
|
122
139
|
type LayoutOptions<L extends string = string> = {
|
|
123
140
|
/** Texts of the layout, read with `t()`. */
|
|
124
141
|
translations?: Translations<L>;
|
|
125
|
-
} &
|
|
142
|
+
} & LayoutBody;
|
|
126
143
|
/** Layout created by `defineLayout()`. */
|
|
127
144
|
interface MailLayout<L extends string = string> {
|
|
128
145
|
readonly format: MailFormat;
|
|
@@ -134,11 +151,11 @@ interface MailLayout<L extends string = string> {
|
|
|
134
151
|
*/
|
|
135
152
|
readonly __locales?: (locale: L) => void;
|
|
136
153
|
}
|
|
137
|
-
type
|
|
138
|
-
mjml:
|
|
154
|
+
type TemplateBody = {
|
|
155
|
+
mjml: TemplateSource;
|
|
139
156
|
html?: never;
|
|
140
157
|
} | {
|
|
141
|
-
html:
|
|
158
|
+
html: TemplateSource;
|
|
142
159
|
mjml?: never;
|
|
143
160
|
};
|
|
144
161
|
/** Options of `defineTemplate()`. */
|
|
@@ -163,8 +180,8 @@ type TemplateOptions<S extends z.ZodType | undefined = undefined, L extends stri
|
|
|
163
180
|
* Text version, rendered by Mustache without HTML escaping. Defaults to a
|
|
164
181
|
* conversion of the HTML version.
|
|
165
182
|
*/
|
|
166
|
-
text?:
|
|
167
|
-
} &
|
|
183
|
+
text?: TemplateSource;
|
|
184
|
+
} & TemplateBody;
|
|
168
185
|
/** Data accepted by a template, given its schema. */
|
|
169
186
|
type DataOf<S> = S extends z.ZodType ? z.input<S> : undefined;
|
|
170
187
|
/** Template created by `defineTemplate()`. */
|
|
@@ -272,7 +289,14 @@ interface MailProvider {
|
|
|
272
289
|
* the logger of the request.
|
|
273
290
|
*/
|
|
274
291
|
interface MailerLogger {
|
|
292
|
+
debug?(data: object, message: string): void;
|
|
275
293
|
info(data: object, message: string): void;
|
|
294
|
+
warn(data: object, message: string): void;
|
|
295
|
+
error(data: object, message: string): void;
|
|
296
|
+
}
|
|
297
|
+
/** Context of a mailer: the logger of a request, for instance. */
|
|
298
|
+
interface MailerContext {
|
|
299
|
+
logger?: MailerLogger | undefined;
|
|
276
300
|
}
|
|
277
301
|
/** Options of `createMailer()`. */
|
|
278
302
|
interface MailerOptions {
|
|
@@ -317,8 +341,8 @@ interface Mailer {
|
|
|
317
341
|
render<TData>(template: MailTemplate<TData>, options: RenderMailOptions<NoInfer<TData>>): Promise<RenderedMail>;
|
|
318
342
|
/** Sends a message rendered elsewhere. */
|
|
319
343
|
sendMessage(options: MessageOptions): Promise<SendResult>;
|
|
320
|
-
/** The same mailer, with another logger. */
|
|
321
|
-
|
|
344
|
+
/** The same mailer, with another logger (`ctx.mailer` is one). */
|
|
345
|
+
withContext(context?: MailerContext): Mailer;
|
|
322
346
|
}
|
|
323
347
|
/**
|
|
324
348
|
* Creates a mailer: renders templates and sends messages through a
|
|
@@ -335,5 +359,5 @@ interface Mailer {
|
|
|
335
359
|
*/
|
|
336
360
|
declare const createMailer: (options: MailerOptions) => Mailer;
|
|
337
361
|
|
|
338
|
-
export { createMailer as
|
|
339
|
-
export type { Address as A,
|
|
362
|
+
export { createMailer as v, defineLayout as w, defineTemplate as x, formatAddress as y, renderMail as z };
|
|
363
|
+
export type { Address as A, EnvelopeOptions as E, LayoutContext as L, MailProvider as M, ProviderResult as P, RenderMailOptions as R, SendMailOptions as S, TemplateData as T, MailMessage as a, LayoutOptions as b, Localized as c, MailAddress as d, MailAttachment as e, MailAttachmentInput as f, MailFormat as g, MailLayout as h, MailRecipients as i, MailTemplate as j, Mailer as k, MailerContext as l, MailerLogger as m, MailerOptions as n, MessageOptions as o, RenderedMail as p, SendResult as q, TemplateOptions as r, TemplateSource as s, TranslateContext as t, Translations as u };
|
package/dist/mailgun.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
readJson,
|
|
4
4
|
responseError,
|
|
5
5
|
stringOf
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7JTF345R.js";
|
|
7
7
|
import {
|
|
8
8
|
base64,
|
|
9
9
|
utf8
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
MailMessageError,
|
|
13
13
|
formatAddress
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/mailgun.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.mailgun.net";
|
package/dist/mailjet.js
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
readJson,
|
|
6
6
|
responseError,
|
|
7
7
|
stringOf
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7JTF345R.js";
|
|
9
9
|
import {
|
|
10
10
|
base64
|
|
11
11
|
} from "./chunk-4FKEUJSR.js";
|
|
12
12
|
import {
|
|
13
13
|
MailSendError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/mailjet.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.mailjet.com";
|
package/dist/plugin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { declareContextKey, definePlugin } from "@dotpress/core";
|
|
3
3
|
declareContextKey("mailer", "mailerPlugin");
|
|
4
4
|
var mailerPlugin = (mailer) => {
|
|
5
|
-
if (typeof mailer?.
|
|
5
|
+
if (typeof mailer?.withContext !== "function") {
|
|
6
6
|
throw new TypeError(
|
|
7
7
|
"mailerPlugin(): expected a mailer created by createMailer()."
|
|
8
8
|
);
|
|
@@ -11,7 +11,10 @@ var mailerPlugin = (mailer) => {
|
|
|
11
11
|
name: "mailer",
|
|
12
12
|
mailer,
|
|
13
13
|
setup(api) {
|
|
14
|
-
api.decorateContext(
|
|
14
|
+
api.decorateContext(
|
|
15
|
+
"mailer",
|
|
16
|
+
(ctx) => mailer.withContext({ logger: ctx.logger })
|
|
17
|
+
);
|
|
15
18
|
}
|
|
16
19
|
});
|
|
17
20
|
};
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { declareContextKey, definePlugin } from '@dotpress/core'\nimport type { Mailer } from './mailer.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Mailer (`mailerPlugin`), logging with the request logger. */\n mailer: Mailer\n }\n}\n\n// Accessing ctx.mailer without the plugin throws an explicit error.\ndeclareContextKey('mailer', 'mailerPlugin')\n\n/**\n * Provides `ctx.mailer` to routes and middlewares. Sent messages are\n * logged with the request logger (request ID).\n *\n * ```ts\n * await createApp({ adapter, plugins: [mailerPlugin(mailer)] })\n *\n * defineRoute({\n * method: 'post',\n * path: '/invitations',\n * handler: async ({ mailer, body }) => {\n * await mailer.send(invitation, { to: body.email, data: { … } })\n * },\n * })\n * ```\n */\nexport const mailerPlugin = (mailer: Mailer) => {\n if (typeof mailer?.
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import { declareContextKey, definePlugin } from '@dotpress/core'\nimport type { Mailer } from './mailer.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Mailer (`mailerPlugin`), logging with the request logger. */\n mailer: Mailer\n }\n}\n\n// Accessing ctx.mailer without the plugin throws an explicit error.\ndeclareContextKey('mailer', 'mailerPlugin')\n\n/**\n * Provides `ctx.mailer` to routes and middlewares. Sent messages are\n * logged with the request logger (request ID).\n *\n * ```ts\n * await createApp({ adapter, plugins: [mailerPlugin(mailer)] })\n *\n * defineRoute({\n * method: 'post',\n * path: '/invitations',\n * handler: async ({ mailer, body }) => {\n * await mailer.send(invitation, { to: body.email, data: { … } })\n * },\n * })\n * ```\n */\nexport const mailerPlugin = (mailer: Mailer) => {\n if (typeof mailer?.withContext !== 'function') {\n throw new TypeError(\n 'mailerPlugin(): expected a mailer created by createMailer().'\n )\n }\n return definePlugin({\n name: 'mailer',\n mailer,\n setup(api) {\n api.decorateContext('mailer', (ctx) =>\n mailer.withContext({ logger: ctx.logger })\n )\n },\n })\n}\n"],"mappings":";AAAA,SAAS,mBAAmB,oBAAoB;AAWhD,kBAAkB,UAAU,cAAc;AAkBnC,IAAM,eAAe,CAAC,WAAmB;AAC9C,MAAI,OAAO,QAAQ,gBAAgB,YAAY;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN;AAAA,IACA,MAAM,KAAK;AACT,UAAI;AAAA,QAAgB;AAAA,QAAU,CAAC,QAC7B,OAAO,YAAY,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/postmark.js
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
readJson,
|
|
5
5
|
responseError,
|
|
6
6
|
stringOf
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7JTF345R.js";
|
|
8
8
|
import {
|
|
9
9
|
base64
|
|
10
10
|
} from "./chunk-4FKEUJSR.js";
|
|
11
11
|
import {
|
|
12
12
|
MailMessageError,
|
|
13
13
|
formatAddress
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/postmark.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.postmarkapp.com";
|
package/dist/resend.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
keyValueTags
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-R2VGIBJO.js";
|
|
4
4
|
import {
|
|
5
5
|
base64
|
|
6
6
|
} from "./chunk-4FKEUJSR.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
MailSendError,
|
|
9
9
|
checkKeys,
|
|
10
10
|
formatAddress
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-T5AWVUNP.js";
|
|
12
12
|
|
|
13
13
|
// src/providers/resend.ts
|
|
14
14
|
import {
|
package/dist/scaleway.js
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
readJson,
|
|
5
5
|
responseError,
|
|
6
6
|
stringOf
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7JTF345R.js";
|
|
8
8
|
import {
|
|
9
9
|
base64
|
|
10
10
|
} from "./chunk-4FKEUJSR.js";
|
|
11
11
|
import {
|
|
12
12
|
MailMessageError,
|
|
13
13
|
formatAddress
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/scaleway.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.scaleway.com";
|
package/dist/sendgrid.js
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
readJson,
|
|
5
5
|
responseError,
|
|
6
6
|
stringOf
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-7JTF345R.js";
|
|
8
8
|
import {
|
|
9
9
|
base64,
|
|
10
10
|
utf8
|
|
11
11
|
} from "./chunk-4FKEUJSR.js";
|
|
12
12
|
import {
|
|
13
13
|
MailMessageError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-T5AWVUNP.js";
|
|
15
15
|
|
|
16
16
|
// src/providers/sendgrid.ts
|
|
17
17
|
var DEFAULT_BASE_URL = "https://api.sendgrid.com";
|
package/dist/smtp.d.ts
CHANGED
|
@@ -20,11 +20,16 @@ interface NodemailerProvider extends MailProvider {
|
|
|
20
20
|
* `smtps://user:password@smtp.example.com`.
|
|
21
21
|
*/
|
|
22
22
|
type SmtpProviderOptions = SMTPTransportOptions | SMTPPoolOptions | string;
|
|
23
|
+
/** Options of `createNodemailerProvider()`. */
|
|
24
|
+
interface NodemailerProviderOptions {
|
|
25
|
+
/** Name of the provider, in the logs. Defaults to `nodemailer`. */
|
|
26
|
+
name?: string | undefined;
|
|
27
|
+
}
|
|
23
28
|
/**
|
|
24
29
|
* Creates a provider from a nodemailer transporter: SMTP, sendmail,
|
|
25
30
|
* Amazon SES through nodemailer, or a stream transport for tests.
|
|
26
31
|
*/
|
|
27
|
-
declare const createNodemailerProvider: (transporter: NodemailerTransporter,
|
|
32
|
+
declare const createNodemailerProvider: (transporter: NodemailerTransporter, options?: NodemailerProviderOptions) => NodemailerProvider;
|
|
28
33
|
/**
|
|
29
34
|
* Creates a provider that sends messages to an SMTP server, through
|
|
30
35
|
* nodemailer (peer dependency). Also works with the SMTP interface of
|
|
@@ -42,4 +47,4 @@ declare const createNodemailerProvider: (transporter: NodemailerTransporter, nam
|
|
|
42
47
|
declare const createSmtpProvider: (options: SmtpProviderOptions) => NodemailerProvider;
|
|
43
48
|
|
|
44
49
|
export { createNodemailerProvider, createSmtpProvider };
|
|
45
|
-
export type { NodemailerProvider, NodemailerTransporter, SmtpProviderOptions };
|
|
50
|
+
export type { NodemailerProvider, NodemailerProviderOptions, NodemailerTransporter, SmtpProviderOptions };
|
package/dist/smtp.js
CHANGED
|
@@ -26,11 +26,22 @@ var toNodemailerMessage = (message) => ({
|
|
|
26
26
|
attachments: message.attachments.map(toAttachment),
|
|
27
27
|
headers: { ...message.headers }
|
|
28
28
|
});
|
|
29
|
-
var createNodemailerProvider = (transporter,
|
|
29
|
+
var createNodemailerProvider = (transporter, options = {}) => {
|
|
30
|
+
const label = "createNodemailerProvider()";
|
|
30
31
|
if (typeof transporter?.sendMail !== "function") {
|
|
31
|
-
throw new TypeError(
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
throw new TypeError(`${label}: expected a nodemailer transporter.`);
|
|
33
|
+
}
|
|
34
|
+
if (typeof options !== "object" || options === null) {
|
|
35
|
+
throw new TypeError(`${label}: options must be an object.`);
|
|
36
|
+
}
|
|
37
|
+
for (const key of Object.keys(options)) {
|
|
38
|
+
if (key !== "name") {
|
|
39
|
+
throw new TypeError(`${label}: unknown option "${key}".`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const { name = "nodemailer" } = options;
|
|
43
|
+
if (typeof name !== "string" || name === "") {
|
|
44
|
+
throw new TypeError(`${label}: name must be a non-empty string.`);
|
|
34
45
|
}
|
|
35
46
|
return {
|
|
36
47
|
name,
|
|
@@ -52,7 +63,7 @@ var createSmtpProvider = (options) => {
|
|
|
52
63
|
const transporter = nodemailer.createTransport(
|
|
53
64
|
options
|
|
54
65
|
);
|
|
55
|
-
return createNodemailerProvider(transporter, "smtp");
|
|
66
|
+
return createNodemailerProvider(transporter, { name: "smtp" });
|
|
56
67
|
};
|
|
57
68
|
export {
|
|
58
69
|
createNodemailerProvider,
|
package/dist/smtp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers/smtp.ts"],"sourcesContent":["// nodemailer runs on Node.js only: Buffer is available.\n/// <reference types=\"node\" />\nimport nodemailer, {\n type SendMailOptions,\n type SMTPPoolOptions,\n type SMTPTransportOptions,\n type Transporter,\n} from 'nodemailer'\nimport type { MailProvider } from '../mailer.js'\nimport type { Address, MailAttachment, MailMessage } from '../message.js'\n\n/** A nodemailer transporter, or any object with the same methods. */\nexport interface NodemailerTransporter {\n sendMail(options: SendMailOptions): Promise<{ messageId?: unknown }>\n close?(): void\n}\n\n/** Provider based on nodemailer: SMTP, or another nodemailer transport. */\nexport interface NodemailerProvider extends MailProvider {\n /** Closes the connections of the transporter (`pool: true`). */\n close(): void\n}\n\n/**\n * Options of `createSmtpProvider()`: the SMTP options of nodemailer\n * (`host`, `port`, `secure`, `auth`, `pool`…), or a connection URL like\n * `smtps://user:password@smtp.example.com`.\n */\nexport type SmtpProviderOptions =\n SMTPTransportOptions | SMTPPoolOptions | string\n\nconst toAddress = ({ name, address }: Address) =>\n name ? { name, address } : address\n\nconst toAttachment = ({\n filename,\n content,\n contentType,\n contentId,\n}: MailAttachment) => ({\n filename,\n // nodemailer takes Buffers, not plain Uint8Arrays (no copy).\n content:\n typeof content === 'string'\n ? content\n : Buffer.from(content.buffer, content.byteOffset, content.byteLength),\n ...(contentType === undefined ? {} : { contentType }),\n // nodemailer defaults to `attachment`.\n ...(contentId === undefined\n ? {}\n : { cid: contentId, contentDisposition: 'inline' as const }),\n})\n\n/** nodemailer options of a message. */\nconst toNodemailerMessage = (message: MailMessage): SendMailOptions => ({\n from: toAddress(message.from),\n to: message.to.map(toAddress),\n cc: message.cc.map(toAddress),\n bcc: message.bcc.map(toAddress),\n replyTo: message.replyTo.map(toAddress),\n subject: message.subject,\n text: message.text,\n html: message.html,\n attachments: message.attachments.map(toAttachment),\n headers: { ...message.headers },\n})\n\n/**\n * Creates a provider from a nodemailer transporter: SMTP, sendmail,\n * Amazon SES through nodemailer, or a stream transport for tests.\n */\nexport const createNodemailerProvider = (\n transporter: NodemailerTransporter,\n
|
|
1
|
+
{"version":3,"sources":["../src/providers/smtp.ts"],"sourcesContent":["// nodemailer runs on Node.js only: Buffer is available.\n/// <reference types=\"node\" />\nimport nodemailer, {\n type SendMailOptions,\n type SMTPPoolOptions,\n type SMTPTransportOptions,\n type Transporter,\n} from 'nodemailer'\nimport type { MailProvider } from '../mailer.js'\nimport type { Address, MailAttachment, MailMessage } from '../message.js'\n\n/** A nodemailer transporter, or any object with the same methods. */\nexport interface NodemailerTransporter {\n sendMail(options: SendMailOptions): Promise<{ messageId?: unknown }>\n close?(): void\n}\n\n/** Provider based on nodemailer: SMTP, or another nodemailer transport. */\nexport interface NodemailerProvider extends MailProvider {\n /** Closes the connections of the transporter (`pool: true`). */\n close(): void\n}\n\n/**\n * Options of `createSmtpProvider()`: the SMTP options of nodemailer\n * (`host`, `port`, `secure`, `auth`, `pool`…), or a connection URL like\n * `smtps://user:password@smtp.example.com`.\n */\nexport type SmtpProviderOptions =\n SMTPTransportOptions | SMTPPoolOptions | string\n\nconst toAddress = ({ name, address }: Address) =>\n name ? { name, address } : address\n\nconst toAttachment = ({\n filename,\n content,\n contentType,\n contentId,\n}: MailAttachment) => ({\n filename,\n // nodemailer takes Buffers, not plain Uint8Arrays (no copy).\n content:\n typeof content === 'string'\n ? content\n : Buffer.from(content.buffer, content.byteOffset, content.byteLength),\n ...(contentType === undefined ? {} : { contentType }),\n // nodemailer defaults to `attachment`.\n ...(contentId === undefined\n ? {}\n : { cid: contentId, contentDisposition: 'inline' as const }),\n})\n\n/** nodemailer options of a message. */\nconst toNodemailerMessage = (message: MailMessage): SendMailOptions => ({\n from: toAddress(message.from),\n to: message.to.map(toAddress),\n cc: message.cc.map(toAddress),\n bcc: message.bcc.map(toAddress),\n replyTo: message.replyTo.map(toAddress),\n subject: message.subject,\n text: message.text,\n html: message.html,\n attachments: message.attachments.map(toAttachment),\n headers: { ...message.headers },\n})\n\n/** Options of `createNodemailerProvider()`. */\nexport interface NodemailerProviderOptions {\n /** Name of the provider, in the logs. Defaults to `nodemailer`. */\n name?: string | undefined\n}\n\n/**\n * Creates a provider from a nodemailer transporter: SMTP, sendmail,\n * Amazon SES through nodemailer, or a stream transport for tests.\n */\nexport const createNodemailerProvider = (\n transporter: NodemailerTransporter,\n options: NodemailerProviderOptions = {}\n): NodemailerProvider => {\n const label = 'createNodemailerProvider()'\n if (typeof transporter?.sendMail !== 'function') {\n throw new TypeError(`${label}: expected a nodemailer transporter.`)\n }\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(`${label}: options must be an object.`)\n }\n for (const key of Object.keys(options)) {\n if (key !== 'name') {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n const { name = 'nodemailer' } = options\n if (typeof name !== 'string' || name === '') {\n throw new TypeError(`${label}: name must be a non-empty string.`)\n }\n return {\n name,\n send: async (message) => {\n const { messageId } = await transporter.sendMail(\n toNodemailerMessage(message)\n )\n // Checked by the mailer.\n return { messageId: messageId as string | undefined }\n },\n close: () => transporter.close?.(),\n }\n}\n\n/**\n * Creates a provider that sends messages to an SMTP server, through\n * nodemailer (peer dependency). Also works with the SMTP interface of\n * Amazon SES, Postmark, Mailgun, Brevo, Mailjet…\n *\n * ```ts\n * const provider = createSmtpProvider({\n * host: 'smtp.example.com',\n * port: 465,\n * secure: true,\n * auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD },\n * })\n * ```\n */\nexport const createSmtpProvider = (\n options: SmtpProviderOptions\n): NodemailerProvider => {\n if (\n typeof options !== 'string' &&\n (typeof options !== 'object' || options === null)\n ) {\n throw new TypeError(\n 'createSmtpProvider(): expected SMTP options or a connection URL.'\n )\n }\n const transporter: Transporter = nodemailer.createTransport(\n options as SMTPTransportOptions\n )\n return createNodemailerProvider(transporter, { name: 'smtp' })\n}\n"],"mappings":";AAEA,OAAO,gBAKA;AAwBP,IAAM,YAAY,CAAC,EAAE,MAAM,QAAQ,MACjC,OAAO,EAAE,MAAM,QAAQ,IAAI;AAE7B,IAAM,eAAe,CAAC;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,OAAuB;AAAA,EACrB;AAAA;AAAA,EAEA,SACE,OAAO,YAAY,WACf,UACA,OAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;AAAA,EACxE,GAAI,gBAAgB,SAAY,CAAC,IAAI,EAAE,YAAY;AAAA;AAAA,EAEnD,GAAI,cAAc,SACd,CAAC,IACD,EAAE,KAAK,WAAW,oBAAoB,SAAkB;AAC9D;AAGA,IAAM,sBAAsB,CAAC,aAA2C;AAAA,EACtE,MAAM,UAAU,QAAQ,IAAI;AAAA,EAC5B,IAAI,QAAQ,GAAG,IAAI,SAAS;AAAA,EAC5B,IAAI,QAAQ,GAAG,IAAI,SAAS;AAAA,EAC5B,KAAK,QAAQ,IAAI,IAAI,SAAS;AAAA,EAC9B,SAAS,QAAQ,QAAQ,IAAI,SAAS;AAAA,EACtC,SAAS,QAAQ;AAAA,EACjB,MAAM,QAAQ;AAAA,EACd,MAAM,QAAQ;AAAA,EACd,aAAa,QAAQ,YAAY,IAAI,YAAY;AAAA,EACjD,SAAS,EAAE,GAAG,QAAQ,QAAQ;AAChC;AAYO,IAAM,2BAA2B,CACtC,aACA,UAAqC,CAAC,MACf;AACvB,QAAM,QAAQ;AACd,MAAI,OAAO,aAAa,aAAa,YAAY;AAC/C,UAAM,IAAI,UAAU,GAAG,KAAK,sCAAsC;AAAA,EACpE;AACA,MAAI,OAAO,YAAY,YAAY,YAAY,MAAM;AACnD,UAAM,IAAI,UAAU,GAAG,KAAK,8BAA8B;AAAA,EAC5D;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,QAAQ,QAAQ;AAClB,YAAM,IAAI,UAAU,GAAG,KAAK,qBAAqB,GAAG,IAAI;AAAA,IAC1D;AAAA,EACF;AACA,QAAM,EAAE,OAAO,aAAa,IAAI;AAChC,MAAI,OAAO,SAAS,YAAY,SAAS,IAAI;AAC3C,UAAM,IAAI,UAAU,GAAG,KAAK,oCAAoC;AAAA,EAClE;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,OAAO,YAAY;AACvB,YAAM,EAAE,UAAU,IAAI,MAAM,YAAY;AAAA,QACtC,oBAAoB,OAAO;AAAA,MAC7B;AAEA,aAAO,EAAE,UAA2C;AAAA,IACtD;AAAA,IACA,OAAO,MAAM,YAAY,QAAQ;AAAA,EACnC;AACF;AAgBO,IAAM,qBAAqB,CAChC,YACuB;AACvB,MACE,OAAO,YAAY,aAClB,OAAO,YAAY,YAAY,YAAY,OAC5C;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,cAA2B,WAAW;AAAA,IAC1C;AAAA,EACF;AACA,SAAO,yBAAyB,aAAa,EAAE,MAAM,OAAO,CAAC;AAC/D;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotpress/mailer",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.5",
|
|
4
4
|
"description": "Typed, multilingual email templates (MJML or HTML) and interchangeable providers",
|
|
5
5
|
"author": "Renaud Guyon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"@aws-sdk/client-sesv2": "^3.782.0",
|
|
74
|
-
"@dotpress/core": "^2.0.0-alpha.
|
|
74
|
+
"@dotpress/core": "^2.0.0-alpha.5",
|
|
75
75
|
"mjml": "^5.0.0",
|
|
76
76
|
"nodemailer": "^10.0.0",
|
|
77
77
|
"resend": "^6.0.0",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
102
|
"@aws-sdk/client-sesv2": "^3.1164.0",
|
|
103
|
-
"@dotpress/core": "2.0.0-alpha.
|
|
104
|
-
"@dotpress/testing": "2.0.0-alpha.
|
|
103
|
+
"@dotpress/core": "2.0.0-alpha.5",
|
|
104
|
+
"@dotpress/testing": "2.0.0-alpha.5",
|
|
105
105
|
"@types/html-to-text": "^9.0.4",
|
|
106
106
|
"@types/mjml": "^5.4.2",
|
|
107
107
|
"@types/mustache": "^4.2.6",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/message.ts"],"sourcesContent":["/** Base class of the errors thrown by `@dotpress/mailer`. */\nexport class MailerError extends Error {\n override name = 'MailerError'\n}\n\n/** Problem found in the data passed to a template. */\nexport interface MailDataIssue {\n /** Path of the invalid value, empty for the data itself. */\n readonly path: readonly (string | number)[]\n readonly message: string\n}\n\n/** The data passed to a template does not match its schema. */\nexport class MailDataError extends MailerError {\n override name = 'MailDataError'\n\n constructor(\n /** Name of the template. */\n readonly template: string,\n readonly issues: readonly MailDataIssue[]\n ) {\n const details = issues\n .map(({ path, message }) =>\n path.length > 0 ? `${path.join('.')}: ${message}` : message\n )\n .join('; ')\n super(`Invalid data for mail template \"${template}\": ${details}`)\n }\n}\n\n/** A template could not be rendered (MJML, translations, Mustache). */\nexport class MailRenderError extends MailerError {\n override name = 'MailRenderError'\n\n constructor(\n /** Name of the template. */\n readonly template: string,\n message: string,\n options?: ErrorOptions\n ) {\n super(`Cannot render mail template \"${template}\": ${message}`, options)\n }\n}\n\n/** Invalid message: address, attachment, header or option. */\nexport class MailMessageError extends MailerError {\n override name = 'MailMessageError'\n\n constructor(message: string) {\n super(`Invalid mail message: ${message}`)\n }\n}\n\n/** Options of `MailSendError`. */\nexport interface MailSendErrorOptions extends ErrorOptions {\n /** HTTP status of the provider API, if any. */\n status?: number | undefined\n}\n\n/** The provider failed to send a message. */\nexport class MailSendError extends MailerError {\n override name = 'MailSendError'\n /** HTTP status of the provider API, if any. */\n readonly status: number | undefined\n\n constructor(\n /** Name of the provider. */\n readonly provider: string,\n message: string,\n options: MailSendErrorOptions = {}\n ) {\n const { status, ...errorOptions } = options\n super(`Mail not sent by the ${provider} provider: ${message}`, errorOptions)\n this.status = status\n }\n}\n","import { MailMessageError } from './errors.js'\n\n/** An email address, normalized. */\nexport interface Address {\n /** Display name, like `Ada Lovelace`. */\n readonly name?: string\n /** Email address, like `ada@example.com`. */\n readonly address: string\n}\n\n/**\n * An email address: `'ada@example.com'`, `'Ada Lovelace <ada@example.com>'`\n * or `{ name: 'Ada Lovelace', address: 'ada@example.com' }`.\n */\nexport type MailAddress = string | Address\n\n/** One or more email addresses. */\nexport type MailRecipients = MailAddress | readonly MailAddress[]\n\n/** A file attached to a message. */\nexport interface MailAttachment {\n /** File name shown by email clients. */\n readonly filename: string\n /** Binary content, or text content (UTF-8). */\n readonly content: Uint8Array | string\n /** MIME type, like `application/pdf`. Guessed by the provider if missing. */\n readonly contentType?: string\n /**\n * Makes the attachment inline: the HTML version references it with\n * `cid:` (`<img src=\"cid:logo\">`).\n */\n readonly contentId?: string\n}\n\n/** Message given to providers. */\nexport interface MailMessage {\n readonly from: Address\n /** At least one address. */\n readonly to: readonly Address[]\n readonly cc: readonly Address[]\n readonly bcc: readonly Address[]\n readonly replyTo: readonly Address[]\n /** Single line. */\n readonly subject: string\n /** HTML version; undefined for a text-only message. */\n readonly html: string | undefined\n readonly text: string\n readonly attachments: readonly MailAttachment[]\n /** Additional headers, like `List-Unsubscribe`. */\n readonly headers: Readonly<Record<string, string>>\n /** Labels to group messages in the statistics of the provider. */\n readonly tags: readonly string[]\n /** Key/value pairs returned by the webhooks of the provider. */\n readonly metadata: Readonly<Record<string, string>>\n /** Name of the template, if the message was rendered from one. */\n readonly template: string | undefined\n /** Rendered locale, if any. */\n readonly locale: string | undefined\n}\n\n/** Recipients and options shared by every message. */\nexport interface EnvelopeOptions {\n to: MailRecipients\n cc?: MailRecipients | undefined\n bcc?: MailRecipients | undefined\n /** Defaults to the `replyTo` option of the mailer. */\n replyTo?: MailRecipients | undefined\n /** Defaults to the `from` option of the mailer. */\n from?: MailAddress | undefined\n attachments?: readonly MailAttachment[] | undefined\n /** Additional headers, like `List-Unsubscribe`. */\n headers?: Readonly<Record<string, string>> | undefined\n /**\n * Labels to group messages in the statistics of the provider: letters,\n * digits, `_` and `-`. Ignored by providers without tags.\n */\n tags?: readonly string[] | undefined\n /**\n * Key/value pairs returned by the webhooks of the provider, to link an\n * event to the app. Keys: letters, digits, `_` and `-`. Ignored by\n * providers without metadata.\n */\n metadata?: Readonly<Record<string, string>> | undefined\n}\n\n/** Envelope of a message, validated. */\nexport interface Envelope {\n readonly from: Address\n readonly to: readonly Address[]\n readonly cc: readonly Address[]\n readonly bcc: readonly Address[]\n readonly replyTo: readonly Address[]\n readonly attachments: readonly MailAttachment[]\n readonly headers: Readonly<Record<string, string>>\n readonly tags: readonly string[]\n readonly metadata: Readonly<Record<string, string>>\n}\n\nconst fail = (message: string): never => {\n throw new MailMessageError(message)\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n// Pragmatic check: one @, no spaces, nor characters that break headers.\nconst EMAIL = /^[^\\s@<>()[\\]\\\\,;:\"]+@[^\\s@<>()[\\]\\\\,;:\"]+$/\nconst NAMED = /^([\\s\\S]*?)\\s*<([^<>]*)>$/\nconst LINE_BREAK = /[\\r\\n]/\nconst TOKEN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/\n// The strictest rule of the providers (SES, Resend): valid everywhere.\nconst TAG = /^[A-Za-z0-9_-]{1,256}$/\n\n// Set by the mailer or the provider.\nconst RESERVED_HEADERS = new Set([\n 'from',\n 'to',\n 'cc',\n 'bcc',\n 'reply-to',\n 'subject',\n 'date',\n 'message-id',\n 'mime-version',\n 'content-type',\n 'content-transfer-encoding',\n])\n\n/** Normalizes and checks an address. */\nexport const parseAddress = (value: unknown, label: string): Address => {\n let name: unknown\n let address: unknown\n if (typeof value === 'string') {\n const named = NAMED.exec(value.trim())\n if (named) {\n // Optional quotes around the name.\n name = (named[1] as string).replace(/^\"(.*)\"$/, '$1').trim()\n address = (named[2] as string).trim()\n } else {\n address = value.trim()\n }\n } else if (isRecord(value)) {\n ;({ name, address } = value)\n if (name !== undefined && typeof name !== 'string') {\n fail(`${label}.name must be a string.`)\n }\n } else {\n fail(`${label} must be an email address.`)\n }\n\n if (typeof address !== 'string' || !EMAIL.test(address)) {\n fail(`${label} is not an email address: ${JSON.stringify(address)}.`)\n }\n if (typeof name === 'string' && LINE_BREAK.test(name)) {\n fail(`${label} has a line break in its name.`)\n }\n return name\n ? { name: name as string, address: address as string }\n : { address: address as string }\n}\n\n/** Normalizes and checks a list of addresses. */\nexport const parseRecipients = (value: unknown, label: string): Address[] => {\n if (value === undefined) return []\n const list: unknown[] = Array.isArray(value) ? value : [value]\n return list.map((item, index) =>\n parseAddress(item, Array.isArray(value) ? `${label}[${index}]` : label)\n )\n}\n\nconst parseAttachments = (value: unknown): MailAttachment[] => {\n if (value === undefined) return []\n if (!Array.isArray(value)) fail('attachments must be a list.')\n\n return (value as unknown[]).map((attachment, index) => {\n const label = `attachments[${index}]`\n if (!isRecord(attachment)) fail(`${label} must be an object.`)\n const { filename, content, contentType, contentId } = attachment as Record<\n string,\n unknown\n >\n if (typeof filename !== 'string' || filename === '') {\n fail(`${label}.filename must be a non-empty string.`)\n }\n if (!(content instanceof Uint8Array) && typeof content !== 'string') {\n fail(`${label}.content must be a Uint8Array or a string.`)\n }\n for (const [key, field] of [\n ['contentType', contentType],\n ['contentId', contentId],\n ] as const) {\n if (\n field !== undefined &&\n (typeof field !== 'string' || field === '' || LINE_BREAK.test(field))\n ) {\n fail(`${label}.${key} must be a non-empty string on one line.`)\n }\n }\n return {\n filename: filename as string,\n content: content as Uint8Array | string,\n ...(contentType === undefined\n ? {}\n : { contentType: contentType as string }),\n ...(contentId === undefined ? {} : { contentId: contentId as string }),\n }\n })\n}\n\nconst parseHeaders = (value: unknown): Record<string, string> => {\n if (value === undefined) return {}\n if (!isRecord(value)) fail('headers must be an object.')\n\n const headers: Record<string, string> = {}\n for (const [name, header] of Object.entries(value as object)) {\n if (!TOKEN.test(name)) fail(`\"${name}\" is not a header name.`)\n if (RESERVED_HEADERS.has(name.toLowerCase())) {\n fail(`the ${name} header is set by the mailer.`)\n }\n if (typeof header !== 'string' || LINE_BREAK.test(header)) {\n fail(`the ${name} header must be a string on one line.`)\n }\n headers[name] = header as string\n }\n return headers\n}\n\nconst parseTags = (value: unknown): string[] => {\n if (value === undefined) return []\n if (!Array.isArray(value)) fail('tags must be a list.')\n const tags = new Set<string>()\n for (const [index, tag] of (value as unknown[]).entries()) {\n if (typeof tag !== 'string' || !TAG.test(tag)) {\n fail(\n `tags[${index}] must have 1 to 256 letters, digits, _ or -: ${JSON.stringify(tag)}.`\n )\n }\n tags.add(tag as string)\n }\n return [...tags]\n}\n\nconst parseMetadata = (value: unknown): Record<string, string> => {\n if (value === undefined) return {}\n if (!isRecord(value)) fail('metadata must be an object.')\n\n const metadata: Record<string, string> = {}\n for (const [key, item] of Object.entries(value as object)) {\n if (!TAG.test(key)) {\n fail(\n `metadata keys must have 1 to 256 letters, digits, _ or -: ${JSON.stringify(key)}.`\n )\n }\n if (typeof item !== 'string' || LINE_BREAK.test(item)) {\n fail(`metadata.${key} must be a string on one line.`)\n }\n metadata[key] = item as string\n }\n return metadata\n}\n\n/** Single-line subject. */\nexport const toSubject = (subject: string) =>\n subject.replace(/\\s*[\\r\\n]+\\s*/g, ' ').trim()\n\n/**\n * Validates the envelope options of a message. `defaults` provides the\n * sender and the reply-to addresses of the mailer.\n */\nexport const parseEnvelope = (\n options: EnvelopeOptions,\n defaults: { from: Address; replyTo: readonly Address[] }\n): Envelope => {\n const to = parseRecipients(options.to, 'to')\n if (to.length === 0) fail('to needs at least one address.')\n\n return {\n from:\n options.from === undefined\n ? defaults.from\n : parseAddress(options.from, 'from'),\n to,\n cc: parseRecipients(options.cc, 'cc'),\n bcc: parseRecipients(options.bcc, 'bcc'),\n replyTo:\n options.replyTo === undefined\n ? defaults.replyTo\n : parseRecipients(options.replyTo, 'replyTo'),\n attachments: parseAttachments(options.attachments),\n headers: parseHeaders(options.headers),\n tags: parseTags(options.tags),\n metadata: parseMetadata(options.metadata),\n }\n}\n\n/** Rejects unknown options. */\nexport const checkKeys = (\n label: string,\n options: unknown,\n allowed: ReadonlySet<string>\n): Record<string, unknown> => {\n if (!isRecord(options)) {\n throw new TypeError(`${label}: expected an object of options.`)\n }\n for (const key of Object.keys(options)) {\n if (!allowed.has(key)) {\n throw new TypeError(`${label}: unknown option \"${key}\".`)\n }\n }\n return options\n}\n\n/**\n * Address as a header value: `\"Ada Lovelace\" <ada@example.com>`, or the\n * address alone. For providers that take strings.\n */\nexport const formatAddress = ({ name, address }: Address) =>\n name ? `\"${name.replace(/([\"\\\\])/g, '\\\\$1')}\" <${address}>` : address\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|