CtrlK
BlogDocsLog inGet started
Tessl Logo

creativecodeology/react-email

Build email templates with @react-email/components (v1.0+) and render them server-side for Resend or any email provider. Use this skill whenever creating, editing, debugging, or reviewing React Email templates — even for simple tasks like 'add a button to the welcome email' or 'create a password reset email'. The skill contains critical email client compatibility rules (no flexbox, no rem units, pixelBasedPreset requirement, Outlook conditional comments) that prevent broken rendering in production. Triggers on: React Email components, email templates, @react-email/components imports, Tailwind in emails, email layout, email styling, Html/Head/Body/Container/Section/Row/Column/Text/Button/Img/Link/Hr/Preview components, render() from @react-email/render, PreviewProps, pixelBasedPreset, email client compatibility, Outlook rendering, Gmail rendering.

95

Quality

95%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

patterns.mdreferences/

React Email Template Patterns

Reusable patterns for common email template types. All examples use Tailwind with pixelBasedPreset.

Standard Email Structure

Every template follows this hierarchy:

Html (lang)
  Tailwind (config with pixelBasedPreset)
    Head (+ Font if needed)
    Preview (inbox preview text)
    Body (background color, font)
      Container (centered, max-width)
        Section (visual groupings)
          Content components

Two-Column Layout

Use Row and Column — never flexbox. Column widths must total the container width.

<Section>
  <Row>
    <Column className="w-1/2 p-4 align-top">
      <Img src="https://..." alt="Feature 1" width="240" height="160" className="w-full h-auto" />
      <Heading as="h3" className="text-lg font-bold m-0 mt-4">Feature One</Heading>
      <Text className="text-sm text-gray-600 m-0 mt-2">Description text.</Text>
    </Column>
    <Column className="w-1/2 p-4 align-top">
      <Img src="https://..." alt="Feature 2" width="240" height="160" className="w-full h-auto" />
      <Heading as="h3" className="text-lg font-bold m-0 mt-4">Feature Two</Heading>
      <Text className="text-sm text-gray-600 m-0 mt-2">Description text.</Text>
    </Column>
  </Row>
</Section>

Three-Column Layout

<Row>
  <Column className="w-1/3 p-2 align-top text-center">
    <Img src="https://..." alt="Icon 1" width="48" height="48" className="mx-auto" />
    <Text className="text-sm font-bold m-0 mt-2">Step 1</Text>
  </Column>
  <Column className="w-1/3 p-2 align-top text-center">
    <Img src="https://..." alt="Icon 2" width="48" height="48" className="mx-auto" />
    <Text className="text-sm font-bold m-0 mt-2">Step 2</Text>
  </Column>
  <Column className="w-1/3 p-2 align-top text-center">
    <Img src="https://..." alt="Icon 3" width="48" height="48" className="mx-auto" />
    <Text className="text-sm font-bold m-0 mt-2">Step 3</Text>
  </Column>
</Row>

Header with Logo

<Section className="py-6 text-center">
  <Img
    src="https://cdn.example.com/logo.png"
    alt="Company Name"
    width="120"
    height="40"
    className="mx-auto"
  />
</Section>

CTA Button

Full-width, prominent:

<Button
  href={actionUrl}
  className="bg-brand text-white text-base font-bold no-underline text-center block py-4 px-6 rounded-md my-6 box-border"
>
  Verify Your Email
</Button>

Compact, inline:

<Button
  href={actionUrl}
  className="bg-brand text-white text-sm font-semibold no-underline text-center py-2 px-4 rounded"
>
  View Details
</Button>

Info Card / Callout

<Section className="bg-blue-50 border border-blue-200 rounded-lg p-6 my-6">
  <Text className="text-sm font-bold text-blue-800 m-0 mb-2">
    Important Notice
  </Text>
  <Text className="text-sm text-blue-700 m-0">
    Your account will be upgraded automatically on March 1st.
  </Text>
</Section>

Order Receipt / Line Items

<Section className="my-6">
  <Heading as="h2" className="text-lg font-bold m-0 mb-4">Order Summary</Heading>

  {items.map((item) => (
    <Row key={item.id} className="border-b border-gray-200">
      <Column className="w-3/4 py-3">
        <Text className="text-sm font-medium m-0">{item.name}</Text>
        <Text className="text-xs text-gray-500 m-0 mt-1">Qty: {item.qty}</Text>
      </Column>
      <Column className="w-1/4 py-3 text-right">
        <Text className="text-sm font-medium m-0">${item.price}</Text>
      </Column>
    </Row>
  ))}

  <Hr className="border-gray-300 my-4" />

  <Row>
    <Column className="w-3/4">
      <Text className="text-base font-bold m-0">Total</Text>
    </Column>
    <Column className="w-1/4 text-right">
      <Text className="text-base font-bold m-0">${total}</Text>
    </Column>
  </Row>
</Section>

Key-Value Details

For shipping info, account details, etc.:

<Section className="bg-gray-50 rounded-lg p-6 my-6">
  <Row className="mb-2">
    <Column className="w-2/5">
      <Text className="text-sm text-gray-500 m-0">Order Number</Text>
    </Column>
    <Column className="w-3/5">
      <Text className="text-sm font-medium m-0">#ORD-2024-1234</Text>
    </Column>
  </Row>
  <Row className="mb-2">
    <Column className="w-2/5">
      <Text className="text-sm text-gray-500 m-0">Ship To</Text>
    </Column>
    <Column className="w-3/5">
      <Text className="text-sm font-medium m-0">123 Main St, City, ST 12345</Text>
    </Column>
  </Row>
  <Row>
    <Column className="w-2/5">
      <Text className="text-sm text-gray-500 m-0">Estimated Delivery</Text>
    </Column>
    <Column className="w-3/5">
      <Text className="text-sm font-medium m-0">March 20-22, 2025</Text>
    </Column>
  </Row>
</Section>

Footer with Unsubscribe

<Section className="mt-8 pt-6 border-t border-gray-200 text-center">
  <Text className="text-xs text-gray-400 m-0 mb-2">
    Company Name, Inc. | 123 Business Ave, City, ST 12345
  </Text>
  <Text className="text-xs text-gray-400 m-0">
    <Link href={unsubscribeUrl} className="text-gray-400 underline">
      Unsubscribe
    </Link>
    {" | "}
    <Link href={preferencesUrl} className="text-gray-400 underline">
      Email Preferences
    </Link>
    {" | "}
    <Link href={privacyUrl} className="text-gray-400 underline">
      Privacy Policy
    </Link>
  </Text>
</Section>

Footer with Social Links

<Section className="mt-8 text-center">
  <Row className="mb-4">
    <Column className="text-center">
      <Link href="https://twitter.com/company" className="mx-2">
        <Img src="https://cdn.example.com/twitter.png" alt="Twitter" width="24" height="24" className="inline" />
      </Link>
      <Link href="https://linkedin.com/company/company" className="mx-2">
        <Img src="https://cdn.example.com/linkedin.png" alt="LinkedIn" width="24" height="24" className="inline" />
      </Link>
      <Link href="https://github.com/company" className="mx-2">
        <Img src="https://cdn.example.com/github.png" alt="GitHub" width="24" height="24" className="inline" />
      </Link>
    </Column>
  </Row>
</Section>

Shared Components

Extract repeated sections into components:

Header Component

// emails/shared/header.tsx
import { Section, Img, Hr } from "@react-email/components";

interface HeaderProps {
  logoUrl: string;
  companyName: string;
}

export function Header({ logoUrl, companyName }: HeaderProps) {
  return (
    <Section className="py-6 text-center">
      <Img
        src={logoUrl}
        alt={companyName}
        width="120"
        height="40"
        className="mx-auto"
      />
      <Hr className="border-gray-200 mt-6 mb-0" />
    </Section>
  );
}

Footer Component

// emails/shared/footer.tsx
import { Section, Text, Link } from "@react-email/components";

interface FooterProps {
  companyName: string;
  address: string;
  unsubscribeUrl?: string;
}

export function Footer({ companyName, address, unsubscribeUrl }: FooterProps) {
  return (
    <Section className="mt-8 pt-6 border-t border-gray-200 text-center">
      <Text className="text-xs text-gray-400 m-0 mb-2">
        {companyName} | {address}
      </Text>
      {unsubscribeUrl && (
        <Text className="text-xs text-gray-400 m-0">
          <Link href={unsubscribeUrl} className="text-gray-400 underline">
            Unsubscribe
          </Link>
        </Text>
      )}
    </Section>
  );
}

Using Shared Components

import { Header } from "./shared/header";
import { Footer } from "./shared/footer";

export default function WelcomeEmail({ name }: WelcomeEmailProps) {
  return (
    <Html lang="en">
      <Tailwind config={{ presets: [pixelBasedPreset] }}>
        <Head />
        <Preview>Welcome!</Preview>
        <Body className="bg-gray-100 font-sans">
          <Container className="mx-auto py-10 px-5 max-w-xl">
            <Section className="bg-white rounded-lg overflow-hidden">
              <Header logoUrl="https://..." companyName="Acme" />
              {/* Email body content */}
              <Footer
                companyName="Acme, Inc."
                address="123 Main St"
                unsubscribeUrl="https://..."
              />
            </Section>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  );
}

Conditional Content

Use standard React conditionals for dynamic content:

{order.status === "shipped" ? (
  <Text>Your order is on its way!</Text>
) : (
  <Text>Your order is being prepared.</Text>
)}

{showUpgradePrompt && (
  <Section className="bg-yellow-50 p-4 rounded-lg my-4">
    <Text className="text-sm m-0">Upgrade to Pro for more features.</Text>
  </Section>
)}

Rendering and Sending

With Resend (react prop)

import { Resend } from "resend";
import WelcomeEmail from "./emails/welcome";

const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "hello@company.com",
  to: user.email,
  subject: "Welcome to Company",
  react: <WelcomeEmail name={user.name} actionUrl={verifyUrl} />,
});

Pre-rendered HTML

import { render } from "@react-email/render";
import WelcomeEmail from "./emails/welcome";

const html = await render(
  <WelcomeEmail name={user.name} actionUrl={verifyUrl} />
);

// Use html string with any email provider
await sendEmail({ to: user.email, subject: "Welcome", html });

Plain Text Version

import { render } from "@react-email/render";

const text = await render(
  <WelcomeEmail name={user.name} actionUrl={verifyUrl} />,
  { plainText: true }
);

Including a plain text version alongside HTML improves deliverability — spam filters favor multipart emails.

references

components.md

patterns.md

styling.md

SKILL.md

tile.json