import escapeHtml from "escape-html" import { compile } from "html-to-text" import { marked } from "marked" import * as z from "zod" const HTML_TO_TEXT = compile({ wordwrap: false }) /** Optional body formats shared by email-sending actions. */ export const OPTIONAL_EMAIL_BODY_SCHEMA = z.object({ /** HTML message body. */ html: z.string().min(1).optional(), /** Markdown message body. */ markdown: z.string().min(1).optional(), /** Plain-text message body. */ text: z.string().min(1).optional(), }) /** Email body with at least one caller-supplied format. */ export const EMAIL_BODY_SCHEMA = z.union([ OPTIONAL_EMAIL_BODY_SCHEMA.required({ html: true }), OPTIONAL_EMAIL_BODY_SCHEMA.required({ markdown: true }), OPTIONAL_EMAIL_BODY_SCHEMA.required({ text: true }), ]) type EmailBody = z.output /** * Whether an object includes at least one email body format. * * @param input - Optional body formats to inspect. */ export function hasEmailBody( input: z.output, ): input is EmailBody { return ( input.html !== undefined || input.markdown !== undefined || input.text !== undefined ) } /** * Produces complete HTML and plain-text MIME alternatives. * * @param input - One or more caller-supplied body formats. */ export function normalizeEmailBody(input: EmailBody): { html: string text: string } { if (input.markdown !== undefined) { const markdownHtml = marked.parse(input.markdown, { async: false }) return { html: input.html ?? markdownHtml, text: input.text ?? HTML_TO_TEXT(markdownHtml), } } if (input.html !== undefined) { return { html: input.html, text: input.text ?? HTML_TO_TEXT(input.html), } } // EMAIL_BODY_SCHEMA guarantees text in the only remaining union member. const text = input.text! return { html: `

${escapeHtml(text).replaceAll(/\r?\n/g, "
")}

`, text, } }