email templates
email templates
this guide covers creating customizing and using email templates with react email
table of contents
- available templates
- template variables
- creating new templates
- template structure
- styling templates
- previewing templates
- best practices
available templates
| template id | purpose | category | |-------------|---------|----------| | invoice | invoice notification to customers | transactional | | company-invite | team member invitation | notification | | quote-recipient | quote sent to customer | transactional | | quote-author | quote viewed notification | notification | | batch-invoice | multiple invoices notification | transactional | | password-reset | password reset request | transactional | | welcome | new user welcome | notification | | verification | email verification | transactional | | payment-received | payment confirmation | transactional | | payment-reminder | payment reminder | transactional | | custom | custom html (no template) | - |
template variables
templates use {{VARIABLE_NAME}} syntax for dynamic content variables are replaced at send time
invoice template variables
{
AUTHOR_NAME: "Your Company Name",
RECIPIENT_NAME: "Customer Name",
AMOUNT_TOTAL: "€1,234.56",
DUE_DATE: "December 15, 2025",
INVOICE_NUMBER: "INV-2025-001",
INVOICE_URL: "https://app.suitsbooks.com/invoices/...",
LINE1_PRODUCT_NAME: "Product Name",
LINE1_QTY: "2",
LINE1_UNIT_PRICE: "€100.00",
LINE1_SUBTOTAL: "€200.00",
amount_paid: "€0.00",
amount_remaining: "€1,234.56",
FOOTER_TEXT: "Thank you for your business"
}
```typescript
### company invite variables
```typescript
{
TEAM_NAME: "Finance Team",
ORGANIZATION_NAME: "SuitsBooks",
RECIPIENT_NAME: "John Doe",
SENDER_NAME: "Jane Smith",
SENDER_EMAIL: "jane@company.com",
INVITE_LINK: "https://app.suitsbooks.com/invite/...",
AUTHOR_COMPANY_LOGO_URL: "https://...",
RECIPIENT_AVATAR: "https://...",
SENDER_IP: "192.168.1.1",
SENDER_LOCATION: "Amsterdam, Netherlands"
}
```typescript
### welcome template variables
```typescript
{
USER_NAME: "John Doe",
USER_EMAIL: "john@example.com",
LOGIN_URL: "https://app.suitsbooks.com/login",
SUPPORT_EMAIL: "support@suitsbooks.com"
}
```typescript
### password reset variables
```typescript
{
USER_NAME: "John Doe",
RESET_URL: "https://app.suitsbooks.com/reset-password?token=...",
EXPIRY_TIME: "24 hours",
SUPPORT_EMAIL: "support@suitsbooks.com",
REQUEST_IP: "192.168.1.1",
REQUEST_LOCATION: "Amsterdam, Netherlands"
}
```typescript
### verification template variables
```typescript
{
USER_NAME: "John Doe",
VERIFICATION_URL: "https://app.suitsbooks.com/verify?token=...",
VERIFICATION_CODE: "123456",
EXPIRY_TIME: "24 hours",
SUPPORT_EMAIL: "support@suitsbooks.com"
}
```typescript
### payment reminder variables
```typescript
{
RECIPIENT_NAME: "Customer Name",
AUTHOR_NAME: "Your Company",
INVOICE_NUMBER: "INV-2025-001",
AMOUNT_DUE: "€1,234.56",
DUE_DATE: "December 1, 2025",
DAYS_OVERDUE: "14",
INVOICE_URL: "https://app.suitsbooks.com/invoices/...",
SUPPORT_EMAIL: "support@suitsbooks.com"
}
```typescript
### payment received variables
```typescript
{
RECIPIENT_NAME: "Customer Name",
AUTHOR_NAME: "Your Company",
INVOICE_NUMBER: "INV-2025-001",
AMOUNT_PAID: "€1,234.56",
PAYMENT_DATE: "December 1, 2025",
PAYMENT_METHOD: "Bank Transfer",
RECEIPT_URL: "https://app.suitsbooks.com/receipts/...",
SUPPORT_EMAIL: "support@suitsbooks.com"
}
```typescript
---
## creating new templates
### step 1: create the template file
create a new file in emails/templates/
```tsx
// emails/templates/order-confirmation.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import React from "react";
/**
* order confirmation email template
*
* variables:
* - {{CUSTOMER_NAME}} - customers name
* - {{ORDER_NUMBER}} - order number
* - {{ORDER_TOTAL}} - total amount
* - {{ORDER_URL}} - url to view order
*/
export default function OrderConfirmationEmail() {
return (
<Html>
<Head>
<meta name="color-scheme" content="light" />
<meta name="supported-color-schemes" content="light" />
</Head>
<Preview>Your order {"{{ORDER_NUMBER}}"} has been confirmed</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#4085e6",
},
},
},
}}
>
<Body className="m-0 w-full bg-[#fafafa] font-sans">
<Container className="mx-auto max-w-[600px] px-4 py-8">
<Section
className="rounded-lg bg-white p-8 shadow-sm"
style={{ border: "1px solid #e5e5e5" }}
>
<Heading className="m-0 mb-4 text-2xl font-semibold text-gray-800">
order confirmed
</Heading>
<Text className="mb-4 text-base text-gray-600">
hi {"{{CUSTOMER_NAME}}"},
</Text>
<Text className="mb-4 text-base text-gray-600">
thank you for your order your order number is{" "}
<strong>{"{{ORDER_NUMBER}}"}</strong>.
</Text>
<Text className="mb-4 text-base text-gray-600">
order total: <strong>{"{{ORDER_TOTAL}}"}</strong>
</Text>
<Section className="my-6 text-center">
<Button
href={"{{ORDER_URL}}"}
className="rounded-sm bg-brand px-6 py-3 text-base font-semibold text-white no-underline"
>
view order
</Button>
</Section>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
}
```typescript
### step 2: register the template
add the template to emails/templates/indexts
```tsx
import OrderConfirmationEmail from "./order-confirmation";
export const EMAIL_TEMPLATES: Record<EmailTemplateId, React.ComponentType> = {
// ... existing templates
"order-confirmation": OrderConfirmationEmail,
};
```typescript
### step 3: add type definition
update lib/email/typests
```typescript
export type EmailTemplateId =
| "invoice"
| "company-invite"
// ... existing types
| "order-confirmation"; // add new template
```typescript
### step 4: add configuration
update lib/email/configts
```typescript
export const TEMPLATE_CONFIG: Record<EmailTemplateId, {...}> = {
// ... existing configs
"order-confirmation": {
defaultSubject: "Order {{ORDER_NUMBER}} confirmed",
requiresAuth: true,
category: "transactional",
},
};
```typescript
### step 5: use the template
```tsx
await sendEmail({
to: "customer@example.com",
subject: "Order #12345 confirmed",
template_id: "order-confirmation",
template_data: {
CUSTOMER_NAME: "John Doe",
ORDER_NUMBER: "12345",
ORDER_TOTAL: "€99.99",
ORDER_URL: "https://app.suitsbooks.com/orders/12345",
},
});
```typescript
---
## template structure
### recommended structure
```tsx
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Link,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import React from "react";
export default function MyTemplate() {
return (
<Html>
{/* 1. head - meta tags */}
<Head>
<meta name="color-scheme" content="light" />
<meta name="supported-color-schemes" content="light" />
</Head>
{/* 2. preview - shows in email client preview */}
<Preview>Preview text shown in inbox</Preview>
{/* 3. tailwind - styling configuration */}
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#4085e6",
},
},
},
}}
>
{/* 4. body - main wrapper */}
<Body className="m-0 w-full bg-[#fafafa] font-sans">
{/* 5. container - content width */}
<Container className="mx-auto max-w-[600px] px-4 py-8">
{/* 6. header section */}
<Section className="mb-6 text-center">
<Text className="text-2xl font-bold text-brand">SuitsBooks</Text>
</Section>
{/* 7. main content card */}
<Section
className="rounded-lg bg-white p-8 shadow-sm"
style={{ border: "1px solid #e5e5e5" }}
>
<Heading>title</Heading>
<Text>content...</Text>
<Button href="{{CTA_URL}}">call to action</Button>
</Section>
{/* 8. footer */}
<Section className="mt-8 text-center">
<Text className="text-xs text-gray-400">
© {new Date().getFullYear()} SuitsBooks
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
}
```typescript
### using the base layout
for consistency you can use the base layout
```tsx
import { BaseLayout } from "./base-layout";
export default function MyTemplate() {
return (
<BaseLayout preview="email preview text" footerText="custom footer">
{/* your content here */}
</BaseLayout>
);
}
```typescript
---
## styling templates
### tailwind css
templates use tailwind css via the Tailwind component
```tsx
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#4085e6",
"brand-dark": "#2563eb",
},
},
},
}}
>
<Body className="bg-gray-100">
<Text className="text-brand font-bold">styled text</Text>
</Body>
</Tailwind>
```typescript
### inline styles
for complex styles or email client compatibility
```tsx
<Section
className="rounded-lg bg-white p-8"
style={{
border: "1px solid #e5e5e5",
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
}}
>
```typescript
### email-safe styles
not all css properties work in email clients safe properties include
- background-color color
- font-family font-size font-weight
- padding margin
- border border-radius
- width max-width
- text-align
avoid
- flexbox (limited support)
- grid (poor support)
- position (poor support)
- box-shadow (limited support)
---
## previewing templates
### using react email dev server
```bash
pnpm email
```typescript
this starts the react email dev server at http://localhost:3040 where you can
- preview all templates
- test with different variables
- view html source
- send test emails
### manual preview
```tsx
import { render } from "@react-email/components";
import WelcomeEmail from "@/emails/templates/welcome";
// render to html string
const html = await render(WelcomeEmail());
console.log(html);
```typescript
---
## best practices
### 1 use semantic variable names
```typescript
// good
{
CUSTOMER_NAME: "John",
ORDER_TOTAL: "€99.99",
DELIVERY_DATE: "Dec 15, 2025"
}
// bad
{
name: "John",
total: "€99.99",
date: "Dec 15, 2025"
}
```typescript
### 2 provide fallback content
```tsx
<Text>
hi {"{{CUSTOMER_NAME}}"}, {/* if not provided shows literally */}
</Text>
// better approach in your sending code
template_data: {
CUSTOMER_NAME: customer.name || "there",
}
```typescript
### 3 keep templates simple
- avoid complex layouts
- use tables for alignment (better email client support)
- test in multiple email clients
### 4 include preview text
```tsx
<Preview>
your invoice for €1,234.56 is ready to view
</Preview>
```typescript
### 5 add alt text for images
```tsx
<img
src="{{LOGO_URL}}"
alt="Company Logo"
width={120}
height={40}
/>
```typescript
### 6 use absolute urls
```tsx
// good
<Button href="https://app.suitsbooks.com/invoice/123">
view invoice
</Button>
// bad
<Button href="/invoice/123">
view invoice
</Button>
```typescript
### 7 document your variables
```tsx
/**
* invoice email template
*
* required variables:
* - {{RECIPIENT_NAME}} - customers full name
* - {{INVOICE_NUMBER}} - invoice identifier
* - {{AMOUNT_TOTAL}} - formatted total amount
* - {{DUE_DATE}} - formatted due date
* - {{INVOICE_URL}} - full url to view invoice
*
* optional variables:
* - {{AUTHOR_NAME}} - sender company name (default: from config)
*/
export default function InvoiceEmail() {
```typescript
### 8 test email client compatibility
test your templates in
- gmail (web ios android)
- outlook (web desktop ios)
- apple mail
- yahoo mail
- mobile clients
use tools like litmus or email on acid for comprehensive testing
---
## troubleshooting
### variables not replacing
ensure you are using the correct syntax
```tsx
// correct
{"{{VARIABLE_NAME}}"}
// wrong (missing braces in jsx)
{`{{VARIABLE_NAME}}`}
styles not applying
1 check email client support 2 use inline styles for critical styles 3 avoid shorthand properties
template not found
1 ensure template is exported from emails/templates/indexts 2 check template id matches exactly 3 verify type is added to EmailTemplateId
preview not working
# clear cache and restart
rm -rf node_modules/.cache
pnpm email
```typescript