@dotpress/mailer 0.0.0-stage → 2.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +491 -2
  3. package/dist/aws-ses.d.ts +30 -0
  4. package/dist/aws-ses.js +108 -0
  5. package/dist/aws-ses.js.map +1 -0
  6. package/dist/brevo.d.ts +23 -0
  7. package/dist/brevo.js +115 -0
  8. package/dist/brevo.js.map +1 -0
  9. package/dist/chunk-2ZABFOZQ.js +15 -0
  10. package/dist/chunk-2ZABFOZQ.js.map +1 -0
  11. package/dist/chunk-FRMKUAF4.js +88 -0
  12. package/dist/chunk-FRMKUAF4.js.map +1 -0
  13. package/dist/chunk-GOAXQ2GE.js +28 -0
  14. package/dist/chunk-GOAXQ2GE.js.map +1 -0
  15. package/dist/chunk-QYVN5N3B.js +221 -0
  16. package/dist/chunk-QYVN5N3B.js.map +1 -0
  17. package/dist/http-B35l9W1d.d.ts +9 -0
  18. package/dist/index.d.ts +97 -0
  19. package/dist/index.js +649 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/mailer-CmQCJ0qc.d.ts +339 -0
  22. package/dist/mailgun.d.ts +31 -0
  23. package/dist/mailgun.js +100 -0
  24. package/dist/mailgun.js.map +1 -0
  25. package/dist/mailjet.d.ts +28 -0
  26. package/dist/mailjet.js +104 -0
  27. package/dist/mailjet.js.map +1 -0
  28. package/dist/plugin.d.ts +33 -0
  29. package/dist/plugin.js +21 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/postmark.d.ts +27 -0
  32. package/dist/postmark.js +149 -0
  33. package/dist/postmark.js.map +1 -0
  34. package/dist/resend.d.ts +31 -0
  35. package/dist/resend.js +92 -0
  36. package/dist/resend.js.map +1 -0
  37. package/dist/scaleway.d.ts +31 -0
  38. package/dist/scaleway.js +113 -0
  39. package/dist/scaleway.js.map +1 -0
  40. package/dist/sendgrid.d.ts +26 -0
  41. package/dist/sendgrid.js +121 -0
  42. package/dist/sendgrid.js.map +1 -0
  43. package/dist/smtp.d.ts +45 -0
  44. package/dist/smtp.js +61 -0
  45. package/dist/smtp.js.map +1 -0
  46. package/package.json +139 -3
@@ -0,0 +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 '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\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":";;;;;;;;;;;;;;;AAAA,OAAO,cAAc;;;ACerB,IAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAO9D,IAAM,iBAAiB,CAC5B,QACA,YACa;AACb,MAAI;AACJ,MAAI,YAAY;AAEhB,aAAW,CAAC,OAAO,KAAK,KAAK,SAAS;AACpC,QAAI,CAAC,SAAS,KAAK,GAAG;AACpB,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,KAAK,KAAK;AAAA,MACrB;AAAA,IACF;AACA,UAAM,OAAO,OAAO,KAAK,KAAK,EAAE,KAAK;AACrC,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI,UAAU,GAAG,MAAM,KAAK,KAAK,iBAAiB;AAAA,IAC1D;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,OAAO,MAAM,GAAG,MAAM,UAAU;AAClC,cAAM,IAAI,UAAU,GAAG,MAAM,KAAK,KAAK,IAAI,GAAG,oBAAoB;AAAA,MACpE;AAAA,IACF;AAEA,QAAI,YAAY,QAAW;AACzB,gBAAU;AACV,kBAAY;AAAA,IACd,WAAW,KAAK,KAAK,MAAM,QAAQ,KAAK,GAAG;AACzC,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,KAAK,KAAK,oBAAoB,KAAK,KAAK,IAAI,CAAC,SAAS,SAAS,QAAQ,QAAQ,KAAK,IAAI,CAAC;AAAA,MACpG;AAAA,IACF;AAAA,EACF;AACA,SAAO,WAAW,CAAC;AACrB;AAGO,IAAM,qBAAqB,CAChC,QACA,iBACwB;AACxB,MAAI,iBAAiB,OAAW,QAAO,CAAC;AACxC,MAAI,CAAC,SAAS,YAAY,GAAG;AAC3B,UAAM,IAAI,UAAU,GAAG,MAAM,mCAAmC;AAAA,EAClE;AACA,SAAO,OAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAAA,IACxD,gBAAgB,GAAG;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAGA,IAAM,SAAS,CAAC,WAAmB,OAAO,MAAM,MAAM,EAAE,CAAC,KAAK;AAOvD,IAAM,gBAAgB,CAC3B,WACA,WACA,kBACuB;AACvB,QAAM,aAAa,CAAC,WAAW,aAAa,EAAE;AAAA,IAAQ,CAAC,WACrD,SAAS,CAAC,QAAQ,OAAO,MAAM,CAAC,IAAI,CAAC;AAAA,EACvC;AACA,aAAW,aAAa,YAAY;AAClC,UAAM,QAAQ,UAAU,YAAY;AACpC,UAAM,QAAQ,UAAU,KAAK,CAAC,WAAW,OAAO,YAAY,MAAM,KAAK;AACvE,QAAI,UAAU,OAAW,QAAO;AAAA,EAClC;AACA,SAAO;AACT;;;AC2BA,IAAM,UAAU,oBAAI,QAAkC;AACtD,IAAM,YAAY,oBAAI,QAAoC;AAGnD,IAAM,eAAe,CAAC,aAC3B,UAAU,IAAI,QAAkB;AAElC,IAAM,iBAAiB,oBAAI,IAAI,CAAC,gBAAgB,QAAQ,MAAM,CAAC;AAC/D,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,eAAe,CACnB,QACA,SACA,YAC4B;AAC5B,MACE,OAAO,YAAY,YACnB,YAAY,QACZ,MAAM,QAAQ,OAAO,GACrB;AACA,UAAM,IAAI,UAAU,GAAG,MAAM,uBAAuB;AAAA,EACtD;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,YAAM,IAAI,UAAU,GAAG,MAAM,qBAAqB,GAAG,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,SAAO;AACT;AAGA,IAAM,WAAW,CACf,QACA,SACA,SACA,aACe;AACf,QAAM,UAAW,CAAC,QAAQ,MAAM,EAAY;AAAA,IAC1C,CAACA,YAAW,QAAQA,OAAM,MAAM;AAAA,EAClC;AACA,QAAM,CAAC,MAAM,IAAI;AACjB,MAAI,WAAW,UAAa,QAAQ,SAAS,GAAG;AAC9C,UAAM,IAAI,UAAU,GAAG,MAAM,qCAAqC;AAAA,EACpE;AACA,MAAI,CAAC,QAAQ,QAAQ,MAAM,CAAC,GAAG;AAC7B,UAAM,IAAI,UAAU,GAAG,MAAM,KAAK,MAAM,YAAY,QAAQ,GAAG;AAAA,EACjE;AACA,SAAO;AACT;AAEA,IAAM,WAAW,CAAC,WAChB,OAAO,WAAW,YAAY,OAAO,WAAW;AAc3C,IAAM,eAAe,CAC1B,YACkB;AAClB,QAAM,SAAS;AACf,QAAM,SAAS,aAAa,QAAQ,SAAS,cAAc;AAC3D,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA,CAAC,WAAW,OAAO,WAAW;AAAA,IAC9B;AAAA,EACF;AACA,QAAM,UAAU;AAAA,IACd;AAAA,IACA,mBAAmB,QAAQ,OAAO,YAAY;AAAA,EAChD;AAEA,QAAM,SAAwB,OAAO,OAAO;AAAA,IAC1C;AAAA,IACA,SAAS,OAAO,OAAO,OAAO;AAAA,EAChC,CAAC;AACD,UAAQ,IAAI,QAAQ;AAAA,IAClB;AAAA,IACA,cAAe,OAAO,gBAAgB,CAAC;AAAA,IACvC,QAAQ,OAAO,MAAM;AAAA,EACvB,CAAC;AACD,SAAO;AACT;AAkBO,IAAM,iBAAiB,CAI5B,YAC4B;AAC5B,QAAM,OAAQ,SAAuC;AACrD,QAAM,SACJ,OAAO,SAAS,YAAY,SAAS,KACjC,0BAA0B,IAAI,MAC9B;AACN,QAAM,SAAS,aAAa,QAAQ,SAAS,gBAAgB;AAC7D,MAAI,OAAO,SAAS,YAAY,SAAS,IAAI;AAC3C,UAAM,IAAI,UAAU,GAAG,MAAM,oCAAoC;AAAA,EACnE;AAEA,QAAM,SAAS,SAAS,QAAQ,QAAQ,UAAU,wBAAwB;AAE1E,QAAM,EAAE,QAAQ,QAAQ,SAAS,KAAK,IAAI;AAC1C,MAAI;AACJ,MAAI,WAAW,QAAW;AAExB,uBAAmB,QAAQ,IAAI,MAAgB;AAC/C,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI;AAAA,QACR,GAAG,MAAM;AAAA,MACX;AAAA,IACF;AACA,QAAI,iBAAiB,WAAW,QAAQ;AACtC,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,wBAAwB,MAAM,0BAA0B,iBAAiB,MAAM;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AACA,MACE,WAAW,UACX,OAAQ,QAAgD,mBACtD,YACF;AACA,UAAM,IAAI,UAAU,GAAG,MAAM,gCAAgC;AAAA,EAC/D;AACA,MAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU;AAC9D,UAAM,IAAI;AAAA,MACR,GAAG,MAAM;AAAA,IACX;AAAA,EACF;AACA,MAAI,SAAS,UAAa,CAAC,SAAS,IAAI,GAAG;AACzC,UAAM,IAAI,UAAU,GAAG,MAAM,wCAAwC;AAAA,EACvE;AAEA,QAAM,MAAM,eAAe,QAAQ;AAAA,IACjC,GAAI,OAAO,YAAY,WAAW,CAAC,CAAC,WAAW,OAAO,CAAU,IAAI,CAAC;AAAA,IACrE,GAAG,mBAAmB,QAAQ,OAAO,YAAY;AAAA,EACnD,CAAC;AACD,QAAM,gBAAgB,SAAU,OAAsB,UAAU,CAAC;AACjE,MAAI,IAAI,SAAS,KAAK,cAAc,SAAS,GAAG;AAC9C,UAAM,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,cAAc,SAAS,MAAM,CAAC;AACtE,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,uCAAuC,QAAQ,KAAK,IAAI,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AACA,QAAM,UAAU,IAAI,SAAS,IAAI,MAAM,CAAC,GAAG,aAAa;AAExD,QAAM,WAAoC,OAAO,OAAO;AAAA,IACtD;AAAA,IACA;AAAA,IACA,SAAS,OAAO,OAAO,OAAO;AAAA,EAChC,CAAC;AACD,YAAU,IAAI,UAAU;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,OAAO,MAAM;AAAA,IACrB,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,cAAe,OAAO,gBAAgB,CAAC;AAAA,IACvC;AAAA,IACA,OAAO,oBAAI,IAAI;AAAA,EACjB,CAAC;AACD,SAAO;AACT;;;AF7RA,IAAM,gBAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAOA,IAAM,aAAa,CAAC,UAClB,OAAO,KAAK,EAAE,QAAQ,YAAY,CAAC,SAAS,cAAc,IAAI,CAAW;AAE3E,IAAM,WAAW,CAAC,UAAmB,OAAO,KAAK;AAKjD,IAAI;AACJ,IAAI;AAGJ,IAAM,WAAW,MACd,eAAe,OAAO,MAAM,EAAE;AAAA,EAC7B,CAAC,WAAW,OAAO;AAAA,EACnB,CAAC,UAAmB;AAClB,iBAAa;AACb,UAAM;AAAA,EACR;AACF;AAGF,IAAM,iBAAiB,MACpB,qBAAqB,OAAO,cAAc,EAAE;AAAA,EAC3C,CAAC,WAAW;AAAA,EACZ,CAAC,UAAmB;AAClB,uBAAmB;AACnB,UAAM;AAAA,EACR;AACF;AAGF,IAAM,gBAAgB,CAAC,UACrB,iBAAiB,UACd,MAA6B,SAAS,0BACvC,MAAM,QAAQ,SAAS,QAAQ,KAC/B,cAAc,MAAM,KAAK;AAS7B,IAAM,oBAAoB,CAAC,UAAmB;AAC5C,QAAM,SAAU,OAAuC;AACvD,MAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS,GAAG;AAC9C,WAAQ,OACL;AAAA,MACC,CAAC,EAAE,MAAM,SAAS,QAAQ,MAAM,QAAQ,IAAI,KAAK,OAAO,MAAM,OAAO;AAAA,IACvE,EACC,KAAK,IAAI;AAAA,EACd;AACA,SAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAGA,IAAM,cAAc,OAAO,MAAc,WAAmB;AAC1D,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS;AAAA,EACxB,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,cAAc,KAAK,IACf,4EACA;AAAA,MACJ,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACA,MAAI;AACF,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,QAAQ;AAAA,MAClC,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,IAClB,CAAC;AACD,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iBAAiB,kBAAkB,KAAK,CAAC;AAAA,MACzC,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAGO,IAAM,SAAS,OAAO,SAAiB;AAC5C,QAAM,EAAE,QAAQ,IAAI,MAAM,eAAe;AACzC,SAAO,QAAQ,MAAM;AAAA,IACnB,UAAU;AAAA,IACV,WAAW;AAAA,MACT,EAAE,UAAU,OAAO,QAAQ,OAAO;AAAA;AAAA,MAElC,EAAE,UAAU,2BAA2B,QAAQ,OAAO;AAAA,MACtD,EAAE,UAAU,4BAA4B,QAAQ,OAAO;AAAA,MACvD,EAAE,UAAU,KAAK,SAAS,EAAE,0BAA0B,KAAK,EAAE;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAGA,IAAM,aACJ,CACE,MACA,OACA,cACA,WAEF,CAAC,QAAwB;AACvB,QAAM,QAAQ,OAAO,OAAO,cAAc,GAAG,IACzC,aAAa,GAAG,IAChB;AAEJ,QAAM,OAAO,QAAQ,MAAgB;AACrC,MAAI,SAAS,QAAW;AACtB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,4BAA4B,GAAG,YAAY,KAAK;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAGF,IAAM,UAAU,CACd,MACA,OACA,QACA,YACW;AACX,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI;AACJ,MAAI;AACF,aAAS,OAAO,OAAO;AAAA,EACzB,SAAS,OAAO;AACd,QAAI,iBAAiB,gBAAiB,OAAM;AAC5C,UAAM,IAAI,gBAAgB,MAAM,OAAO,KAAK,4BAA4B;AAAA,MACtE,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,MACA,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,YAAY,CAChB,YACA,YACsB;AAAA,EACtB;AAAA,EACA,GAAG,WAAW,WAAW,MAAM,YAAY,WAAW,cAAc,MAAM;AAC5E;AAGA,IAAM,UAAU,OACd,YACA,WACG;AACH,QAAM,EAAE,MAAM,OAAO,IAAI;AACzB,MAAI,SAAS;AAAA,IACX;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,UAAU,YAAY,MAAM;AAAA,EAC9B;AACA,MAAI,QAAQ;AACV,aAAS,QAAQ,MAAM,UAAU,OAAO,QAAQ;AAAA,MAC9C;AAAA,MACA,GAAG,WAAW,MAAM,UAAU,OAAO,cAAc,MAAM;AAAA,MACzD,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,SAAO,WAAW,WAAW,SAAS,YAAY,MAAM,MAAM,IAAI;AACpE;AAGA,IAAM,WAAW,CACf,YACA,WACG;AACH,QAAM,MAAM,UAAU;AACtB,MAAI,OAAO,WAAW,MAAM,IAAI,GAAG;AACnC,MAAI,CAAC,MAAM;AACT,WAAO,QAAQ,YAAY,MAAM;AACjC,eAAW,MAAM,IAAI,KAAK,IAAI;AAE9B,SAAK,MAAM,MAAM,WAAW,MAAM,OAAO,GAAG,CAAC;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,IAAM,iBAAiB,CACrB,MACA,MACA,QACA,MACA,WACG;AACH,MAAI;AACF,WAAO,SAAS,OAAO,QAAQ,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC;AAAA,EACrD,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA,2BAA2B,IAAI,KAAM,MAAgB,OAAO;AAAA,MAC5D,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAEA,IAAM,YAAY,OAChB,YACA,SACoB;AACpB,QAAM,EAAE,MAAM,OAAO,IAAI;AACzB,MAAI,CAAC,QAAQ;AACX,QAAI,SAAS,QAAW;AACtB,YAAM,IAAI,cAAc,MAAM;AAAA,QAC5B,EAAE,MAAM,CAAC,GAAG,SAAS,iDAAiD;AAAA,MACxE,CAAC;AAAA,IACH;AACA,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,SAAS,MAAM,OAAO,eAAe,IAAI;AAC/C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,OAAO,MAAM,OAAO,IAAI,CAAC,EAAE,MAAM,QAAQ,OAAO;AAAA,QAC9C,MAAM,KAAK,IAAI,CAAC,QAAS,OAAO,QAAQ,WAAW,OAAO,GAAG,IAAI,GAAI;AAAA,QACrE;AAAA,MACF,EAAE;AAAA,IACJ;AAAA,EACF;AACA,QAAM,SAAkB,OAAO;AAC/B,MAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG;AAC1E,UAAM,IAAI,cAAc,MAAM;AAAA,MAC5B,EAAE,MAAM,CAAC,GAAG,SAAS,oCAAoC;AAAA,IAC3D,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,IAAM,gBAAgB,CACpB,YACA,WACG;AACH,QAAM,EAAE,QAAQ,IAAI;AACpB,MAAI,OAAO,YAAY,SAAU,QAAO;AAExC,SAAO,QAAQ,MAAgB;AACjC;AAcO,IAAM,aAAa,OACxB,UACA,YAC0B;AAC1B,QAAM,aAAa,aAAa,QAAQ;AACxC,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,QAAM,EAAE,QAAQ,WAAW,cAAc,IAAI,WAAW,CAAC;AAEzD,QAAM,OAAO,MAAM,UAAU,YAAY,SAAS,IAAI;AAEtD,MAAI;AACJ,MAAI,QAAQ,SAAS,GAAG;AACtB,aAAS,cAAc,SAAS,WAAW,aAAa;AACxD,QAAI,WAAW,QAAW;AACxB,YAAM,QAAQ,CAAC,WAAW,aAAa,EAAE,OAAO,OAAO;AACvD,YAAM,IAAI;AAAA,QACR;AAAA,QACA,MAAM,SAAS,IACX,uBAAuB,MAAM,KAAK,MAAM,CAAC,gBAAgB,QAAQ,KAAK,IAAI,CAAC,OAC3E,yDAAyD,QAAQ,KAAK,IAAI,CAAC;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,MAAM,SAAS,YAAY,MAAM;AAAA,IACjC;AAAA,IACA;AAAA,EACF;AAEA,QAAM,UAAU;AAAA,IACd;AAAA,MACE;AAAA,MACA;AAAA,MACA,cAAc,YAAY,MAAM;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,WAAW,OACpB;AAAA,IACE;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,QAAQ,WAAW,MAAM,UAAU,YAAY,MAAM,CAAC;AAAA,IACpE;AAAA,IACA;AAAA,EACF,IACA,MAAM,OAAO,IAAI;AAErB,SAAO,EAAE,UAAU,MAAM,QAAQ,SAAS,MAAM,KAAK;AACvD;;;AGlRA,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,eAAe,oBAAI,IAAI,CAAC,GAAG,kBAAkB,UAAU,MAAM,CAAC;AACpE,IAAM,kBAAkB,oBAAI,IAAI,CAAC,GAAG,kBAAkB,QAAQ,MAAM,CAAC;AAErE,IAAM,cAAc,CAAC,WAAoB;AACvC,MAAI,OAAQ,QAAgC,SAAS,YAAY;AAC/D,UAAM,IAAI,UAAU,mDAAmD;AAAA,EACzE;AACF;AAGA,IAAM,SAAS,CAAC,YAAsC;AACpD,aAAW,SAAS,OAAO,OAAO,OAAO,GAAG;AAC1C,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO,OAAO,KAAK;AAAA,EACtE;AACA,SAAO,OAAO,OAAO,OAAO;AAC9B;AAeO,IAAM,eAAe,CAAC,YAAmC;AAC9D,QAAM,SAAS,UAAU,0BAA0B,SAAS,cAAc;AAC1E,QAAM,EAAE,UAAU,eAAe,OAAO,IAAI;AAC5C,MACE,OAAO,UAAU,SAAS,YAC1B,SAAS,SAAS,MAClB,OAAO,SAAS,SAAS,YACzB;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MACE,kBAAkB,WACjB,OAAO,kBAAkB,YAAY,kBAAkB,KACxD;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,WAAW,OAAW,aAAY,MAAM;AAC5C,MAAI,OAAO,SAAS,QAAW;AAC7B,UAAM,IAAI,UAAU,2CAA2C;AAAA,EACjE;AAEA,QAAM,WAAW;AAAA,IACf,MAAM,aAAa,OAAO,MAAM,MAAM;AAAA,IACtC,SAAS,gBAAgB,OAAO,SAAS,SAAS;AAAA,EACpD;AAEA,QAAM,SAAS,CAACC,YAA6C;AAC3D,UAAM,UAAU,OACd,UACA,YAIwB;AACxB,YAAM,UAAU,OAAO,EAAE,GAAG,UAAU,GAAG,QAAQ,CAAC;AAClD,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,SAAS,KAAK,OAAO;AAAA,MACtC,SAAS,OAAO;AAEd,YAAI,iBAAiB,YAAa,OAAM;AACxC,cAAM,IAAI;AAAA,UACR,SAAS;AAAA,UACT,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UACrD,EAAE,OAAO,MAAM;AAAA,QACjB;AAAA,MACF;AAEA,YAAM,YACJ,OAAO,QAAQ,cAAc,WAAW,OAAO,YAAY;AAC7D,MAAAA,SAAQ;AAAA,QACN;AAAA,UACE,UAAU,SAAS;AAAA,UACnB,UAAU,QAAQ;AAAA,UAClB;AAAA,UACA,YACE,QAAQ,GAAG,SAAS,QAAQ,GAAG,SAAS,QAAQ,IAAI;AAAA,QACxD;AAAA,QACA;AAAA,MACF;AACA,aAAO,EAAE,UAAU;AAAA,IACrB;AAEA,UAAM,SAA2B,CAAC,UAAU,kBAC1C,WAAW,UAAU;AAAA,MACnB;AAAA,MACA,GAAG;AAAA,IACL,CAAU;AAEZ,UAAM,SAAiB;AAAA,MACrB;AAAA,MAEA;AAAA,MAEA,MAAM,OAAO,UAAU,gBAAgB;AACrC,cAAM,EAAE,SAAS,QAAQ,MAAM,GAAG,gBAAgB,IAAI;AAAA,UACpD;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,YAAY,UAAa,OAAO,YAAY,UAAU;AACxD,gBAAM,IAAI,UAAU,0CAA0C;AAAA,QAChE;AAEA,cAAM,WAAW,cAAc,iBAAiB,QAAQ;AACxD,cAAM,OAAO,MAAM,OAAO,UAAU,EAAE,QAAQ,KAAK,CAAU;AAC7D,eAAO,QAAQ,UAAU;AAAA,UACvB,SAAS,YAAY,SAAY,KAAK,UAAU,UAAU,OAAO;AAAA,UACjE,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,UACX,UAAU,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,QACf,CAAC;AAAA,MACH;AAAA,MAEA,aAAa,OAAO,mBAAmB;AACrC,cAAM,EAAE,SAAS,MAAM,MAAM,GAAG,gBAAgB,IAAI;AAAA,UAClD;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,YAAI,OAAO,YAAY,UAAU;AAC/B,gBAAM,IAAI,UAAU,iDAAiD;AAAA,QACvE;AACA,mBAAW,CAAC,KAAK,KAAK,KAAK;AAAA,UACzB,CAAC,QAAQ,IAAI;AAAA,UACb,CAAC,QAAQ,IAAI;AAAA,QACf,GAAY;AACV,cAAI,UAAU,UAAa,OAAO,UAAU,UAAU;AACpD,kBAAM,IAAI;AAAA,cACR,yBAAyB,GAAG;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,UAAa,SAAS,QAAW;AAC5C,gBAAM,IAAI,UAAU,gDAAgD;AAAA,QACtE;AACA,cAAM,WAAW,cAAc,iBAAiB,QAAQ;AACxD,eAAO,QAAQ,UAAU;AAAA,UACvB,SAAS,UAAU,OAAO;AAAA,UAC1B;AAAA,UACA,MAAM,QAAS,MAAM,OAAO,IAAc;AAAA,UAC1C,UAAU;AAAA,UACV,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,MAEA,YAAY,CAAC,UAAU;AACrB,oBAAY,KAAK;AACjB,eAAO,OAAO,KAAK;AAAA,MACrB;AAAA,IACF;AACA,WAAO,OAAO,OAAO,MAAM;AAAA,EAC7B;AAEA,SAAO,OAAO,MAAM;AACtB;;;ACvRA,IAAM,OAAO,CAAC,cACZ,UAAU,IAAI,aAAa,EAAE,KAAK,IAAI;AAOjC,IAAM,wBAAwB,CACnC,UAAkC,CAAC,MAClB;AACjB,QAAM,EAAE,OAAO,OAAO,QAAQ,CAAC,WAAW,QAAQ,IAAI,MAAM,EAAE,IAAI;AAElE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,OAAO,YAAY;AACvB,YAAM,QAAQ;AAAA,QACZ,UAAU,QAAQ,YAAY,SAAS,GAAG,QAAQ,SAAS,KAAK,QAAQ,MAAM,MAAM,EAAE;AAAA,QACtF,SAAS,cAAc,QAAQ,IAAI,CAAC;AAAA,QACpC,OAAO,KAAK,QAAQ,EAAE,CAAC;AAAA,MACzB;AACA,iBAAW,CAAC,OAAO,SAAS,KAAK;AAAA,QAC/B,CAAC,MAAM,QAAQ,EAAE;AAAA,QACjB,CAAC,OAAO,QAAQ,GAAG;AAAA,QACnB,CAAC,YAAY,QAAQ,OAAO;AAAA,MAC9B,GAAY;AACV,YAAI,UAAU,SAAS,EAAG,OAAM,KAAK,GAAG,KAAK,KAAK,KAAK,SAAS,CAAC,EAAE;AAAA,MACrE;AACA,YAAM,KAAK,YAAY,QAAQ,OAAO,EAAE;AACxC,UAAI,QAAQ,YAAY,SAAS,GAAG;AAClC,cAAM;AAAA,UACJ,gBAAgB,QAAQ,YACrB,IAAI,CAAC,EAAE,SAAS,MAAM,QAAQ,EAC9B,KAAK,IAAI,CAAC;AAAA,QACf;AAAA,MACF;AACA,UAAI,QAAQ,KAAK,SAAS,GAAG;AAC3B,cAAM,KAAK,SAAS,QAAQ,KAAK,KAAK,IAAI,CAAC,EAAE;AAAA,MAC/C;AACA,YAAM,WAAW,OAAO,QAAQ,QAAQ,QAAQ;AAChD,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM;AAAA,UACJ,aAAa,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,QAC3E;AAAA,MACF;AACA,UAAI,KAAM,OAAM,KAAK,IAAI,QAAQ,IAAI;AACrC,YAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IACxB;AAAA,EACF;AACF;;;ACvCO,IAAM,uBAAuB,MAAsB;AACxD,QAAM,OAAsB,CAAC;AAC7B,MAAI,QAAQ;AAEZ,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,MAAM,OAAO,YAAY;AACvB,WAAK,KAAK,OAAO;AACjB,eAAS;AACT,aAAO,EAAE,WAAW,UAAU,KAAK,GAAG;AAAA,IACxC;AAAA,IACA,MAAM,MAAM,KAAK,GAAG,EAAE;AAAA,IACtB,OAAO,MAAM;AACX,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AACF;","names":["format","logger"]}
@@ -0,0 +1,339 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Translations of a template or layout: one entry per key, with a text for
5
+ * each locale. Every entry must have the same locales.
6
+ *
7
+ * ```ts
8
+ * { greeting: { en: 'Hello', fr: 'Bonjour' } }
9
+ * ```
10
+ */
11
+ type Translations<L extends string = string> = Readonly<Record<string, Readonly<Record<L, string>>>>;
12
+ /** A text for each locale, like `{ en: 'Welcome', fr: 'Bienvenue' }`. */
13
+ type Localized<L extends string = string> = Readonly<Record<L, string>>;
14
+
15
+ /** An email address, normalized. */
16
+ interface Address {
17
+ /** Display name, like `Ada Lovelace`. */
18
+ readonly name?: string;
19
+ /** Email address, like `ada@example.com`. */
20
+ readonly address: string;
21
+ }
22
+ /**
23
+ * An email address: `'ada@example.com'`, `'Ada Lovelace <ada@example.com>'`
24
+ * or `{ name: 'Ada Lovelace', address: 'ada@example.com' }`.
25
+ */
26
+ type MailAddress = string | Address;
27
+ /** One or more email addresses. */
28
+ type MailRecipients = MailAddress | readonly MailAddress[];
29
+ /** A file attached to a message. */
30
+ interface MailAttachment {
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;
37
+ /**
38
+ * Makes the attachment inline: the HTML version references it with
39
+ * `cid:` (`<img src="cid:logo">`).
40
+ */
41
+ readonly contentId?: string;
42
+ }
43
+ /** Message given to providers. */
44
+ interface MailMessage {
45
+ readonly from: Address;
46
+ /** At least one address. */
47
+ readonly to: readonly Address[];
48
+ readonly cc: readonly Address[];
49
+ readonly bcc: readonly Address[];
50
+ readonly replyTo: readonly Address[];
51
+ /** Single line. */
52
+ readonly subject: string;
53
+ /** HTML version; undefined for a text-only message. */
54
+ readonly html: string | undefined;
55
+ readonly text: string;
56
+ readonly attachments: readonly MailAttachment[];
57
+ /** Additional headers, like `List-Unsubscribe`. */
58
+ readonly headers: Readonly<Record<string, string>>;
59
+ /** Labels to group messages in the statistics of the provider. */
60
+ readonly tags: readonly string[];
61
+ /** Key/value pairs returned by the webhooks of the provider. */
62
+ readonly metadata: Readonly<Record<string, string>>;
63
+ /** Name of the template, if the message was rendered from one. */
64
+ readonly template: string | undefined;
65
+ /** Rendered locale, if any. */
66
+ readonly locale: string | undefined;
67
+ }
68
+ /** Recipients and options shared by every message. */
69
+ interface EnvelopeOptions {
70
+ to: MailRecipients;
71
+ cc?: MailRecipients | undefined;
72
+ bcc?: MailRecipients | undefined;
73
+ /** Defaults to the `replyTo` option of the mailer. */
74
+ replyTo?: MailRecipients | undefined;
75
+ /** Defaults to the `from` option of the mailer. */
76
+ from?: MailAddress | undefined;
77
+ attachments?: readonly MailAttachment[] | undefined;
78
+ /** Additional headers, like `List-Unsubscribe`. */
79
+ headers?: Readonly<Record<string, string>> | undefined;
80
+ /**
81
+ * Labels to group messages in the statistics of the provider: letters,
82
+ * digits, `_` and `-`. Ignored by providers without tags.
83
+ */
84
+ tags?: readonly string[] | undefined;
85
+ /**
86
+ * Key/value pairs returned by the webhooks of the provider, to link an
87
+ * event to the app. Keys: letters, digits, `_` and `-`. Ignored by
88
+ * providers without metadata.
89
+ */
90
+ metadata?: Readonly<Record<string, string>> | undefined;
91
+ }
92
+ /**
93
+ * Address as a header value: `"Ada Lovelace" <ada@example.com>`, or the
94
+ * address alone. For providers that take strings.
95
+ */
96
+ declare const formatAddress: ({ name, address }: Address) => string;
97
+
98
+ /** Source format of a template or layout. */
99
+ type MailFormat = 'mjml' | 'html';
100
+ /** Context of the functions that produce a template or layout source. */
101
+ interface TranslateContext {
102
+ /** Locale being rendered; undefined for a template without translations. */
103
+ readonly locale: string | undefined;
104
+ /** Text of a translation key in this locale. Throws for an unknown key. */
105
+ t(key: string): string;
106
+ }
107
+ /** Context of a layout: its translations, and the content of the template. */
108
+ interface LayoutContext extends TranslateContext {
109
+ /** Source of the template, in the format of the layout. */
110
+ readonly content: string;
111
+ }
112
+ /** A source, or a function that produces it in the rendered locale. */
113
+ type Source = string | ((context: TranslateContext) => string);
114
+ type LayoutSource = {
115
+ mjml: (context: LayoutContext) => string;
116
+ html?: never;
117
+ } | {
118
+ html: (context: LayoutContext) => string;
119
+ mjml?: never;
120
+ };
121
+ /** Options of `defineLayout()`. */
122
+ type LayoutOptions<L extends string = string> = {
123
+ /** Texts of the layout, read with `t()`. */
124
+ translations?: Translations<L>;
125
+ } & LayoutSource;
126
+ /** Layout created by `defineLayout()`. */
127
+ interface MailLayout<L extends string = string> {
128
+ readonly format: MailFormat;
129
+ /** Locales of the layout translations; empty without translations. */
130
+ readonly locales: readonly string[];
131
+ /**
132
+ * Type-level only: the locales the layout can render.
133
+ * @internal
134
+ */
135
+ readonly __locales?: (locale: L) => void;
136
+ }
137
+ type TemplateSource = {
138
+ mjml: Source;
139
+ html?: never;
140
+ } | {
141
+ html: Source;
142
+ mjml?: never;
143
+ };
144
+ /** Options of `defineTemplate()`. */
145
+ type TemplateOptions<S extends z.ZodType | undefined = undefined, L extends string = string> = {
146
+ /** Name of the template, used in errors and logs. */
147
+ name: string;
148
+ /**
149
+ * Layout that receives the template source as `content`. It must have
150
+ * translations in the template locales.
151
+ */
152
+ layout?: MailLayout<NoInfer<string extends L ? never : L>>;
153
+ /** Schema of the data. Without it, the template accepts no data. */
154
+ schema?: S;
155
+ /**
156
+ * Subject, by locale or the same for all. Rendered by Mustache without
157
+ * HTML escaping: `'Welcome {{firstName}}'`.
158
+ */
159
+ subject: string | Localized<L>;
160
+ /** Texts of the template, read with `t()`. */
161
+ translations?: Translations<L>;
162
+ /**
163
+ * Text version, rendered by Mustache without HTML escaping. Defaults to a
164
+ * conversion of the HTML version.
165
+ */
166
+ text?: Source;
167
+ } & TemplateSource;
168
+ /** Data accepted by a template, given its schema. */
169
+ type DataOf<S> = S extends z.ZodType ? z.input<S> : undefined;
170
+ /** Template created by `defineTemplate()`. */
171
+ interface MailTemplate<TData = undefined> {
172
+ readonly name: string;
173
+ readonly format: MailFormat;
174
+ /** Locales of the template; empty for a template without translations. */
175
+ readonly locales: readonly string[];
176
+ /**
177
+ * Type-level only: the data of the template.
178
+ * @internal
179
+ */
180
+ readonly __data?: TData;
181
+ }
182
+ /** Data accepted by a template. */
183
+ type TemplateData<T> = T extends MailTemplate<infer D> ? D : never;
184
+ /**
185
+ * Defines a layout: the structure shared by templates (head, header,
186
+ * footer). The layout function receives the template source as `content`,
187
+ * and its own translations through `t()`.
188
+ *
189
+ * ```ts
190
+ * const baseLayout = defineLayout({
191
+ * translations: { footer: { en: 'Sent by Acme', fr: 'Envoyé par Acme' } },
192
+ * mjml: ({ t, content }) => `<mjml><mj-body>${content}…${t('footer')}…</mj-body></mjml>`,
193
+ * })
194
+ * ```
195
+ */
196
+ declare const defineLayout: <L extends string = string>(options: LayoutOptions<L>) => MailLayout<L>;
197
+ /**
198
+ * Defines an email template. The source (`mjml` or `html`) is rendered in
199
+ * three steps: `t()` inserts the translations, MJML compiles the result
200
+ * (cached by locale), then Mustache inserts the data.
201
+ *
202
+ * ```ts
203
+ * const welcome = defineTemplate({
204
+ * name: 'welcome',
205
+ * layout: baseLayout,
206
+ * schema: z.object({ firstName: z.string(), url: z.url() }),
207
+ * subject: { en: 'Welcome {{firstName}}', fr: 'Bienvenue {{firstName}}' },
208
+ * translations: { cta: { en: 'Get started', fr: 'Commencer' } },
209
+ * mjml: ({ t }) => `<mj-section>…<mj-button href="{{{ url }}}">${t('cta')}</mj-button>…</mj-section>`,
210
+ * })
211
+ * ```
212
+ */
213
+ declare const defineTemplate: <S extends z.ZodType | undefined = undefined, L extends string = string>(options: TemplateOptions<S, L>) => MailTemplate<DataOf<S>>;
214
+
215
+ /** `data` option: required when the template has a schema. */
216
+ type DataOption<TData> = [TData] extends [undefined] ? {
217
+ data?: undefined;
218
+ } : {
219
+ data: TData;
220
+ };
221
+ /** Options of `renderMail()`. */
222
+ type RenderMailOptions<TData> = {
223
+ /**
224
+ * Locale to render, like `fr-CA`. Falls back to its base language (`fr`),
225
+ * then to `defaultLocale`.
226
+ */
227
+ locale?: string | undefined;
228
+ /** Locale used when the requested one is not available. */
229
+ defaultLocale?: string | undefined;
230
+ } & DataOption<TData>;
231
+ /** A rendered email. */
232
+ interface RenderedMail {
233
+ /** Name of the template. */
234
+ readonly template: string;
235
+ /** Rendered locale; undefined for a template without translations. */
236
+ readonly locale: string | undefined;
237
+ readonly subject: string;
238
+ readonly html: string;
239
+ readonly text: string;
240
+ }
241
+ /**
242
+ * Renders a template: subject, HTML and text versions. The data is
243
+ * validated by the schema of the template.
244
+ *
245
+ * ```ts
246
+ * const { subject, html, text } = await renderMail(welcome, {
247
+ * locale: 'fr-CA',
248
+ * defaultLocale: 'en',
249
+ * data: { firstName: 'Ada', url: 'https://app.example.com' },
250
+ * })
251
+ * ```
252
+ */
253
+ declare const renderMail: <TData>(template: MailTemplate<TData>, options: RenderMailOptions<NoInfer<TData>>) => Promise<RenderedMail>;
254
+
255
+ /** Result of a provider. */
256
+ interface ProviderResult {
257
+ /** Identifier of the message given by the provider, if any. */
258
+ messageId?: string | undefined;
259
+ }
260
+ /**
261
+ * Sends messages: SMTP server, email API, test outbox… Throw a
262
+ * `MailSendError` on failure, or a `MailMessageError` for a message the
263
+ * provider cannot send; other errors are wrapped in a `MailSendError`.
264
+ */
265
+ interface MailProvider {
266
+ /** Name used in errors and logs, like `smtp`. */
267
+ readonly name: string;
268
+ send(message: MailMessage): Promise<ProviderResult | void>;
269
+ }
270
+ /**
271
+ * Logger of the mailer. A Dotpress or Pino logger fits; `mailerPlugin` uses
272
+ * the logger of the request.
273
+ */
274
+ interface MailerLogger {
275
+ info(data: object, message: string): void;
276
+ }
277
+ /** Options of `createMailer()`. */
278
+ interface MailerOptions {
279
+ provider: MailProvider;
280
+ /** Default sender: `'Acme <noreply@acme.com>'`. */
281
+ from: MailAddress;
282
+ /** Default reply-to addresses. */
283
+ replyTo?: MailRecipients | undefined;
284
+ /** Locale used when the requested one is not available. */
285
+ defaultLocale?: string | undefined;
286
+ /** Logs each sent message (template, provider, message ID). */
287
+ logger?: MailerLogger | undefined;
288
+ }
289
+ /** Options of `mailer.send()`. */
290
+ type SendMailOptions<TData> = EnvelopeOptions & {
291
+ /** Overrides the subject of the template. */
292
+ subject?: string | undefined;
293
+ /** Locale to render, like `fr-CA` (see `renderMail()`). */
294
+ locale?: string | undefined;
295
+ } & DataOption<TData>;
296
+ /** Options of `mailer.sendMessage()`: a message rendered elsewhere. */
297
+ interface MessageOptions extends EnvelopeOptions {
298
+ subject: string;
299
+ /** HTML version. At least one of `html` and `text` is required. */
300
+ html?: string | undefined;
301
+ /** Text version. Converted from the HTML version if missing. */
302
+ text?: string | undefined;
303
+ }
304
+ /** Result of a sent message. */
305
+ interface SendResult {
306
+ /** Identifier of the message given by the provider, if any. */
307
+ readonly messageId: string | undefined;
308
+ }
309
+ interface Mailer {
310
+ readonly provider: MailProvider;
311
+ /** Renders a template, then sends it. */
312
+ send<TData>(template: MailTemplate<TData>, options: SendMailOptions<NoInfer<TData>>): Promise<SendResult>;
313
+ /**
314
+ * Renders a template without sending it. The default locale is the one
315
+ * of the mailer.
316
+ */
317
+ render<TData>(template: MailTemplate<TData>, options: RenderMailOptions<NoInfer<TData>>): Promise<RenderedMail>;
318
+ /** Sends a message rendered elsewhere. */
319
+ sendMessage(options: MessageOptions): Promise<SendResult>;
320
+ /** The same mailer, with another logger. */
321
+ withLogger(logger: MailerLogger): Mailer;
322
+ }
323
+ /**
324
+ * Creates a mailer: renders templates and sends messages through a
325
+ * provider.
326
+ *
327
+ * ```ts
328
+ * const mailer = createMailer({
329
+ * provider: createSmtpProvider({ host: 'smtp.example.com' }),
330
+ * from: 'Acme <noreply@acme.com>',
331
+ * defaultLocale: 'en',
332
+ * })
333
+ * await mailer.send(welcome, { to: user.email, locale: user.locale, data })
334
+ * ```
335
+ */
336
+ declare const createMailer: (options: MailerOptions) => Mailer;
337
+
338
+ export { createMailer as u, defineLayout as v, defineTemplate as w, formatAddress as x, renderMail as y };
339
+ export type { Address as A, DataOf as D, EnvelopeOptions as E, LayoutContext as L, MailProvider as M, ProviderResult as P, RenderMailOptions as R, SendMailOptions as S, TemplateData as T, MailMessage as a, DataOption as b, LayoutOptions as c, Localized as d, MailAddress as e, MailAttachment as f, MailFormat as g, MailLayout as h, MailRecipients as i, MailTemplate as j, Mailer as k, MailerLogger as l, MailerOptions as m, MessageOptions as n, RenderedMail as o, SendResult as p, Source as q, TemplateOptions as r, TranslateContext as s, Translations as t };
@@ -0,0 +1,31 @@
1
+ import { MailProvider } from './index.js';
2
+ import { HttpProviderOptions } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Options of `createMailgunProvider()`. */
6
+ interface MailgunProviderOptions extends HttpProviderOptions {
7
+ /** API key, or a sending key of the domain. */
8
+ apiKey: string;
9
+ /** Sending domain, like `mg.example.com`. */
10
+ domain: string;
11
+ /**
12
+ * Base URL of the API. Defaults to `https://api.mailgun.net`; use
13
+ * `https://api.eu.mailgun.net` for a domain created in the EU region.
14
+ */
15
+ baseUrl?: string | undefined;
16
+ }
17
+ /**
18
+ * Creates a provider that sends messages with the API of
19
+ * [Mailgun](https://www.mailgun.com), through `fetch`: no dependency.
20
+ *
21
+ * ```ts
22
+ * const provider = createMailgunProvider({
23
+ * apiKey: process.env.MAILGUN_API_KEY!,
24
+ * domain: 'mg.example.com',
25
+ * })
26
+ * ```
27
+ */
28
+ declare const createMailgunProvider: (options: MailgunProviderOptions) => MailProvider;
29
+
30
+ export { createMailgunProvider };
31
+ export type { MailgunProviderOptions };
@@ -0,0 +1,100 @@
1
+ import {
2
+ checkHttpOptions,
3
+ readJson,
4
+ responseError,
5
+ stringOf
6
+ } from "./chunk-FRMKUAF4.js";
7
+ import {
8
+ base64,
9
+ utf8
10
+ } from "./chunk-2ZABFOZQ.js";
11
+ import {
12
+ MailMessageError,
13
+ formatAddress
14
+ } from "./chunk-QYVN5N3B.js";
15
+
16
+ // src/providers/mailgun.ts
17
+ var DEFAULT_BASE_URL = "https://api.mailgun.net";
18
+ var MAX_OPTIONS_BYTES = 16 * 1024;
19
+ var toForm = (message) => {
20
+ const form = new FormData();
21
+ const options = [];
22
+ form.append("from", formatAddress(message.from));
23
+ for (const [field, addresses] of [
24
+ ["to", message.to],
25
+ ["cc", message.cc],
26
+ ["bcc", message.bcc]
27
+ ]) {
28
+ for (const address of addresses) form.append(field, formatAddress(address));
29
+ }
30
+ form.append("subject", message.subject);
31
+ if (message.text !== "") form.append("text", message.text);
32
+ if (message.html !== void 0 && message.html !== "") {
33
+ form.append("html", message.html);
34
+ }
35
+ if (message.replyTo.length > 0) {
36
+ options.push(["h:Reply-To", message.replyTo.map(formatAddress).join(", ")]);
37
+ }
38
+ for (const [name, value] of Object.entries(message.headers)) {
39
+ options.push([`h:${name}`, value]);
40
+ }
41
+ for (const tag of message.tags) options.push(["o:tag", tag]);
42
+ for (const [key, value] of Object.entries(message.metadata)) {
43
+ options.push([`v:${key}`, value]);
44
+ }
45
+ const size = options.reduce(
46
+ (total, [name, value]) => total + utf8(name).byteLength + utf8(value).byteLength,
47
+ 0
48
+ );
49
+ if (size > MAX_OPTIONS_BYTES) {
50
+ throw new MailMessageError(
51
+ `Mailgun accepts at most ${MAX_OPTIONS_BYTES} bytes of headers, tags and metadata, got ${size}.`
52
+ );
53
+ }
54
+ for (const [name, value] of options) form.append(name, value);
55
+ for (const {
56
+ filename,
57
+ content,
58
+ contentType,
59
+ contentId
60
+ } of message.attachments) {
61
+ const blob = new Blob(
62
+ // A copy: Blob takes ArrayBuffer views only (not shared buffers).
63
+ [typeof content === "string" ? utf8(content) : new Uint8Array(content)],
64
+ contentType === void 0 ? {} : { type: contentType }
65
+ );
66
+ if (contentId === void 0) form.append("attachment", blob, filename);
67
+ else form.append("inline", blob, contentId);
68
+ }
69
+ return form;
70
+ };
71
+ var createMailgunProvider = (options) => {
72
+ const { values, baseUrl, fetch } = checkHttpOptions(
73
+ "createMailgunProvider()",
74
+ options,
75
+ ["apiKey", "domain"],
76
+ DEFAULT_BASE_URL
77
+ );
78
+ const url = `${baseUrl}/v3/${encodeURIComponent(values.domain)}/messages`;
79
+ const authorization = `Basic ${base64(`api:${values.apiKey}`)}`;
80
+ return {
81
+ name: "mailgun",
82
+ send: async (message) => {
83
+ const body = toForm(message);
84
+ const response = await fetch(url, {
85
+ method: "POST",
86
+ headers: { authorization },
87
+ body
88
+ });
89
+ const result = await readJson(response);
90
+ if (!response.ok) {
91
+ throw responseError("mailgun", response, stringOf(result, "message"));
92
+ }
93
+ return { messageId: stringOf(result, "id") };
94
+ }
95
+ };
96
+ };
97
+ export {
98
+ createMailgunProvider
99
+ };
100
+ //# sourceMappingURL=mailgun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/mailgun.ts"],"sourcesContent":["import { base64, utf8 } from '../encoding.js'\nimport { MailMessageError } from '../errors.js'\nimport type { MailProvider } from '../mailer.js'\nimport { formatAddress, type MailMessage } from '../message.js'\nimport {\n checkHttpOptions,\n readJson,\n responseError,\n stringOf,\n type HttpProviderOptions,\n} from './http.js'\n\n/** Options of `createMailgunProvider()`. */\nexport interface MailgunProviderOptions extends HttpProviderOptions {\n /** API key, or a sending key of the domain. */\n apiKey: string\n /** Sending domain, like `mg.example.com`. */\n domain: string\n /**\n * Base URL of the API. Defaults to `https://api.mailgun.net`; use\n * `https://api.eu.mailgun.net` for a domain created in the EU region.\n */\n baseUrl?: string | undefined\n}\n\nconst DEFAULT_BASE_URL = 'https://api.mailgun.net'\n// Total size of the o:, h:, v: and t: options.\nconst MAX_OPTIONS_BYTES = 16 * 1024\n\n/** Form data of the API, see https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/messages/post-v3--domain-name--messages */\nconst toForm = (message: MailMessage) => {\n const form = new FormData()\n const options: [string, string][] = []\n\n form.append('from', formatAddress(message.from))\n // One field per address: names may contain commas.\n for (const [field, addresses] of [\n ['to', message.to],\n ['cc', message.cc],\n ['bcc', message.bcc],\n ] as const) {\n for (const address of addresses) form.append(field, formatAddress(address))\n }\n form.append('subject', message.subject)\n if (message.text !== '') form.append('text', message.text)\n if (message.html !== undefined && message.html !== '') {\n form.append('html', message.html)\n }\n\n if (message.replyTo.length > 0) {\n options.push(['h:Reply-To', message.replyTo.map(formatAddress).join(', ')])\n }\n for (const [name, value] of Object.entries(message.headers)) {\n options.push([`h:${name}`, value])\n }\n for (const tag of message.tags) options.push(['o:tag', tag])\n for (const [key, value] of Object.entries(message.metadata)) {\n options.push([`v:${key}`, value])\n }\n const size = options.reduce(\n (total, [name, value]) =>\n total + utf8(name).byteLength + utf8(value).byteLength,\n 0\n )\n if (size > MAX_OPTIONS_BYTES) {\n throw new MailMessageError(\n `Mailgun accepts at most ${MAX_OPTIONS_BYTES} bytes of headers, tags and metadata, got ${size}.`\n )\n }\n for (const [name, value] of options) form.append(name, value)\n\n for (const {\n filename,\n content,\n contentType,\n contentId,\n } of message.attachments) {\n const blob = new Blob(\n // A copy: Blob takes ArrayBuffer views only (not shared buffers).\n [typeof content === 'string' ? utf8(content) : new Uint8Array(content)],\n contentType === undefined ? {} : { type: contentType }\n )\n // Inline images are referenced by their file name: cid:<contentId>.\n if (contentId === undefined) form.append('attachment', blob, filename)\n else form.append('inline', blob, contentId)\n }\n return form\n}\n\n/**\n * Creates a provider that sends messages with the API of\n * [Mailgun](https://www.mailgun.com), through `fetch`: no dependency.\n *\n * ```ts\n * const provider = createMailgunProvider({\n * apiKey: process.env.MAILGUN_API_KEY!,\n * domain: 'mg.example.com',\n * })\n * ```\n */\nexport const createMailgunProvider = (\n options: MailgunProviderOptions\n): MailProvider => {\n const { values, baseUrl, fetch } = checkHttpOptions(\n 'createMailgunProvider()',\n options,\n ['apiKey', 'domain'],\n DEFAULT_BASE_URL\n )\n const url = `${baseUrl}/v3/${encodeURIComponent(values.domain)}/messages`\n const authorization = `Basic ${base64(`api:${values.apiKey}`)}`\n\n return {\n name: 'mailgun',\n send: async (message) => {\n const body = toForm(message)\n // The content type (with its boundary) is set by fetch.\n const response = await fetch(url, {\n method: 'POST',\n headers: { authorization },\n body,\n })\n const result = await readJson(response)\n if (!response.ok) {\n throw responseError('mailgun', response, stringOf(result, 'message'))\n }\n return { messageId: stringOf(result, 'id') }\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,28 @@
1
+ import { MailProvider } from './index.js';
2
+ import { HttpProviderOptions } from './index.js';
3
+ import 'zod';
4
+
5
+ /** Options of `createMailjetProvider()`. */
6
+ interface MailjetProviderOptions extends HttpProviderOptions {
7
+ /** API key (public). */
8
+ apiKey: string;
9
+ /** Secret key (private). */
10
+ apiSecret: string;
11
+ /** Base URL of the API. Defaults to `https://api.mailjet.com`. */
12
+ baseUrl?: string | undefined;
13
+ }
14
+ /**
15
+ * Creates a provider that sends messages with the Send API v3.1 of
16
+ * [Mailjet](https://www.mailjet.com), through `fetch`: no dependency.
17
+ *
18
+ * ```ts
19
+ * const provider = createMailjetProvider({
20
+ * apiKey: process.env.MAILJET_API_KEY!,
21
+ * apiSecret: process.env.MAILJET_API_SECRET!,
22
+ * })
23
+ * ```
24
+ */
25
+ declare const createMailjetProvider: (options: MailjetProviderOptions) => MailProvider;
26
+
27
+ export { createMailjetProvider };
28
+ export type { MailjetProviderOptions };