Live demo — shared accounts, sample data, reset regularly. Don't enter anything real.Get the source

Recipe: Add a transactional email

Recipe: Add a transactional email

  1. Create the template in emails/your-email.tsx:

    import { Text } from "@react-email/components";
    import { EmailButton, EmailLayout } from "./components/layout";
     
    export function YourEmail({ someProp }: { someProp: string }) {
      return (
        <EmailLayout preview="Short inbox preview text" heading="Your heading">
          <Text style={{ fontSize: "14px", color: "#3f3f46", lineHeight: "22px" }}>
            Body copy. {someProp}
          </Text>
          <EmailButton href="https://example.com">Call to action</EmailButton>
        </EmailLayout>
      );
    }
     
    export default YourEmail;
     
    // Shown by `npm run email:dev`'s preview server.
    YourEmail.PreviewProps = { someProp: "example value" };

    Use EmailLayout/EmailButton from emails/components/layout.tsx for consistent branding — plain inline styles only, email clients don't run Tailwind.

  2. Preview it: npm run email:dev, open the printed URL, find your file in the sidebar. Edits hot-reload.

  3. Send it from server code:

    import { sendEmail } from "@/lib/email";
    import { YourEmail } from "@/emails/your-email";
     
    await sendEmail({
      to: user.email,
      subject: "Your subject line",
      react: YourEmail({ someProp: "value" }),
      // critical: true only if the user is blocked without this email
      // (verification, password reset) — otherwise leave it best-effort.
    });
  4. Verify: without RESEND_API_KEY set, confirm you see [email] RESEND_API_KEY not set — skipping "Your subject line" to ... in the server log rather than an error. With a real key, check the Resend dashboard for delivery.

To remove an email, delete its file from emails/ and the sendEmail call site — there's no registry to update elsewhere.