Customizing Email Templates
All production email templates live in packages/backend/convex/email/templates/. They are React components rendered to HTML using @react-email/render.
All production email templates live in
packages/backend/convex/email/templates/. They are React components rendered
to HTML using @react-email/render.
Template Pattern
Auth and security templates receive appName from the required backend
APP_NAME environment variable. Keep product names parameterized instead of
hardcoding starter branding in template copy.
Every template follows a consistent pattern:
// 1. React Email imports
import { Body, Button, Container, Html, Text } from "@react-email/components";
import { render } from "@react-email/render";
// 2. Props interface
interface MyEmailOptions {
name: string;
actionUrl: string;
}
// 3. Inline styles (email clients don't support CSS files)
const bodyStyle = {
backgroundColor: "#ffffff",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
// 4. React component
export function MyEmail({ name, actionUrl }: MyEmailOptions) {
return (
<Html>
<Body style={bodyStyle}>
<Container>
<Text>Hello {name}!</Text>
<Button href={actionUrl}>Take action</Button>
</Container>
</Body>
</Html>
);
}
// 5. HTML render function
export function renderMyEmail(args: MyEmailOptions) {
return render(<MyEmail {...args} />);
}
// 6. Plain text render function
export function renderMyEmailText({ name, actionUrl }: MyEmailOptions) {
return `Hello ${name}!\n\nTake action: ${actionUrl}`;
}Styling
Email templates use inline styles, not CSS classes. Email clients have limited CSS support, so inline styles ensure consistent rendering.
The existing templates share common style objects:
const bodyStyle = {
backgroundColor: "#ffffff",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
const textStyle = {
color: "#111827",
fontSize: "16px",
lineHeight: "26px",
};
const buttonStyle = {
backgroundColor: "#111827",
borderRadius: "6px",
color: "#ffffff",
fontSize: "16px",
fontWeight: "600",
padding: "12px 18px",
};
const footerStyle = {
color: "#6b7280",
fontSize: "13px",
};Creating a New Template
-
Create the template file in
packages/backend/convex/email/templates/:// myNewEmail.tsx export function MyNewEmail({ ... }) { ... } export function renderMyNewEmail(args) { return render(<MyNewEmail {...args} />); } export function renderMyNewEmailText(args) { return "..."; } -
Send it from a Convex function using the
sendEmailfacade:import { sendEmail } from "../email"; import { renderMyNewEmail, renderMyNewEmailText } from "../email/templates/myNewEmail"; const html = await renderMyNewEmail({ ... }); const text = renderMyNewEmailText({ ... }); await sendEmail(ctx, { to: "user@example.com", subject: "Subject line", html, text, }); -
Always include plain text — the
textfield provides a fallback for email clients that don't render HTML.
Branding
To brand all emails:
- Replace "Leave Localhost" references in template copy and preview text.
- Update the button and body style colors to match your brand.
- Add your logo using the
Imgcomponent (host the image on your domain or a CDN).
Previewing Changes
Use the React Email preview server for rapid iteration:
bun --cwd packages/email devEach file under packages/email/emails/ is a thin wrapper that imports a backend
template and passes sample props — so the preview shows exactly what ships. When
you add a new backend template, add a matching wrapper so it appears at :3003:
// packages/email/emails/my-new-email.tsx
import { MyNewEmail } from "@leavelocalhost/backend/convex/email/templates/myNewEmail";
export default function MyNewEmailPreview() {
return <MyNewEmail name="Sample User" actionUrl="https://app.example.com/action" />;
}Testing
After modifying templates, send a test email to yourself using the Resend dashboard or a Convex function to verify rendering across email clients (Gmail, Outlook, Apple Mail).
Next Reads
- React Email — the preview workspace.
- Resend — sending configuration.