Keplars

React Email

Build beautiful emails using React components with TypeScript support

Build type-safe, component-based email templates using React Email and send them with Keplars Mail Service.

What is React Email? A collection of high-quality, unstyled React components for creating beautiful emails. Built by Resend.

Why React Email?

  • Component-Based: Reusable email components (Header, Footer, Button)
  • Type-Safe: Full TypeScript support with prop validation
  • Live Preview: Dev server to preview emails in real-time
  • Testable: Unit test your email templates
  • Version Control: Git-friendly, works with your existing workflow
  • Modern DX: ESLint, Prettier, and hot reload support

Installation

Install Dependencies

npm install react-email @react-email/components

Initialize React Email (Optional)

npx email init

This creates an emails/ directory with example templates.

Quick Start

Create Your First Template

Create emails/welcome.tsx:

import {
  Body,
  Button,
  Container,
  Head,
  Html,
  Preview,
  Section,
  Text,
} from '@react-email/components';
import * as React from 'react';

interface WelcomeEmailProps {
  userName: string;
  dashboardUrl?: string;
}

export const WelcomeEmail = ({
  userName,
  dashboardUrl = 'https://app.keplars.com',
}: WelcomeEmailProps) => {
  return (
    <Html>
      <Head />
      <Preview>Welcome to Keplars - Get started now</Preview>
      <Body style={main}>
        <Container style={container}>
          <Section style={section}>
            <Text style={heading}>Welcome {userName}!</Text>
            <Text style={text}>
              Thank you for joining Keplars Mail Service. We're excited to help you
              send beautiful emails.
            </Text>
            <Button style={button} href={dashboardUrl}>
              Go to Dashboard
            </Button>
          </Section>
        </Container>
      </Body>
    </Html>
  );
};

export default WelcomeEmail;

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 section = {
  padding: '0 48px',
};

const heading = {
  fontSize: '32px',
  lineHeight: '1.3',
  fontWeight: '700',
  color: '#484848',
};

const text = {
  fontSize: '16px',
  lineHeight: '26px',
  color: '#484848',
};

const button = {
  backgroundColor: '#5469d4',
  borderRadius: '4px',
  color: '#fff',
  fontSize: '16px',
  textDecoration: 'none',
  textAlign: 'center' as const,
  display: 'block',
  width: '100%',
  padding: '12px',
};

Render and Send

import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';

async function sendWelcomeEmail(userEmail: string, userName: string) {
  const html = await render(<WelcomeEmail userName={userName} />);

  const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to: [userEmail],
      subject: `Welcome ${userName}!`,
      body: html,
    }),
  });

  return response.json();
}

await sendWelcomeEmail('[email protected]', 'John Doe');

Complete Examples

2FA Code Email (Instant Priority)

import { Body, Container, Head, Html, Section, Text } from '@react-email/components';
import * as React from 'react';

interface TwoFactorEmailProps {
  code: string;
  expiresIn?: number;
}

export const TwoFactorEmail = ({ code, expiresIn = 10 }: TwoFactorEmailProps) => {
  return (
    <Html>
      <Head />
      <Body style={main}>
        <Container style={container}>
          <Section style={section}>
            <Text style={heading}>Your Verification Code</Text>
            <Text style={text}>
              Use this code to complete your authentication:
            </Text>
            <Text style={codeStyle}>{code}</Text>
            <Text style={subtext}>
              This code expires in {expiresIn} minutes.
            </Text>
          </Section>
        </Container>
      </Body>
    </Html>
  );
};

export default TwoFactorEmail;

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

const container = {
  backgroundColor: '#ffffff',
  margin: '0 auto',
  padding: '20px 0 48px',
};

const section = {
  padding: '0 48px',
};

const heading = {
  fontSize: '24px',
  fontWeight: '700',
  color: '#484848',
};

const text = {
  fontSize: '16px',
  lineHeight: '26px',
  color: '#484848',
};

const codeStyle = {
  fontSize: '32px',
  fontWeight: '700',
  letterSpacing: '8px',
  textAlign: 'center' as const,
  color: '#5469d4',
  backgroundColor: '#f4f4f4',
  padding: '16px',
  borderRadius: '4px',
  margin: '24px 0',
};

const subtext = {
  fontSize: '14px',
  color: '#666',
  textAlign: 'center' as const,
};

Send with instant priority:

import { render } from '@react-email/render';
import { TwoFactorEmail } from './emails/two-factor';

async function send2FACode(userEmail: string, code: string) {
  const html = await render(<TwoFactorEmail code={code} />);

  await fetch('https://api.keplars.com/api/v1/send-email/instant', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to: [userEmail],
      subject: 'Your verification code',
      body: html,
    }),
  });
}

Order Confirmation

import {
  Body,
  Button,
  Column,
  Container,
  Head,
  Html,
  Row,
  Section,
  Text,
} from '@react-email/components';
import * as React from 'react';

interface OrderConfirmationProps {
  customerName: string;
  orderNumber: string;
  orderDate: string;
  items: Array<{ name: string; quantity: number; price: number }>;
  totalAmount: number;
  trackingUrl?: string;
}

export const OrderConfirmation = ({
  customerName,
  orderNumber,
  orderDate,
  items,
  totalAmount,
  trackingUrl,
}: OrderConfirmationProps) => {
  return (
    <Html>
      <Head />
      <Body style={main}>
        <Container style={container}>
          <Section>
            <Text style={heading}>Order Confirmed! 🎉</Text>
            <Text style={text}>Hi {customerName},</Text>
            <Text style={text}>
              Your order has been confirmed and will be shipped soon.
            </Text>

            <Section style={infoSection}>
              <Text style={label}>Order Number</Text>
              <Text style={value}>{orderNumber}</Text>
              <Text style={label}>Order Date</Text>
              <Text style={value}>{orderDate}</Text>
            </Section>

            <Section style={itemsSection}>
              <Text style={subheading}>Order Items</Text>
              {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 align="right">
                    <Text style={itemPrice}>${item.price.toFixed(2)}</Text>
                  </Column>
                </Row>
              ))}
            </Section>

            <Section style={totalSection}>
              <Text style={totalLabel}>Total</Text>
              <Text style={totalAmount}>${totalAmount.toFixed(2)}</Text>
            </Section>

            {trackingUrl && (
              <Button style={button} href={trackingUrl}>
                Track Your Order
              </Button>
            )}
          </Section>
        </Container>
      </Body>
    </Html>
  );
};

export default OrderConfirmation;

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

const container = {
  backgroundColor: '#ffffff',
  margin: '0 auto',
  padding: '20px 0 48px',
  maxWidth: '600px',
};

const heading = {
  fontSize: '28px',
  fontWeight: '700',
  color: '#484848',
  padding: '0 48px',
};

const subheading = {
  fontSize: '20px',
  fontWeight: '600',
  color: '#484848',
  marginTop: '32px',
};

const text = {
  fontSize: '16px',
  lineHeight: '26px',
  color: '#484848',
  padding: '0 48px',
};

const infoSection = {
  padding: '0 48px',
  marginTop: '24px',
  backgroundColor: '#f4f4f4',
  borderRadius: '4px',
  padding: '16px 48px',
};

const label = {
  fontSize: '12px',
  color: '#666',
  fontWeight: '600',
  marginBottom: '4px',
  marginTop: '16px',
};

const value = {
  fontSize: '16px',
  color: '#484848',
  marginTop: '0',
};

const itemsSection = {
  padding: '0 48px',
  marginTop: '32px',
};

const itemRow = {
  borderBottom: '1px solid #e6e6e6',
  paddingBottom: '16px',
  marginBottom: '16px',
};

const itemName = {
  fontSize: '16px',
  color: '#484848',
  margin: '0',
};

const itemQuantity = {
  fontSize: '14px',
  color: '#666',
  margin: '4px 0 0',
};

const itemPrice = {
  fontSize: '16px',
  fontWeight: '600',
  color: '#484848',
  margin: '0',
};

const totalSection = {
  padding: '24px 48px',
  borderTop: '2px solid #484848',
  marginTop: '24px',
};

const totalLabel = {
  fontSize: '18px',
  fontWeight: '600',
  color: '#484848',
  display: 'inline-block',
  marginRight: '16px',
};

const totalAmount = {
  fontSize: '24px',
  fontWeight: '700',
  color: '#5469d4',
  display: 'inline-block',
};

const button = {
  backgroundColor: '#5469d4',
  borderRadius: '4px',
  color: '#fff',
  fontSize: '16px',
  textDecoration: 'none',
  textAlign: 'center' as const,
  display: 'block',
  width: '100%',
  padding: '12px',
  marginTop: '32px',
};

Express.js Integration

import express from 'express';
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
import { OrderConfirmation } from './emails/order-confirmation';

const app = express();
app.use(express.json());

const KEPLERS_API_KEY = process.env.KEPLERS_API_KEY;

async function sendEmail(to: string, subject: string, html: string) {
  const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${KEPLERS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to: [to], subject, body: html }),
  });

  if (!response.ok) {
    throw new Error(`Failed to send email: ${response.statusText}`);
  }

  return response.json();
}

app.post('/api/send-welcome', async (req, res) => {
  try {
    const { email, name } = req.body;

    const html = await render(<WelcomeEmail userName={name} />);
    const result = await sendEmail(email, `Welcome ${name}!`, html);

    res.json({ success: true, data: result });
  } catch (error) {
    console.error('Failed to send welcome email:', error);
    res.status(500).json({ error: 'Failed to send email' });
  }
});

app.post('/api/send-order-confirmation', async (req, res) => {
  try {
    const { email, customerName, order } = req.body;

    const html = await render(
      <OrderConfirmation
        customerName={customerName}
        orderNumber={order.number}
        orderDate={order.date}
        items={order.items}
        totalAmount={order.total}
        trackingUrl={order.trackingUrl}
      />
    );

    const result = await sendEmail(
      email,
      `Order Confirmation - ${order.number}`,
      html
    );

    res.json({ success: true, data: result });
  } catch (error) {
    console.error('Failed to send order confirmation:', error);
    res.status(500).json({ error: 'Failed to send email' });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Next.js Integration

API Route (App Router)

Create app/api/send-email/route.ts:

import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/welcome';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    const { email, name } = await request.json();

    const html = await render(<WelcomeEmail userName={name} />);

    const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        to: [email],
        subject: `Welcome ${name}!`,
        body: html,
      }),
    });

    if (!response.ok) {
      throw new Error('Failed to send email');
    }

    const result = await response.json();
    return NextResponse.json({ success: true, data: result });
  } catch (error) {
    console.error('Error sending email:', error);
    return NextResponse.json(
      { error: 'Failed to send email' },
      { status: 500 }
    );
  }
}

Server Action

Create app/actions/send-email.ts:

'use server';

import { render } from '@react-email/render';
import { WelcomeEmail } from '@/emails/welcome';

export async function sendWelcomeEmail(email: string, name: string) {
  const html = await render(<WelcomeEmail userName={name} />);

  const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to: [email],
      subject: `Welcome ${name}!`,
      body: html,
    }),
  });

  if (!response.ok) {
    throw new Error('Failed to send email');
  }

  return response.json();
}

Use in a client component:

'use client';

import { sendWelcomeEmail } from './actions/send-email';

export function WelcomeForm() {
  async function handleSubmit(formData: FormData) {
    const email = formData.get('email') as string;
    const name = formData.get('name') as string;

    await sendWelcomeEmail(email, name);
  }

  return (
    <form action={handleSubmit}>
      <input type="email" name="email" required />
      <input type="text" name="name" required />
      <button type="submit">Send Welcome Email</button>
    </form>
  );
}

Available Components

React Email provides many pre-built components:

Layout Components

  • <Html> - Root HTML element
  • <Head> - Document head
  • <Body> - Document body
  • <Container> - Centered container (max-width: 600px)
  • <Section> - Generic section wrapper
  • <Row> / <Column> - Grid layout

Content Components

  • <Text> - Paragraph text
  • <Heading> - Headings (h1-h6)
  • <Link> - Styled links
  • <Button> - Call-to-action buttons
  • <Img> - Optimized images
  • <Hr> - Horizontal divider
  • <Code> - Inline or block code
  • <Preview> - Email preview text (inbox preview)

Example Usage

import {
  Button,
  Code,
  Column,
  Container,
  Heading,
  Hr,
  Img,
  Link,
  Preview,
  Row,
  Section,
  Text,
} from '@react-email/components';

export const CompleteExample = () => (
  <Html>
    <Head />
    <Preview>Complete example of React Email components</Preview>
    <Body>
      <Container>
        <Img src="https://example.com/logo.png" alt="Logo" width="200" />
        <Heading>Welcome to React Email</Heading>
        <Text>This email showcases various components.</Text>
        <Hr />

        <Section>
          <Row>
            <Column>Left content</Column>
            <Column>Right content</Column>
          </Row>
        </Section>

        <Code>const code = "highlighted code";</Code>

        <Button href="https://example.com">Click Me</Button>

        <Text>
          Visit our <Link href="https://example.com">website</Link>
        </Text>
      </Container>
    </Body>
  </Html>
);

Development Workflow

Preview Emails Locally

Start the React Email dev server:

npm run email dev

This starts a local server at http://localhost:3000 where you can:

  • Preview all your email templates
  • Test with different props
  • See changes in real-time
  • Test responsive design

Project Structure

your-project/
├── emails/
│   ├── welcome.tsx
│   ├── two-factor.tsx
│   ├── order-confirmation.tsx
│   ├── password-reset.tsx
│   └── components/
│       ├── header.tsx
│       ├── footer.tsx
│       └── button.tsx
├── src/
│   └── lib/
│       └── email.ts          # Email sending utility
└── package.json

Reusable Components

Create shared email components:

// emails/components/header.tsx
import { Img, Section, Text } from '@react-email/components';

export const EmailHeader = () => (
  <Section style={header}>
    <Img
      src="https://your-domain.com/logo.png"
      alt="Company Logo"
      width="150"
    />
    <Text style={tagline}>Your Company Tagline</Text>
  </Section>
);

const header = {
  padding: '20px 0',
  borderBottom: '1px solid #e6e6e6',
};

const tagline = {
  fontSize: '14px',
  color: '#666',
};

Use in templates:

import { EmailHeader } from './components/header';

export const WelcomeEmail = ({ userName }) => (
  <Html>
    <Body>
      <Container>
        <EmailHeader />
        <Text>Welcome {userName}!</Text>
      </Container>
    </Body>
  </Html>
);

Testing Email Templates

Unit Testing with Vitest

import { render } from '@react-email/render';
import { describe, expect, it } from 'vitest';
import { WelcomeEmail } from './welcome';

describe('WelcomeEmail', () => {
  it('renders with user name', async () => {
    const html = await render(<WelcomeEmail userName="John Doe" />);

    expect(html).toContain('Welcome John Doe!');
  });

  it('includes dashboard link', async () => {
    const html = await render(
      <WelcomeEmail userName="John" dashboardUrl="https://app.example.com" />
    );

    expect(html).toContain('https://app.example.com');
  });

  it('handles missing optional props', async () => {
    const html = await render(<WelcomeEmail userName="John" />);

    expect(html).toContain('https://app.keplars.com');
  });
});

Integration Testing

import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';

async function testEmailSending() {
  const html = await render(<WelcomeEmail userName="Test User" />);

  const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to: ['[email protected]'],
      subject: 'Test Email',
      body: html,
    }),
  });

  console.log('Email sent:', response.ok ? 'Success' : 'Failed');
}

Best Practices

1. Type Your Props

interface EmailProps {
  userName: string;
  orderNumber: string;
  items: Array<{
    name: string;
    price: number;
  }>;
}

export const Email = ({ userName, orderNumber, items }: EmailProps) => {
  // TypeScript ensures correct prop usage
};

2. Use Default Values

export const Email = ({
  userName,
  dashboardUrl = 'https://app.keplars.com',
}: EmailProps) => {
  // Falls back to default if not provided
};

3. Extract Styles

const styles = {
  main: { backgroundColor: '#f6f9fc' },
  container: { padding: '20px' },
  heading: { fontSize: '24px' },
};

export const Email = () => (
  <Html>
    <Body style={styles.main}>
      <Container style={styles.container}>
        <Text style={styles.heading}>Title</Text>
      </Container>
    </Body>
  </Html>
);

4. Create Component Library

Organize reusable components:

emails/
├── templates/
│   ├── welcome.tsx
│   └── order.tsx
├── components/
│   ├── header.tsx
│   ├── footer.tsx
│   ├── button.tsx
│   └── layout.tsx
└── styles/
    └── theme.ts

5. Version Control Templates

// emails/welcome-v2.tsx
export const WelcomeEmailV2 = ({ userName }: Props) => {
  // New version with updated design
};

// Gradually migrate users to new version
const template = useNewDesign ? WelcomeEmailV2 : WelcomeEmail;

Email Client Compatibility: Test emails across different clients (Gmail, Outlook, Apple Mail). React Email components are designed for broad compatibility, but always test critical emails.

Troubleshooting

Module Resolution Issues

If you see Cannot find module '@react-email/components':

npm install @react-email/components

Or update your tsconfig.json:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "jsx": "react-jsx"
  }
}

Styles Not Rendering

Inline styles work best for emails:

<Text style={{ color: '#000' }}>Text</Text>

Avoid CSS classes or external stylesheets.

TypeScript Errors

Ensure you have React types installed:

npm install --save-dev @types/react

Resources

Next Steps


Build professional email templates with React Email and send them reliably with Keplars Mail Service.

On this page