SuperAPI AI Directory

react-email

Marketing & growth

View repository

Updated 10-15 20:32

About this skill

React Email 6

Email is not the browser. Flex, Grid, and JS die in Outlook. The final HTML must be tables, inline styles, plain text, and a look that survives Gmail, Outlook Windows, Apple Mail, and mobile.

Unified package react-email (v6). Do not install @react-email/components in a new project. Preview: @react-email/ui.

Stack

npm i react-email @react-email/ui
import {
  Html, Head, Body, Container, Section, Row, Column,
  Text, Heading, Button, Img, Link, Hr, Preview, Font, render,
} from "react-email"
  • render(<Email />) → HTML.
  • render(<Email />, { plainText: true }) → text part. Always send both.
  • Tailwind via the React Email wrapper only if the project already uses it; prefer inline style objects for predictability.

Anatomy

emails/
  _layout.tsx          # Html, Head, Preview, Body, Container, footer
  auth/magic-link.tsx
  transactional/receipt.tsx
  marketing/welcome.tsx

Single shared layout (~600px). Typed props per template. No data fetching inside the email — the caller passes already-resolved data.

Non-negotiable rules

  1. Layout with Container + Section + Row/Column — never flexbox/grid/div loose for structure.
  2. Content width 600px (attribute + style). Outlook ignores max-width on its own.
  3. Styles inline (objects style={{}}). No external stylesheet; <style> only for minimal reset / progressive dark-mode in the Head.
  4. CTA with component Button (VML/MSO included). Do not invent <a> with “CSS button” padding.
  5. Every Img has width, height and alt (empty alt="" only if decorative).
  6. Html lang="pt-BR" (or the actual language). Preview with 40–90 useful characters — not “Do not reply to this email” as the preview.
  7. Part plain text equivalent (same links and facts).
  8. No JavaScript, interactive forms, autoplay video, or webfont as the only font.

Compatibility

Do Avoid
Row / Column (tables + MSO) display: flex, Grid, gap
width={600} in the container Fluid 100% without constraint
Button from the package <div> clickable, border-radius in <a> raw
Hex colors (#111827) oklch(), color-mix, CSS variables as the only source
System fonts + fallback Only "Inter" / hosted font
HTTPS-hosted PNG/JPG SVG as critical content, CID without need
Solid background background-image as information
Margin via Section/Text style Browser margin collapsing

Minimum clients to consider: Gmail (web + app), Outlook Windows (Word engine), Outlook Mac, Apple Mail, iOS Mail, Yahoo. Test for real (Litmus, Email on Acid, or sending to accounts) before calling it done.

Gmail may invert colors in dark mode. Do not rely on light text on a light background "because the media query saves you." Backgrounds and texts with contrast in both modes, or accept inversion with mid-contrast colors.

<Head>
  <meta name="color-scheme" content="light dark" />
  <meta name="supported-color-schemes" content="light dark" />
</Head>

Accessibility

  • Hierarchy: one Heading as="h1" per email; then h2 sections. Do not skip levels.
  • Body ≥ 14px, line-height ~1.5. Links underlined or with obvious text ("Reset password"), never "click here".
  • Contrast WCAG AA (4.5:1 normal text). Brand accent is no excuse for light gray on white.
  • Information not conveyed by color alone (error: text + prefix "Error:", not just red).
  • Logo images: alt with the product name. Tracking pixels: alt="", 1×1, never content.
  • Touch area on the CTA (~44px in height via padding of the Button).
  • Prefer semantic HTML that React Email already outputs (p, h1, a, img) over role ARIA that clients ignore.

Design

Beautiful email is rhythm and restraint, not modern CSS.

  • One brand accent (button + links) with AA contrast. Canvas background #f4f4f5, card #ffffff, text #111827, muted #52525b.
  • Hierarchy: h1 24–28px / body 16px / footer 12–13px muted.
  • Consistent vertical spacing (24/32px between blocks). No sections stuck together.
  • One primary CTA. Secondary becomes a text link.
  • Width 600, internal padding 24–32px. Mobile: columns become a stack via Row/Column (not complex media queries).
  • Small logo at the top (height ~32–40px), not an 800px hero.
  • No gradients, glassmorphism, heavy shadows, excessive rounded borders (Outlook makes them disappear).
  • Footer: who sends it, why, preferences/unsubscribe link if marketing (required in many jurisdictions).

Patterns:

Type Content
Auth Who requested it, action button, validity, notice "if it wasn't you, ignore", raw URL below the button
Transactional Fact (order #, amount), simple table Row/Column, next step
Welcome / e-commerce One message, one CTA, one product — not a 12-block newsletter

Skeleton

import { Html, Head, Preview, Body, Container, Section, Text, Heading, Button, Link, Img, Hr } from "react-email"

const font = "system-ui, -apple-system, 'Segoe UI', sans-serif"

export function MagicLinkEmail({ url, expiresMinutes }: { url: string; expiresMinutes: number }) {
  return (
    <Html lang="pt-BR">
      <Head />
      <Preview>Seu link de acesso vale por {expiresMinutes} minutos</Preview>
      <Body style={{ margin: 0, backgroundColor: "#f4f4f5", fontFamily: font }}>
        <Container style={{ maxWidth: 600, width: "100%", margin: "32px auto", backgroundColor: "#ffffff" }}>
          <Section style={{ padding: "32px 32px 0" }}>
            <Img src="https://example.com/logo.png" width="120" height="32" alt="Acme" />
          </Section>
          <Section style={{ padding: "24px 32px" }}>
            <Heading as="h1" style={{ fontSize: 24, color: "#111827", fontWeight: 600 }}>
              Entrar na Acme
            </Heading>
            <Text style={{ fontSize: 16, lineHeight: "24px", color: "#3f3f46" }}>
              Use o botão abaixo para acessar sua conta. O link expira em {expiresMinutes} minutos.
            </Text>
            <Button href={url} style={{ backgroundColor: "#18181b", color: "#fafafa", padding: "12px 20px" }}>
              Acessar conta
            </Button>
            <Text style={{ fontSize: 13, lineHeight: "20px", color: "#52525b" }}>
              Se o botão não funcionar, copie:{" "}
              <Link href={url} style={{ color: "#18181b", textDecoration: "underline" }}>{url}</Link>
            </Text>
          </Section>
          <Hr />
          <Section style={{ padding: "0 32px 32px" }}>
            <Text style={{ fontSize: 12, color: "#71717a" }}>
              Se você não pediu este e-mail, ignore. Acme nunca pede senha por mensagem.
            </Text>
          </Section>
        </Container>
      </Body>
    </Html>
  )
}

Sending

  • HTML + text/plain. List-Unsubscribe in marketing.
  • Images on a stable HTTPS CDN; do not embed heavy base64.
  • Low total weight (images below ~100KB each when possible).
  • Local preview (react-email / UI) does not replace Outlook Windows.

Anti-patterns

  • Import of @react-email/components in a v6 app
  • Flex/Grid, gap, position: absolute
  • CTA without Button
  • Empty preview or one identical to the subject
  • No plain text
  • White text on a white background "in dark mode"
  • alt missing or alt="image"
  • "Click here"
  • Gradient, video, image map as sole navigation

Integration with other skills

  • Copy: website-copywriting
  • Locale/templates per language: i18n-backend / i18n-frontend
  • Auth flows (server generates the link): authentication-authorization, phoenix, ruby-on-rails
  • Web UI does not apply 1:1 — do not copy Tailwind from an app to email without inlining

Completion criteria

  • Imports of react-email (v6)
  • 600px layout in table components; one Button CTA
  • lang, Preview, alt, AA contrast, body ≥14px
  • HTML + plain text
  • Sober visual (rhythm, one accent, hierarchy) — not a "landing page"
  • At least the Outlook + Gmail path considered (documented or tested)