@unitpost/email

Component library

Build emails from a small set of cross-client-tested components. Import them as React (`@unitpost/email/react`), author visually in the template editor, or write the same tags as a string. Same document, same HTML recipients get.

Introduction

Email component library that works in Outlook, Gmail, and Apple Mail.

Email component library that works in Outlook, Gmail, and Apple Mail. `@unitpost/email` is the same renderer the send engine uses — preview here is the email recipients get. Author as React components, as a TSX string, or visually in the template editor. All three produce the same document.

React when you want it

`import { Heading, Button, render } from "@unitpost/email/react"` is the react-email-shaped path — types and JSX in your editor. The visual editor and agents use the same tags as a string (`parseTsx`) so the document can round-trip. Neither path is a second renderer.

Style with Tailwind-style `className` (compiled to inline CSS), extra declarations via `custom-css`, or the component props. Recipients get the compiled markup — not a stylesheet they have to load.

Insert dynamic data anywhere with `{{variable}}` — it's substituted at send time from the values you pass to the API, a contact's fields, or a campaign default.

Installation

Add the package and render your first email in a few minutes.

You don't need to install anything to use the components in the in-app editor — open any template, switch to Code mode, and start composing. For a coding agent, one Skill command. For your own codebase, the npm package.

Skill (for agents)

Teaches the catalog, layouts, and renderer. No account.

Bash
npx skills add unitpostcom/email

1. Install the package

Install `@unitpost/email` (plus its `zod` peer dependency) — it ships the component types, the parser, and the same `renderToHtml` renderer the send engine uses. Source: github.com/unitpostcom/email.

Bash
# npm
npm install @unitpost/email zod

# pnpm
pnpm add @unitpost/email zod

# yarn
yarn add @unitpost/email zod

# Requires Node.js 18+.

2. Write an email template

In a React app, import the catalog and call `render`. Without React (or in the editor's Code mode), the same tags are a string passed to `parseTsx`.

Node.js
import { Section, Heading, Text, Button, render } from "@unitpost/email/react";

export function Welcome({ name }: { name: string }) {
  return (
    <Section paddingY={32}>
      <Heading level={1}>Welcome, {name}</Heading>
      <Text>Thanks for joining. Confirm your email to get started.</Text>
      <Button href="https://example.com/verify">Verify email</Button>
    </Section>
  );
}

const html = render(<Welcome name="Mike" />);

// No React: parseTsx(`<Section>…</Section>`) then renderToHtml(doc).

Prefer the visual builder?

You never have to write code — the template editor builds the identical document with drag-and-drop blocks, and you can flip to Code mode any time to see (or edit) the TSX.

3. Send it

Save the template in your workspace and reference it when you send, or pass the rendered HTML directly. Both go through the same delivery pipeline — see the API reference for the full send surface.

cURL
curl https://www.unitpost.com/api/v1/email \
   -H "Authorization: Bearer $UNITPOST_API_KEY" \
   -H "Content-Type: application/json" \
   -H "User-Agent: my-app/1.0" \
   -d '{
     "from": "you@yourdomain.com",
     "to": "mike@example.com",
     "template": {
       "id": "tmpl_...",
       "variables": { "token": "abc123" }
     }
   }'

4. Next steps

Explore the components below — start with Section and Row for layout, then Heading, Text, and Button for content. Every block also accepts the common props.

Components

Every building block, with a live preview and its props.

Layout

<Section>

Container

Use a Section to band content together — a hero, a card, a footer. It can hold any leaf block and can be nested inside another Section.

Open full page

PropTypeDefaultDescription
background-colorcolorFill color behind the section.
padding-xnumber24Horizontal inner padding (px).
padding-ynumber24Vertical inner padding (px).
border-widthnumberBorder stroke width (px). 0/absent = no visible border.
border-styleenumsolid | dashed | dottedBorder stroke style.
border-colorcolorBorder stroke color.
border-radiusnumberCorner rounding (px). Independent of width — round a filled card without a stroke.

<Row>

Container

Rows render as a single table row with one cell per column, so they stay side-by-side even in Outlook. A Row may only contain Column components.

Open full page

PropTypeDefaultDescription
column-gapnumber8Horizontal gap (px) between columns. Set 0 for flush.
stack-on-mobilebooleantrueBest-effort: let columns wrap on narrow viewports (table emails stay side-by-side in Outlook).
background-colorcolorFill color behind the row.
padding-xnumber24Horizontal inner padding (px).
padding-ynumber8Vertical inner padding (px).
border-widthnumberBorder stroke width (px). 0/absent = no visible border.
border-styleenumsolid | dashed | dottedBorder stroke style.
border-colorcolorBorder stroke color.
border-radiusnumberCorner rounding (px). Independent of width — round a filled card without a stroke.

<Column>

Container

Columns are table cells. `width` is a percentage of the row; the widths of the columns in a row should add up to ~100.

Open full page

PropTypeDefaultDescription
widthnumber50Width as a percentage (1–100) of the parent Row.
background-colorcolorFill color behind the column.
padding-xnumber8Horizontal inner padding (px).
padding-ynumber0Vertical inner padding (px).
border-widthnumberBorder stroke width (px). 0/absent = no visible border.
border-styleenumsolid | dashed | dottedBorder stroke style.
border-colorcolorBorder stroke color.
border-radiusnumberCorner rounding (px). Independent of width — round a filled card without a stroke.

Content

A title (H1–H4). Inner text supports {{variables}}.

Open full page

PropTypeDefaultDescription
levelenum1 | 2 | 3 | 42Heading level — controls size/weight.
alignenumleft | center | rightleftText alignment.
colorcolorText color.
font-sizenumberFont size (px). Overrides the level's default size (28/22/18/16 for H1–H4).
font-familystringOverride the document font for this heading.
font-weightstringFont weight (e.g. 400, 600, 700). Overrides the default bold heading weight.
line-heightstringLine height — a unitless multiplier (e.g. 1.3) or CSS length.
letter-spacingnumberLetter spacing (tracking) in px. Great for eyebrow labels.

Inner content may include inline HTML for formatting: <strong>, <em>, <u>, <a href>, and <span style> (color / background-color). These round-trip through the visual editor.

Open full page

PropTypeDefaultDescription
alignenumleft | center | rightleftText alignment.
colorcolorText color.
font-sizenumber16Font size (px).
font-familystringOverride the document font for this block.
font-weightstringFont weight (e.g. 400, 500, 600, 700).
line-heightstringLine height — a unitless multiplier (e.g. 1.5) or CSS length.
letter-spacingnumberLetter spacing (tracking) in px.

Margins are unreliable across clients, so a Spacer renders as an explicit fixed-height cell.

Open full page

PropTypeDefaultDescription
heightrequirednumber24Gap height (px).

A thin horizontal rule to separate sections.

Open full page

PropTypeDefaultDescription
colorcolor#e4e4e7Line color.

Author rich copy in Markdown — compiled to email-safe HTML.

Open full page

PropTypeDefaultDescription
alignenumleft | center | rightleftAlignment.
colorcolorBase text color.
font-sizenumber16Base font size (px).

A monospace code block — handy for API keys and snippets.

Open full page

PropTypeDefaultDescription
background-colorcolor#f4f4f5Block background.
colorcolor#1a1a1aCode text color.

Interactive

A call-to-action — a styled, padded link that looks like a button.

Open full page

PropTypeDefaultDescription
hrefrequiredurl#Destination URL. {{variables}} are allowed.
alignenumleft | center | rightleftHorizontal alignment of the button.
background-colorcolor#18181bButton fill color.
text-colorcolor#ffffffLabel color.
border-radiusnumber9999Corner radius (px).
inner-padding-xnumber32Horizontal padding inside the button (px).
inner-padding-ynumber12Vertical padding inside the button (px).

A standalone styled hyperlink (no button background).

Open full page

PropTypeDefaultDescription
hrefrequiredurl#Destination URL. {{variables}} are allowed.
alignenumleft | center | rightleftAlignment.
colorcolorLink color.
font-sizenumber16Font size (px).
font-familystringOverride the document font for this link.
underlinebooleantrueWhether the link is underlined. Defaults to underlined; set false to remove it.

Media

A responsive image, optionally wrapped in a link.

Open full page

PropTypeDefaultDescription
srcrequiredurlImage URL (use an absolute, hosted URL).
altstringAlternative text (shown if the image can't load).
hrefurlMake the image a link to this URL.
widthnumberContainer (frame) width in px. The image scales to fill it; defaults to full content width.
heightnumberOptional frame height in px. By default the height adapts to the image's aspect ratio — set this to pin an explicit height.
objectFitenumcover | contain | fillHow the image fills the frame when a height is set: cover (crop), contain (letterbox), or fill (stretch). Client support varies.
backgroundColorcolorFrame background shown around the image when “contain” leaves gaps.
borderRadiusnumberCorner rounding in px.
alignenumleft | center | rightcenterHorizontal alignment.
border-widthnumberFrame border stroke width (px).
border-styleenumsolid | dashed | dottedFrame border stroke style.
border-colorcolorFrame border stroke color.

Advanced

There's no <Html> tag in code mode — any markup that isn't a known component (e.g. a <table> or <div>) automatically becomes an Html block and is emitted verbatim at render. Content passes through the 3-layer HTML sanitizer (scripts, event handlers, and javascript: URLs are stripped) both on save and at render. {{variables}} interpolate inside the markup. Prefer the built-in components when possible — raw HTML is on you to keep Outlook-safe.

Open full page

No component-specific props beyond the common ones.

Common props

Every block accepts these in addition to its own props.

PropTypeDefaultDescription
margin-bottomnumber16Vertical space (px) below the block.
classNamestringTailwind-style utility classes compiled to inline CSS at render (e.g. "mt-4 text-zinc-500 sm:hidden"). Responsive/pseudo variants are hoisted into a <style> block. `class` and `tw` are accepted as aliases in code.
custom-csscssExtra inline CSS declarations merged onto the block's root element (e.g. "letter-spacing: 1px; opacity: 0.9"). Inlined so it survives every client.

Per-side spacing is also supported via padding and margin objects in the visual editor's inspector.

Layouts

Pre-built sections from the same components — not a second API. Copy the React example into your app, or insert from the Layouts rail in the editor. Same HTML either way.

Header

Centered logo mark — the classic email opener.

Logo + links

Logo on the left, quick links on the right.

Hero

Simple hero

Big headline, supporting line, and a pill CTA — the transactional first screen.

Image hero

Full-width image above a headline, copy, and CTA — the announcement opener.

Status hero

Eyebrow label + headline + copy — confirmations and status updates.

Content

Card

A soft tinted card for grouped details — receipts, key facts, summaries.

Detail rows

Label/value lines in a tinted card — order summaries and invoices.

Verification code

A big spaced code inside a tinted card — OTP and verification emails.

Quote / callout

An indented callout for a quote or an important note.

Action feature

An emoji icon, a headline, and a button — perfect for calling out a specific setting or next step.

Announcement banner

A tinted callout card with an icon, headline, and link — great for product updates.

Article feature

An eyebrow label, headline, full-width image, and a link — for deep dives and sponsored content.

Columns

Split 50/50

Image beside copy — the classic feature split (stacks on mobile).

Two cards

Side-by-side bordered cards — check-in/checkout, before/after, plan A/B.

Three features

Three equal columns for a compact feature or stats strip.

Numbered steps

A stacked 1-2-3 list — onboarding and how-it-works flows.

CTA band

A filled band with a headline and a contrasting pill button.

Centered CTA

Just a headline and a pill button — minimal and direct.

Hairline and a friendly help line — the transactional closer. No postal address or unsubscribe (marketing emails get those from the managed compliance footer automatically).

Logo and a links row (blog, help, contact). No unsubscribe or postal address — those are added automatically by the managed compliance footer.

Two-column footer: a short tagline on the left, app/store links on the right. No unsubscribe or postal address — the managed compliance footer adds those automatically.

Playground

Write email code and see the exact HTML our engine produces — live, in your browser, with nothing to install. It renders through the same pipeline the editor's exact preview and the send engine use.

Open the playground