KitRocket

Custom Email Template

Create beautiful transactional email templates with React Email and send them through Resend.

KitRocket uses React Email for templates and Resend for delivery. This guide shows you how to create a custom email template from scratch.

Step 1: Create the template component

Create a new file in the templates directory:

import {
  Html,
  Head,
  Body,
  Container,
  Section,
  Text,
  Button,
  Heading,
  Hr,
  Img,
} from "@react-email/components";

interface InviteEmailProps {
  inviterName: string;
  teamName: string;
  inviteUrl: string;
}

export function InviteEmail({ inviterName, teamName, inviteUrl }: InviteEmailProps) {
  return (
    <Html>
      <Head />
      <Body style={body}>
        <Container style={container}>
          <Img
            src={`${process.env.BETTER_AUTH_URL}/logo.png`}
            width="40"
            height="40"
            alt="Logo"
          />
          <Heading style={heading}>You're invited</Heading>
          <Text style={text}>
            {inviterName} has invited you to join <strong>{teamName}</strong>.
          </Text>
          <Section style={buttonContainer}>
            <Button style={button} href={inviteUrl}>
              Accept Invitation
            </Button>
          </Section>
          <Hr style={hr} />
          <Text style={footer}>
            If you didn't expect this invitation, you can safely ignore this email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

// Styles
const body = {
  backgroundColor: "#f6f9fc",
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};

const container = {
  backgroundColor: "#ffffff",
  margin: "0 auto",
  padding: "40px 20px",
  maxWidth: "560px",
  borderRadius: "8px",
};

const heading = {
  fontSize: "24px",
  fontWeight: "bold" as const,
  color: "#1a1a1a",
  marginBottom: "16px",
};

const text = {
  fontSize: "16px",
  lineHeight: "24px",
  color: "#4a4a4a",
};

const buttonContainer = {
  textAlign: "center" as const,
  marginTop: "24px",
  marginBottom: "24px",
};

const button = {
  backgroundColor: "#000000",
  color: "#ffffff",
  fontSize: "16px",
  fontWeight: "bold" as const,
  padding: "12px 24px",
  borderRadius: "6px",
  textDecoration: "none",
};

const hr = {
  borderColor: "#e6e6e6",
  marginTop: "24px",
  marginBottom: "24px",
};

const footer = {
  fontSize: "14px",
  color: "#999999",
};

Step 2: Add a send function

Add a function to send the new email:

import { Resend } from "resend";
import { InviteEmail } from "./templates/invite";

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

// ... existing send functions

export async function sendInviteEmail(
  to: string,
  props: { inviterName: string; teamName: string; inviteUrl: string }
) {
  const { data, error } = await resend.emails.send({
    from: process.env.EMAIL_FROM!,
    to,
    subject: `${props.inviterName} invited you to join ${props.teamName}`,
    react: InviteEmail(props),
  });

  if (error) {
    console.error("Failed to send invite email:", error);
    throw new Error("Email delivery failed");
  }

  return data;
}

Step 3: Use in an API route

Call the send function from any API route:

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth/server";
import { sendInviteEmail } from "@/lib/email/send";

export async function POST(request: NextRequest) {
  const session = await auth.api.getSession({
    headers: request.headers,
  });

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { email, teamName } = await request.json();

  const inviteUrl = `${process.env.BETTER_AUTH_URL}/invite/accept?token=...`;

  await sendInviteEmail(email, {
    inviterName: session.user.name,
    teamName,
    inviteUrl,
  });

  return NextResponse.json({ sent: true });
}

Step 4: Preview locally

React Email includes a dev server for previewing templates:

pnpm email:dev

This opens a browser at localhost:3001 where you can see all your templates with hot reload. Change styles and content, and see updates instantly.

If the script isn't set up yet, add it:

{
  "scripts": {
    "email:dev": "email dev --dir src/lib/email/templates"
  }
}

Template tips

Use inline styles

Email clients don't support CSS classes reliably. Always use inline styles or the style prop.

Test across clients

Email rendering varies wildly. Test in:

  • Gmail (web and mobile)
  • Apple Mail
  • Outlook (the hardest)
  • Yahoo Mail

Use Litmus or Email on Acid for cross-client testing.

Keep it simple

Stick to single-column layouts. Avoid complex CSS (no flexbox, no grid). Tables are still the most reliable layout method for emails, and React Email handles this under the hood.

Add a plain text fallback

await resend.emails.send({
  from: process.env.EMAIL_FROM!,
  to,
  subject: "You're invited",
  react: InviteEmail(props),
  text: `${props.inviterName} invited you to join ${props.teamName}. Accept here: ${props.inviteUrl}`,
});

Available React Email components

ComponentPurpose
Html, Head, BodyDocument structure
ContainerCentered content wrapper
Section, Row, ColumnLayout
TextParagraphs
HeadingHeaders (h1-h6)
ButtonCTA buttons
LinkHyperlinks
ImgImages
HrHorizontal rule
PreviewEmail preview text (shows in inbox)

Next steps

On this page