Migrate

Bring your React Email templates.
Leave the tables behind.

One command converts every template, keeps its props, loops and conditions, then renders the original and the migration side by side and proves the content survived. Nothing is written until you say so.

Read the guide →

Before: React Email + Tailwind

emails/welcome.tsx
import { Body, Button, Container, Head, Heading, Html, Preview, Tailwind, Text } from "@react-email/components";

export default function Welcome({ name = "Ada" }: { name?: string }) {
  return (
    <Html>
      <Head />
      <Preview>Welcome to Orbit</Preview>
      <Tailwind>
        <Body className="bg-gray-100 font-sans">
          <Container className="mx-auto my-10 max-w-[520px] rounded-lg bg-white p-8">
            <Heading className="m-0 text-2xl font-bold text-gray-900">Welcome, {name}</Heading>
            <Text className="text-base text-gray-600">Your workspace is ready.</Text>
            <Button href="https://example.com/start" className="rounded-md bg-indigo-600 px-5 py-3 text-white">
              Open your workspace
            </Button>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  );
}

Welcome.PreviewProps = { name: "Ada" };

After: Hyper Elements, written by the migration

emails/welcome.tsx
// Migrated from React Email by @hyper-elements/migrate (codemod mode).
import {
  Button,
  Column,
  Email,
  Heading,
  Section,
  Text,
} from "@hyper-elements/react";

export default function Welcome({ name = "Ada" }: { name?: string }) {
  return (
    <Email
      width={520}
      previewText="Welcome to Orbit"
      style={{
        backgroundColor: "#f3f4f6",
        color: "#000000",
        fontFamily:
          "ui-sans-serif,system-ui,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji'",
      }}
    >
      <Section style={{ padding: [40, 0, 40, 0] }}>
        <Column
          style={{ padding: "32px 32px 32px 32px", backgroundColor: "#ffffff", borderRadius: 8 }}
        >
          <Heading
            style={{
              padding: "0px 0px 0px 0px",
              fontSize: 24,
              fontWeight: 700,
              color: "#101828",
              lineHeight: 1.3333333333333333,
            }}
          >
            Welcome, {name}
          </Heading>
          <Text
            style={{
              padding: "16px 0px 16px 0px",
              fontSize: 16,
              color: "#4a5565",
              lineHeight: 1.5,
            }}
          >
            Your workspace is ready.
          </Text>
          <Column style={{ padding: "0px 0px 0px 0px", alignItems: "start" }}>
            <Button
              href={{ href: "https://example.com/start", target: "_blank" }}
              style={{
                backgroundColor: "#4f39f6",
                color: "#ffffff",
                fontSize: 16,
                lineHeight: "120%",
                padding: "12px 20px 12px 20px",
                borderRadius: 6,
              }}
            >
              Open your workspace
            </Button>
          </Column>
        </Column>
      </Section>
    </Email>
  );
}

Welcome.PreviewProps = { name: "Ada" };

Real output: Tailwind classes became typed styles, margins became padding, the container became the email's width. ✓ 100% editable

Checked, not hoped

A migration you can trust is one you can verify.

After converting, the tool renders both versions and compares what a reader gets. Content that differs fails the run with exit code 2. How things look is listed for you to review.

Every word

In the same order, numbers, currency and percentages included. Nothing lost, nothing added.

Every link and image

Each href, each image and its alt text, on both sides.

On phones and wide screens

Again at 375px, and at each wider width where a rule like Tailwind's md: applies.

In dark mode

Where either side has a prefers-color-scheme rule: a logo or text swapped for dark mode.

Props flipped

Rendered with your PreviewProps, then with each boolean prop flipped, so conditional content is checked too.

How it looks

Each word's size, weight, color and place on the page. Differences are listed for you, not hidden.

106 of 106

real React Email templates convert with no content lost. 96 are fully editable in the visual editor, and 98.4% of their content is on average.

git-safe

--write refuses files git can't restore unless you pass --allow-dirty, so every migration can be undone. --strict fails on any difference at all.