@uxf/mailing
v13.0.2
Published
Email rendering utilities and build tools
Readme
@uxf/mailing
Tooling for authoring react-email templates and rendering them to HTML from Node — an esbuild-based production build step, a tsx dev runner, responsive layout helpers, and a preview-props helper.
When to use
Use this in a project that writes transactional emails as React (react-email) components and needs to turn them into HTML from a Node/CLI process (in UXF projects a PHP backend shells out to Node to produce the final HTML). It provides:
build-renderers— bundles each template into a standalone, self-executing.jsrenderer.dev-runner— renders a single template on the fly withtsx, no build step.responsive—ResponsiveRow/ResponsiveColumnlayout helpers for email-safe multi-column rows.with-preview-props— attaches preview data to a template for thereact-emailpreview UI.
It is not an email-sending package (no SMTP/transport) and not a component library — the email components (Html, Body, Text, Tailwind, render, …) come from the react-email peer dependency.
Installation
yarn add @uxf/mailing- ESM-only (
"type": "module") and requires Node >= 24. - Peer dependencies the consuming project must provide:
react ^19.3.0,react-dom ^19.3.0,react-email ^6.11.0. tsx ^4.23.12is also a peer dependency. The package ships its entry points as raw.ts/.tsx, sobuild-renderersanddev-runnerare executed withtsxrather thannode— see Build & run. npm installs it automatically; yarn and pnpm need it added explicitly.- Bundled dependencies (nothing to install):
esbuild,glob,yargs,dotenv.
Public entry points
The package ships an exports map — import only these subpaths (there is no root/index export):
| Import path | Kind | Exports |
| --------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @uxf/mailing/with-preview-props | module | withPreviewProps |
| @uxf/mailing/responsive | module | ResponsiveRow, ResponsiveColumn, isResponsiveColumn, createResponsiveRow, createResponsiveColumn, and types ResponsiveRowProps, ResponsiveColumnProps, BaseSectionProps |
| @uxf/mailing/types | types | EmailOptions<Data>, AnyObject, BuildResult |
| @uxf/mailing/build-renderers | CLI script | run via tsx (see Build & run) |
| @uxf/mailing/dev-runner | CLI script | run via tsx (see Build & run) |
renderer-entry.tsx and renderer-plugin.ts are internal build machinery and are not part of the public surface.
Quick start
A template is a .tsx file that exports a named EmailComponent (rendered to HTML) plus a default export that wires up preview data for the react-email preview UI. The component receives EmailOptions-shaped props ({ data }).
// templates/react/my-template.tsx
import { Body, Container, Head, Html, pixelBasedPreset, Tailwind, Text } from "react-email";
import { withPreviewProps } from "@uxf/mailing/with-preview-props";
import previewProps from "./my-template.json";
export function EmailComponent(props: { data: { title: string; message: string } }) {
return (
<Tailwind config={{ presets: [pixelBasedPreset] }}>
<Html lang="cs">
<Head>
<title>{props.data.title}</title>
</Head>
<Body className="mx-auto max-w-[600px] p-5 font-sans">
<Container>
<Text>{props.data.message}</Text>
</Container>
</Body>
</Html>
</Tailwind>
);
}
export default function MyTemplate() {
return withPreviewProps(EmailComponent, previewProps);
}// templates/react/my-template.json
{
"data": {
"title": "My Email Template",
"message": "Hello from my custom template!"
}
}Render it in dev, or build a production bundle:
# dev: render on the fly (no build)
tsx node_modules/@uxf/mailing/dev-runner.tsx templates/react/my-template.tsx '{"data": {"title": "Hi", "message": "Hello"}}'
# production: bundle every template under the path into a sibling .js
tsx node_modules/@uxf/mailing/build-renderers.ts --path templates/reactThe JSON argument is the top-level EmailOptions object; its data field is passed to the component as props.data.
API
withPreviewProps
import { withPreviewProps } from "@uxf/mailing/with-preview-props";
withPreviewProps<Data extends AnyObject, Props extends EmailOptions<Data>>(
Component: FunctionComponent<Props>,
props: Props,
): ReactElement;Attaches props to Component.PreviewProps (consumed by the react-email preview UI) and returns <Component data={props.data} />. Use it in the template's default export, passing the named EmailComponent and the imported preview JSON.
responsive
Email-safe multi-column layout built from nested tables. Columns are declared with span (relative width); rows warn in the console above 3 columns.
import { ResponsiveColumn, ResponsiveRow } from "@uxf/mailing/responsive";
<ResponsiveRow maxWidth={600} paddingLeft={16} paddingRight={16}>
<ResponsiveColumn span={2} style={{ backgroundColor: "green" }} />
<ResponsiveColumn span={1} style={{ backgroundColor: "red" }} />
</ResponsiveRow>;ResponsiveRowProps— table attributes plusmaxWidth,paddingLeft,paddingRight,paddingTop,paddingBottom(numbers, in px).padding*andmaxWidthare removed from thestyleprop and set via the dedicated props instead.maxWidth(default600) is the width of the whole row, left and right padding included; the columns share what is left in proportion to theirspan.ResponsiveColumnProps— table attributes plusspan?: number(default1) andtdProps(forwarded to the inner<td>).isResponsiveColumn(node)— type guard;createResponsiveRow/createResponsiveColumnare the factories the defaultResponsiveRow/ResponsiveColumnare built from.
types
import type { AnyObject, BuildResult, EmailOptions } from "@uxf/mailing/types";EmailOptions<Data extends AnyObject = AnyObject>—{ data: Data }, the props shape every template receives.AnyObject—Record<string, any>.BuildResult— per-file result metadata produced bybuild-renderers.
Build & run
build-renderers (production)
tsx node_modules/@uxf/mailing/build-renderers.ts --path templates/react--path,-p(required) — templates directory. Globs every**/*.tsxunder it.- Bundles each template with esbuild (ESM, minified,
target: es2022,platform: node, JSXautomatic) into a.jsfile next to the source. - Externals (resolved from the runtime's
node_modules, not inlined):react,react-dom,react/jsx-runtime,react-email,yargs.
Each bundle is a self-executing renderer. Run it with a JSON argument or a --config file:
# JSON string
node templates/react/my-template.js '{"data": {"title": "Test", "message": "Hello"}}'
# or read the JSON from a file
node templates/react/my-template.js --config templates/react/my-template.jsonIt renders the template's named EmailComponent and prints the HTML to stdout.
dev-runner (development)
tsx node_modules/@uxf/mailing/dev-runner.tsx <template-path> <json-data>Dynamically imports the template's named EmailComponent, renders it with the given JSON, and prints the HTML. No build step; it also loads a project .env via dotenv/config.
Environment variables
Templates can read process.env at render time, e.g.:
<Img alt="Logo" src={`${process.env.FRONTEND_URL}/assets/mailing/logo.png`} />dev-runner loads variables from the project .env (dotenv/config). Production .js bundles do not load .env themselves — the caller must supply the environment.
tsconfig requirements
The consuming project's tsconfig.json must set jsx: "react-jsx" and include the templates directory:
{
"compilerOptions": {
"jsx": "react-jsx"
},
"include": ["templates/react/**/*.ts", "templates/react/**/*.tsx"]
}Gotchas
- ESM + Node ≥ 24 only. The package has no root export — always import from a subpath listed above.
- Every template must export a named
EmailComponent(this is what gets rendered) and a default function (suppliesPreviewPropsfor the preview UI). The runners importEmailComponent, not the default. - Template files must end in
.tsx;build-renderersonly globs**/*.tsx. react,react-dom,react-email(andyargs) are external in built bundles — they must be installed in the runtime'snode_modules.- Keep to at most 3
ResponsiveColumns perResponsiveRow; more logs a console warning. Set row padding/maxWidthvia the dedicated props, notstyle.
Troubleshooting
React is not definedat runtime — thetsconfig.jsonincludedoes not cover the template files; ensuretemplates/react/**is included andjsx: "react-jsx"is set.- Build failures — confirm templates end with
.tsx, each exports a namedEmailComponentplus a default function, and runtsc --noEmit(yarn typecheck) to surface type errors.
