@unitpost/email
@unitpost/email builds HTML emails from React or a TSX string. Maintained by Unitpost, a sending service. Version 0.6.0, 4 stars (read 2026-11-09).
@unitpost/email is a library for writing an email in TypeScript or JavaScript and rendering it to HTML and plain text. You either build a React component from its own components (Section, Heading, Text, Button and so on) and call render(), or write the same tags as a string and pass it to parseTsx().
- Language
- TypeScript, JavaScript (React optional)
- License
- MIT, free
- Maintainer
- Unitpost
- Latest release
- 0.6.0 (2026-11-08)
- Weekly npm downloads
- 414
- Source
- unitpostcom/email
Figures read on November 9, 2026.
- A TypeScript team that keeps emails in its repository
- Teams that prefer a template held in a string, with no build step
- Any sending provider: rendering needs no account or API key
- A Node.js runtime: 18 or newer is declared, CI runs 20
- A backend outside JavaScript
- A project that needs a 1.0 release or frozen HTML output
- A project that cannot take
zodas a peer dependency
Minimal example
This is the README's React example (quickstart steps 2 and 3).
// emails/welcome.tsximport { Section, Heading, Text, Button, render } from '@unitpost/email/react';export function WelcomeEmail({ name }: { name: string }) {return (<Section><Heading>Welcome, {name}</Heading><Text>Thanks for signing up. Confirm your email to get started.</Text><Button href="https://example.com/confirm">Confirm email</Button></Section>);}// renderconst html = render(<WelcomeEmail name="Ada" />);
render() returns an HTML string synchronously and does not use React DOM. Here is the same email without React, from the README's "Without React" section.
import { parseTsx, renderToHtml } from '@unitpost/email';const doc = parseTsx(`<Section><Heading>Welcome, {{first_name}}</Heading><Button href="https://example.com/confirm">Confirm email</Button></Section>`);const html = renderToHtml(doc, { first_name: 'Ada' });
{{first_name}} is a variable filled by the second argument of renderToHtml() (or of render()), and the values are HTML-escaped.
Pros and cons
- MIT license, no account to render
- React and TSX-string authoring paths
- Plain-text part from
renderToText() - Local preview server with plain-text and dark views
- One runtime dependency, no CSS inliner
- Pre-1.0, rendered HTML can change
- Unknown Tailwind classes dropped silently
- Component props override
className - Hooks and plain HTML throw in React
- Small adoption
The details behind each point are in How it works, Limits and gotchas and Maintenance.
How it works
The renderer emits table layout: the source writes <table role="presentation" border="0" cellpadding="0" cellspacing="0"> elements, and the project describes the output as table layout with inline CSS.
- Inlining:
className(Tailwind-style) andcustomCsscompile to inlinestyleattributes at render time, in a fixed order: theme defaults,className, schema props, thencustomCss. There is no separate inliner dependency, and the one runtime dependency issanitize-html.sm:andhover:variants are hoisted into a<style>block in the<head>. That block always holds the fluid-body rule, the column-stacking rules at 600px, those hoisted rules, then either the dark-mode CSS or a light-onlycolor-schemedeclaration. - Outlook: the 0.4.0 changelog lists VML and Office namespaces on
<html>,OfficeDocumentSettingsat 96 DPI,mso-table-lspaceandmso-table-rspaceset to 0, andmso-line-height-rule: exactly. It adds a fixed-width ghost table holding the card attheme.contentWidth, because Outlook for Windows ignoresmax-width. The changelog says this applies to "every sent Design email". We rendered the published 0.6.0 package and read the HTML: the Reactrender()output carries the same MSO conditionals and namespaces, and we did not open it in a mail client. The buttons are padded tables with no VML. Since 0.6.0, aSectionwith a background image gets a VML rectangle. - Plain text:
renderToText(doc)returns a plain-text string to send next to the HTML. React elements go throughfromJsx()first. - Preview:
npx @unitpost/email devlists the emails inemails/, reloads on save and shows each at desktop and mobile width, light and dark, with HTML, plain-text and design lint views. It listens on 127.0.0.1 only and usesesbuildto bundle React emails.
The HTML and text parts you send travel as a MIME message. MIME covers how the parts are packaged and message format covers the headers around them.
Send it with an email API
Render the email, then pass the HTML and the plain-text part to the Nuntly SDK.
import { Nuntly } from '@nuntly/sdk';import { parseTsx, renderToHtml, renderToText } from '@unitpost/email';const nuntly = new Nuntly({ apiKey: process.env.NUNTLY_API_KEY! });const doc = parseTsx(`<Section><Heading>Welcome, {{first_name}}</Heading></Section>`);const vars = { first_name: 'Ada' };const html = renderToHtml(doc, vars);const text = renderToText(doc, vars);await nuntly.emails.send({from: 'Acme <hello@example.com>',to: 'jane@example.org',subject: 'Welcome',html,text,});
The Send Your First Email guide covers the SDK setup.
Email client support
- Outlook
- Gmail
- Apple Mail
- Outlook for Windows layout fixes
- Opt-in dark mode
- Client versions tested
- Screenshots or test results
- Test method
The README opens with "Email component library that works in Outlook, Gmail, and Apple Mail", and the 0.4.0 changelog describes the Outlook settings. We did not test the output in a mail client.
Outlook on Windows is the risky one. The renderer adds a fixed-width ghost table, but the Button puts its padding on the link and its corners on border-radius, and caniemail lists neither as supported there, so expect a square, tight button (inferred from the markup). Set width on every Image. Dark mode is off by default, and caniemail lists prefers-color-scheme as unsupported in every Gmail client and in Outlook on Windows, where Gmail applies its own inversion. The renderer loads no web fonts, and caniemail lists base64 images as unsupported in Gmail. Before you ship, send a test to Outlook on Windows and Gmail.
Limits and gotchas
- Component props override
className. Props with a default (TextfontSizeandmarginBottom,Sectionpadding,Headingsize,Buttoncolor) win over Tailwind-style classes, sotext-lg,p-4orbg-red-600on those components change nothing, even in the vendor's own samples. Set the prop, or usecustomCss, which is applied last. - Unsupported classes are dropped without a warning.
classNameis a curated subset of Tailwind: only part of the color palette, andmd:,lg:anddark:classes are discarded, with no message from the preview lint.sm:means 600px and below, not a min-width breakpoint, and its rule has no!important, so it cannot override a value the element already sets inline (padding, font size). CheckcompileClasses(className).unknownwhen a style does not apply. - Outlook for Windows is untested, and two components are at risk.
Buttonputs its padding on the link and its corners onborder-radius, and caniemail says Outlook for Windows supports neither there, so the button probably renders square and tight (inferred from the markup, not seen in Outlook). Setwidthon everyImage, because without it there is nowidthattribute and Outlook ignores CSS width on images. - React components are not rendered by React. Your function components are called as plain functions and lowered to the email document, so hooks, Context providers, plain HTML elements such as
<div>and async components throw. Pass data in as props and return only catalog components. - Missing variables ship as written. A
{{token}}without a value stays in the HTML, so checkresolveVariables(doc, values).missingbefore you send. Withcategory="marketing"the renderer adds a fixed English unsubscribe footer whose link is{{unsubscribe_url}}, so pass that value yourself.
Maintenance
Active but pre-1.0: eleven releases in four months, the latest on 2026-11-08, the same day as the last commit.
- Pin the version. The changelog counts a change to the rendered HTML as breaking, and 0.5.0 shipped one: the "Sent with Unitpost" mark became opt-in.
- Expect a mirror. The GitHub repository only receives commits synced from another Unitpost repository, and pull requests are imported, not merged.
- Take versions from npm. The repository has no GitHub Releases and no git tags.
Compare
- React Email: React components, maintained by another sending service.
- JSX Email: React components too, with an individual maintainer.
- MJML: XML-style markup instead of JSX.
- mjml-react: React components that compile through MJML.
- Maizzle: Vue single-file components with Tailwind CSS.
- better-svelte-email: Svelte 5 components.
- Hermes: a Go library that fills a struct instead of a template.
- The email frameworks hub: every tool, with its status and read date.
FAQ
What is @unitpost/email?
It is an open-source (MIT) TypeScript library from Unitpost that turns React components, or the same tags written as a TSX string, into HTML and plain-text emails. The npm package is @unitpost/email and the latest version is 0.6.0, read 2026-11-09.
Do I need a Unitpost account to use it?
No, according to the README: rendering needs no account or API key, and the HTML works with any provider. The README's first send example and the preview server's "Send test" button do use Unitpost, so you wire up your own sender for production.
Can I use it without React?
Yes. Write the tags as a string, parse it with parseTsx(source) and render the document with renderToHtml(doc). The README says this needs no build step, and a golden test checks that the React and string paths render the same email.
Is @unitpost/email production ready?
It is pre-1.0 (0.6.0), published since 2026-07-12, with 4 GitHub stars and 414 weekly npm downloads (read 2026-11-09). The changelog treats a change to rendered HTML as breaking and 0.5.0 shipped one, so pin the version and compare the rendered output when you upgrade.
Does it handle Outlook?
The project says it works in Outlook, Gmail and Apple Mail, and its 0.4.0 changelog describes MSO settings and a fixed-width ghost table for Outlook for Windows. It publishes no per-client test results, and the Button is expected to render square in Outlook on Windows, so test your own templates.