Email Templates Skill
Transactional email design using React Email and integration with email providers like Resend or AWS SES.
Core Principles
-
Responsive Design: Emails work on all devices
-
Accessibility: Proper semantic HTML and alt text
-
Plain Text Fallback: Always include plain text version
-
Deliverability: Follow best practices to avoid spam
-
Testing: Preview emails before sending
Installation
React Email
npm install react-email @react-email/components
Email provider (choose one)
npm install resend # Recommended npm install @aws-sdk/client-ses
React Email Setup
- Create Email Templates Directory
emails/ ├── welcome.tsx ├── reset-password.tsx ├── verify-email.tsx ├── receipt.tsx └── components/ ├── layout.tsx └── button.tsx
- Base Layout Component
// emails/components/layout.tsx import { Html, Head, Preview, Body, Container, Section, Text, } from "@react-email/components"
interface LayoutProps { preview: string children: React.ReactNode }
export function EmailLayout({ preview, children }: LayoutProps) { return ( <Html> <Head /> <Preview>{preview}</Preview> <Body style={main}> <Container style={container}> {children} <Section style={footer}> <Text style={footerText}> © 2026 Your Company. All rights reserved. </Text> </Section> </Container> </Body> </Html> ) }
const main = { backgroundColor: "#f6f9fc", fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif', }
const container = { backgroundColor: "#ffffff", margin: "0 auto", padding: "20px 0 48px", marginBottom: "64px", }
const footer = { color: "#8898aa", fontSize: "12px", lineHeight: "16px", marginTop: "32px", }
const footerText = { margin: "0", textAlign: "center" as const, }
- Reusable Button Component
// emails/components/button.tsx import { Button as ReactEmailButton } from "@react-email/components"
interface ButtonProps { href: string children: React.ReactNode }
export function Button({ href, children }: ButtonProps) { return ( <ReactEmailButton href={href} style={button}> {children} </ReactEmailButton> ) }
const button = { backgroundColor: "#5469d4", borderRadius: "4px", color: "#fff", fontSize: "16px", fontWeight: "bold", textDecoration: "none", textAlign: "center" as const, display: "block", padding: "12px 24px", }
Email Templates
Welcome Email
// emails/welcome.tsx import { Section, Heading, Text, } from "@react-email/components" import { EmailLayout } from "./components/layout" import { Button } from "./components/button"
interface WelcomeEmailProps { name: string dashboardUrl: string }
export default function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
return (
<EmailLayout preview={Welcome to our platform, ${name}!}>
<Section style={content}>
<Heading style={heading}>Welcome aboard, {name}!</Heading>
<Text style={paragraph}>
We're excited to have you on board. Your account has been successfully created.
</Text>
<Text style={paragraph}>
Here's what you can do next:
</Text>
<ul style={list}>
<li>Complete your profile</li>
<li>Explore the dashboard</li>
<li>Invite your team members</li>
</ul>
<Button href={dashboardUrl}>
Go to Dashboard
</Button>
<Text style={paragraph}>
If you have any questions, feel free to reply to this email.
</Text>
</Section>
</EmailLayout>
) }
const content = { padding: "24px", }
const heading = { fontSize: "24px", lineHeight: "32px", fontWeight: "700", margin: "0 0 16px", }
const paragraph = { fontSize: "16px", lineHeight: "24px", margin: "0 0 16px", }
const list = { fontSize: "16px", lineHeight: "24px", marginBottom: "16px", }
Password Reset Email
// emails/reset-password.tsx import { Section, Heading, Text, Hr, } from "@react-email/components" import { EmailLayout } from "./components/layout" import { Button } from "./components/button"
interface ResetPasswordEmailProps { name: string resetUrl: string expiresIn: string }
export default function ResetPasswordEmail({ name, resetUrl, expiresIn, }: ResetPasswordEmailProps) { return ( <EmailLayout preview="Reset your password"> <Section style={content}> <Heading style={heading}>Reset your password</Heading>
<Text style={paragraph}>Hi {name},</Text>
<Text style={paragraph}>
We received a request to reset your password. Click the button below to choose a new password.
</Text>
<Button href={resetUrl}>
Reset Password
</Button>
<Text style={paragraph}>
This link will expire in {expiresIn}.
</Text>
<Hr style={hr} />
<Text style={small}>
If you didn't request a password reset, you can safely ignore this email.
</Text>
</Section>
</EmailLayout>
) }
const content = { padding: "24px", }
const heading = { fontSize: "24px", lineHeight: "32px", fontWeight: "700", margin: "0 0 16px", }
const paragraph = { fontSize: "16px", lineHeight: "24px", margin: "0 0 16px", }
const hr = { borderColor: "#e6ebf1", margin: "20px 0", }
const small = { fontSize: "14px", lineHeight: "20px", color: "#6b7280", }
Receipt Email
// emails/receipt.tsx import { Section, Heading, Text, Hr, Row, Column, } from "@react-email/components" import { EmailLayout } from "./components/layout"
interface ReceiptEmailProps { name: string orderId: string date: string items: Array<{ name: string quantity: number price: number }> total: number }
export default function ReceiptEmail({
name,
orderId,
date,
items,
total,
}: ReceiptEmailProps) {
return (
<EmailLayout preview={Receipt for order ${orderId}}>
<Section style={content}>
<Heading style={heading}>Thanks for your order!</Heading>
<Text style={paragraph}>Hi {name},</Text>
<Text style={paragraph}>
Your order has been confirmed. Here's a summary:
</Text>
<Section style={orderInfo}>
<Text style={label}>Order ID: <strong>{orderId}</strong></Text>
<Text style={label}>Date: {date}</Text>
</Section>
<Hr style={hr} />
{items.map((item, index) => (
<Row key={index} style={itemRow}>
<Column>
<Text style={itemName}>{item.name}</Text>
<Text style={itemQuantity}>Qty: {item.quantity}</Text>
</Column>
<Column style={itemPriceColumn}>
<Text style={itemPrice}>${item.price.toFixed(2)}</Text>
</Column>
</Row>
))}
<Hr style={hr} />
<Row style={totalRow}>
<Column>
<Text style={totalLabel}>Total</Text>
</Column>
<Column style={totalPriceColumn}>
<Text style={totalPrice}>${total.toFixed(2)}</Text>
</Column>
</Row>
</Section>
</EmailLayout>
) }
const content = { padding: "24px" } const heading = { fontSize: "24px", fontWeight: "700", margin: "0 0 16px" } const paragraph = { fontSize: "16px", margin: "0 0 16px" } const orderInfo = { backgroundColor: "#f6f9fc", padding: "16px", borderRadius: "4px", marginBottom: "16px" } const label = { fontSize: "14px", margin: "4px 0" } const hr = { borderColor: "#e6ebf1", margin: "20px 0" } const itemRow = { marginBottom: "12px" } const itemName = { fontSize: "16px", margin: "0" } const itemQuantity = { fontSize: "14px", color: "#6b7280", margin: "4px 0 0" } const itemPriceColumn = { textAlign: "right" as const } const itemPrice = { fontSize: "16px", margin: "0" } const totalRow = { marginTop: "8px" } const totalLabel = { fontSize: "18px", fontWeight: "700", margin: "0" } const totalPriceColumn = { textAlign: "right" as const } const totalPrice = { fontSize: "18px", fontWeight: "700", margin: "0" }
Sending Emails
Using Resend (Recommended)
// lib/email.server.ts import { Resend } from "resend" import { render } from "@react-email/render" import WelcomeEmail from "~/emails/welcome"
const resend = new Resend(process.env.RESEND_API_KEY)
export async function sendWelcomeEmail(to: string, name: string, dashboardUrl: string) { const html = render(<WelcomeEmail name={name} dashboardUrl={dashboardUrl} />)
await resend.emails.send({ from: "noreply@yourapp.com", to, subject: "Welcome to Your App!", html, }) }
// Alternative: Using Resend with React component directly export async function sendWelcomeEmailDirect(to: string, name: string, dashboardUrl: string) { await resend.emails.send({ from: "noreply@yourapp.com", to, subject: "Welcome to Your App!", react: <WelcomeEmail name={name} dashboardUrl={dashboardUrl} />, }) }
Using AWS SES
// lib/email.server.ts import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses" import { render } from "@react-email/render" import WelcomeEmail from "~/emails/welcome"
const ses = new SESClient({ region: "us-east-1" })
export async function sendWelcomeEmail(to: string, name: string, dashboardUrl: string) { const html = render(<WelcomeEmail name={name} dashboardUrl={dashboardUrl} />) const text = render(<WelcomeEmail name={name} dashboardUrl={dashboardUrl} />, { plainText: true, })
const command = new SendEmailCommand({ Source: "noreply@yourapp.com", Destination: { ToAddresses: [to] }, Message: { Subject: { Data: "Welcome to Your App!" }, Body: { Html: { Data: html }, Text: { Data: text }, }, }, })
await ses.send(command) }
SST Integration
// sst.config.ts import { Config } from "sst/constructs"
export default { stacks(app) { app.stack(function Email({ stack }) { // Option 1: Resend API key const RESEND_API_KEY = new Config.Secret(stack, "RESEND_API_KEY")
// Option 2: SES (no additional config needed)
// Lambda functions automatically get SES permissions
new RemixSite(stack, "site", {
bind: [RESEND_API_KEY],
})
})
}, }
Email Best Practices
Deliverability
// 1. Verify sender domain // Add SPF, DKIM, DMARC records to your DNS
// 2. Use proper from address from: "Your Name <noreply@yourapp.com>"
// 3. Include unsubscribe link <Link href={unsubscribeUrl} style={unsubscribeLink}> Unsubscribe </Link>
// 4. Authenticate email domain // With Resend: verify domain in dashboard // With SES: verify domain and configure DKIM
Content Guidelines
// DO: Use plain, professional language subject: "Your order #12345 has shipped"
// DON'T: Use spammy language subject: "🔥 AMAZING DEAL!!! BUY NOW!!!"
// DO: Include clear CTAs <Button href={url}>View Order</Button>
// DON'T: Use too many links // Limit to 1-3 primary CTAs
// DO: Provide context "You're receiving this because you signed up for updates on [date]"
// DON'T: Send without context // Always explain why they're receiving the email
Accessibility
import { Img } from "@react-email/components"
// Always include alt text <Img src="https://yourapp.com/logo.png" alt="Your App Logo" width="150" height="50" />
// Use semantic HTML <Heading>Main Title</Heading> <Text>Paragraph content</Text>
// Ensure good color contrast const linkStyle = { color: "#0066cc", // Good contrast with white background textDecoration: "underline", }
// Use readable font sizes const text = { fontSize: "16px", // Minimum 14px lineHeight: "24px", // 1.5x font size }
Testing Emails
Development Preview
Start React Email dev server
npm run email:dev
package.json
{ "scripts": { "email:dev": "email dev" } }
Visit http://localhost:3000 to preview emails
Send Test Emails
// scripts/test-email.ts import { sendWelcomeEmail } from "./lib/email.server"
await sendWelcomeEmail( "test@example.com", "Test User", "https://app.yourapp.com/dashboard" )
console.log("Test email sent!")
Email Testing Tools
// Use Mailtrap for testing in development const resend = new Resend( process.env.NODE_ENV === "production" ? process.env.RESEND_API_KEY : process.env.MAILTRAP_API_KEY )
// Use Litmus or Email on Acid for rendering tests across clients
Advanced Patterns
Email with Attachments
await resend.emails.send({ from: "noreply@yourapp.com", to: email, subject: "Your Invoice", react: <InvoiceEmail />, attachments: [ { filename: "invoice.pdf", content: pdfBuffer, }, ], })
Scheduled Emails
// Using EventBridge with SST import { Cron } from "sst/constructs"
new Cron(stack, "DailySummary", { schedule: "cron(0 9 * * ? *)", // 9 AM daily job: "functions/send-daily-summary.handler", })
// functions/send-daily-summary.ts export async function handler() { const users = await getActiveUsers()
for (const user of users) { await sendDailySummaryEmail(user.email, user.name, user.stats) } }
Email Analytics
// Track opens and clicks with Resend await resend.emails.send({ from: "noreply@yourapp.com", to: email, subject: "Your Report", react: <ReportEmail />, tags: [ { name: "category", value: "report" }, { name: "user_id", value: userId }, ], })
// Query analytics const analytics = await resend.emails.get(emailId) console.log(analytics.opens, analytics.clicks)
Common Email Types
// Welcome series export const welcomeEmails = [ { delay: 0, template: "welcome", subject: "Welcome!" }, { delay: 1, template: "getting-started", subject: "Getting Started Guide" }, { delay: 3, template: "tips", subject: "Pro Tips for Success" }, ]
// Transactional
- Welcome email
- Email verification
- Password reset
- Receipt/invoice
- Shipping notification
- Account changes
// Engagement
- Weekly summary
- Achievement unlocked
- Abandoned cart
- Re-engagement
- Product updates
Resources
-
React Email Documentation
-
Resend Documentation
-
AWS SES Best Practices
-
Email on Acid
-
Can I email - Email client support tables