Fix: React Email and Resend Not Working — Emails Not Sending, Templates Not Rendering, or Styling Broken
Quick Answer
How to fix React Email and Resend issues — email template components, inline styles, Resend API integration, preview server, attachments, and email client compatibility.
The Problem
Resend returns a success response but no email arrives:
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
const { data, error } = await resend.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: 'Welcome!',
html: '<h1>Hello</h1>',
});
// data.id exists but email never arrivesOr React Email components render in the preview but look broken in actual email clients:
import { Html, Body, Container, Text } from '@react-email/components';
function WelcomeEmail() {
return (
<Html>
<Body style={{ display: 'flex', justifyContent: 'center' }}>
<Container>
<Text>Welcome!</Text>
</Container>
</Body>
</Html>
);
}
// Preview looks perfect — Gmail shows a messOr the React Email preview server fails:
npx react-email dev
# Error: Cannot find module '@react-email/components'Why This Happens
React Email renders React components to HTML strings for email. Resend is the sending API. The issues are at different layers:
- Resend requires a verified domain — the
fromaddress must use a domain you’ve verified in the Resend dashboard. Using an unverified domain silently fails or goes to spam.[email protected]is the only default sender that works without verification. - Email clients strip most CSS — Gmail removes
<style>tags, Outlook doesn’t support flexbox or grid, and Apple Mail handles media queries differently. React Email components use inline styles and table-based layouts specifically because those are the only reliable approaches across email clients. - React Email is server-side only —
render()converts React Email components to HTML strings. This runs on the server (API route, Server Action). Trying to import React Email components in client-side code fails because some dependencies are Node.js-only. - The preview server needs the components installed —
npx react-email devscans for.tsxfiles in youremails/directory. If the directory structure or component imports are wrong, the preview server shows nothing.
Fix 1: Set Up React Email Templates
npm install @react-email/components react-email resend// emails/welcome.tsx — email template
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Text,
Link,
Img,
Button,
Hr,
Row,
Column,
} from '@react-email/components';
interface WelcomeEmailProps {
username: string;
loginUrl: string;
}
export default function WelcomeEmail({ username, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
{/* Preview text — shown in inbox list, not in email body */}
<Preview>Welcome to MyApp, {username}!</Preview>
<Body style={main}>
<Container style={container}>
{/* Logo */}
<Img
src="https://myapp.com/logo.png"
width="120"
height="36"
alt="MyApp"
style={{ margin: '0 auto', display: 'block' }}
/>
<Text style={heading}>Welcome, {username}!</Text>
<Text style={paragraph}>
Your account has been created. Click the button below to get started.
</Text>
{/* CTA button — must use table-based layout for Outlook */}
<Section style={{ textAlign: 'center' as const, marginTop: '32px' }}>
<Button
href={loginUrl}
style={button}
>
Go to Dashboard
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
If you didn't create this account, you can safely ignore this email.
</Text>
</Container>
</Body>
</Html>
);
}
// Styles must be inline objects — no CSS classes, no external stylesheets
const main = {
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: '600' as const,
color: '#1a1a1a',
textAlign: 'center' as const,
margin: '30px 0',
};
const paragraph = {
fontSize: '16px',
lineHeight: '26px',
color: '#484848',
};
const button = {
backgroundColor: '#3b82f6',
borderRadius: '6px',
color: '#fff',
fontSize: '16px',
fontWeight: '600' as const,
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '12px 24px',
};
const hr = {
borderColor: '#e6ebf1',
margin: '32px 0',
};
const footer = {
color: '#8898aa',
fontSize: '12px',
lineHeight: '16px',
};Fix 2: Send Emails with Resend
# Set up environment variable
# RESEND_API_KEY=re_xxxxxxxxxx// lib/email.ts — Resend client
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, username: string) {
// Render React component to HTML string
const html = await render(WelcomeEmail({
username,
loginUrl: `https://myapp.com/login`,
}));
const { data, error } = await resend.emails.send({
from: 'MyApp <[email protected]>', // Must be verified domain
to,
subject: `Welcome to MyApp, ${username}!`,
html,
// Optional: plain text fallback
text: `Welcome to MyApp, ${username}! Visit https://myapp.com/login to get started.`,
// Optional: reply-to
replyTo: '[email protected]',
// Optional: tags for analytics
tags: [
{ name: 'category', value: 'welcome' },
],
});
if (error) {
console.error('Email send failed:', error);
throw new Error(`Failed to send email: ${error.message}`);
}
return data;
}
// Or pass the React component directly (Resend supports this)
export async function sendWelcomeEmailDirect(to: string, username: string) {
const { data, error } = await resend.emails.send({
from: 'MyApp <[email protected]>',
to,
subject: `Welcome to MyApp, ${username}!`,
react: WelcomeEmail({ username, loginUrl: 'https://myapp.com/login' }),
});
return { data, error };
}// app/api/send/route.ts — Next.js API route
import { sendWelcomeEmail } from '@/lib/email';
export async function POST(request: Request) {
const { email, username } = await request.json();
try {
const result = await sendWelcomeEmail(email, username);
return Response.json({ success: true, id: result?.id });
} catch (error) {
return Response.json({ error: 'Failed to send email' }, { status: 500 });
}
}Fix 3: Email-Safe Styling
Email clients have strict CSS support. Use these patterns:
import {
Html, Body, Container, Section, Row, Column, Text, Img,
} from '@react-email/components';
// Two-column layout — use Row/Column (renders as tables)
function NewsletterEmail({ articles }: { articles: Article[] }) {
return (
<Html>
<Body style={{ backgroundColor: '#f6f9fc', fontFamily: 'Arial, sans-serif' }}>
<Container style={{ maxWidth: '600px', margin: '0 auto' }}>
{/* Header with background color */}
<Section style={{
backgroundColor: '#1a1a2e',
padding: '40px 20px',
textAlign: 'center' as const,
borderRadius: '8px 8px 0 0',
}}>
<Text style={{ color: '#ffffff', fontSize: '28px', fontWeight: 'bold', margin: '0' }}>
Weekly Digest
</Text>
</Section>
{/* Two-column layout */}
<Section style={{ padding: '20px' }}>
<Row>
<Column style={{ width: '50%', paddingRight: '10px', verticalAlign: 'top' }}>
<Text style={{ fontWeight: 'bold', fontSize: '16px' }}>
{articles[0]?.title}
</Text>
<Text style={{ color: '#666', fontSize: '14px' }}>
{articles[0]?.excerpt}
</Text>
</Column>
<Column style={{ width: '50%', paddingLeft: '10px', verticalAlign: 'top' }}>
<Text style={{ fontWeight: 'bold', fontSize: '16px' }}>
{articles[1]?.title}
</Text>
<Text style={{ color: '#666', fontSize: '14px' }}>
{articles[1]?.excerpt}
</Text>
</Column>
</Row>
</Section>
{/* Safe CSS properties for email */}
{/*
✅ Safe: color, background-color, font-size, font-weight, font-family,
text-align, padding, margin, border, width, height, line-height,
text-decoration, vertical-align, display (block/inline/none)
❌ Avoid: flexbox, grid, position, float, box-shadow, border-radius (Outlook),
transform, animation, CSS variables, calc(), media queries (limited)
*/}
</Container>
</Body>
</Html>
);
}Fix 4: Resend Domain Verification
# Verify domain in Resend dashboard:
# 1. Go to https://resend.com/domains
# 2. Add your domain (e.g., myapp.com)
# 3. Add the DNS records (SPF, DKIM, DMARC) to your domain provider
# 4. Wait for verification (usually minutes, sometimes hours)DNS records you’ll need to add:
| Type | Name | Value | Purpose |
|---|---|---|---|
| TXT | @ or myapp.com | v=spf1 include:amazonses.com ~all | SPF |
| CNAME | resend._domainkey | Provided by Resend | DKIM |
| TXT | _dmarc | v=DMARC1; p=none; | DMARC |
Until domain is verified, use the test sender:
await resend.emails.send({
from: 'Acme <[email protected]>', // Works without verification
to: '[email protected]', // Test recipient
subject: 'Test',
html: '<p>Test email</p>',
});Fix 5: Preview and Development
# Start the React Email preview server
npx react-email dev
# Or add to package.json
# "email:dev": "react-email dev --dir emails --port 3001"# Expected directory structure
emails/
├── welcome.tsx
├── reset-password.tsx
├── order-confirmation.tsx
└── newsletter.tsx// Generate static HTML for testing
import { render } from '@react-email/render';
import WelcomeEmail from './emails/welcome';
const html = await render(WelcomeEmail({
username: 'Alice',
loginUrl: 'https://myapp.com/login',
}));
// Write to file for manual testing
import fs from 'fs';
fs.writeFileSync('test-email.html', html);
// Open test-email.html in a browser to previewFix 6: Attachments and Batch Sending
import { Resend } from 'resend';
import fs from 'fs';
const resend = new Resend(process.env.RESEND_API_KEY);
// Send with attachment
await resend.emails.send({
from: 'MyApp <[email protected]>',
to: '[email protected]',
subject: 'Your Invoice',
html: '<p>Please find your invoice attached.</p>',
attachments: [
{
filename: 'invoice.pdf',
content: fs.readFileSync('./invoices/inv-001.pdf'),
},
{
filename: 'receipt.pdf',
path: 'https://myapp.com/receipts/rec-001.pdf', // URL-based
},
],
});
// Batch send — multiple emails in one API call
const { data, error } = await resend.batch.send([
{
from: 'MyApp <[email protected]>',
to: '[email protected]',
subject: 'Hello Alice',
html: '<p>Hi Alice!</p>',
},
{
from: 'MyApp <[email protected]>',
to: '[email protected]',
subject: 'Hello Bob',
html: '<p>Hi Bob!</p>',
},
]);
// Up to 100 emails per batch
// Schedule email for later
await resend.emails.send({
from: 'MyApp <[email protected]>',
to: '[email protected]',
subject: 'Reminder',
html: '<p>Don\'t forget!</p>',
scheduledAt: '2024-12-25T09:00:00Z', // ISO 8601
});Still Not Working?
Email sends successfully but never arrives — check spam/junk folder first. Then verify your domain’s DNS records are correct in the Resend dashboard (all three: SPF, DKIM, DMARC should show green checkmarks). Without proper DNS, emails are flagged as spam or silently dropped by the recipient’s mail server.
React Email components render as plain text — you’re passing the component as a string instead of rendering it. Use render(MyEmail(props)) to convert the React component to an HTML string, or pass the component directly via Resend’s react property instead of html.
Styles look correct in preview but broken in Gmail — Gmail strips <style> tags and class attributes. All styles must be inline. React Email’s <style> component handles some inlining, but manual inline styles are most reliable. Also, Gmail doesn’t support border-radius in some contexts, background-image, or CSS variables.
render() throws “Cannot read properties of undefined” — the component might be using hooks or browser APIs. React Email templates must be pure render functions — no useState, no useEffect, no window. They’re rendered once on the server to produce a static HTML string.
For related API and backend issues, see Fix: Auth.js Not Working and Fix: Hono Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Better Auth Not Working — Login Failing, Session Null, or OAuth Callback Error
How to fix Better Auth issues — server and client setup, email/password and OAuth providers, session management, middleware protection, database adapters, and plugin configuration.
Fix: next-safe-action Not Working — Action Not Executing, Validation Errors Missing, or Type Errors
How to fix next-safe-action issues — action client setup, Zod schema validation, useAction and useOptimisticAction hooks, middleware, error handling, and authorization patterns.
Fix: ts-rest Not Working — Contract Types Not Matching, Client Requests Failing, or Server Validation Errors
How to fix ts-rest issues — contract definition, type-safe client and server setup, Zod validation, Next.js App Router integration, error handling, and OpenAPI generation.
Fix: Auth.js (NextAuth) Not Working — Session Null, OAuth Callback Error, or CSRF Token Mismatch
How to fix Auth.js and NextAuth.js issues — OAuth provider setup, session handling in App Router and Pages Router, JWT vs database sessions, middleware protection, and credential provider configuration.