Sends transactional emails with Resend including React Email templates, API integration, and Next.js setup. Use when sending emails, creating email templates, or integrating transactional email in applications.
Sends transactional emails using Resend with React Email templates. Use when you need to send emails or create email templates in Next.js applications.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Modern email API for developers with React Email support.
Install:
npm install resend
Environment variable:
# .env.local
RESEND_API_KEY=re_...
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
const { data, error } = await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: ['user@example.com'],
subject: 'Hello World',
html: '<p>Welcome to our app!</p>',
});
// app/api/send/route.ts
import { Resend } from 'resend';
import { NextResponse } from 'next/server';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
const { to, subject, message } = await req.json();
try {
const { data, error } = await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: [to],
subject,
html: `<p>${message}</p>`,
});
if (error) {
return NextResponse.json({ error }, { status: 400 });
}
return NextResponse.json({ data });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to send email' },
{ status: 500 }
);
}
}
npm install @react-email/components
// emails/welcome.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Section,
Text,
} from '@react-email/components';
interface WelcomeEmailProps {
username: string;
loginUrl: string;
}
export function WelcomeEmail({ username, loginUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Welcome to Acme - Your account is ready!</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://acme.com/logo.png"
width={48}
height={48}
alt="Acme"
/>
<Heading style={h1}>Welcome, {username}!</Heading>
<Text style={text}>
Thanks for signing up for Acme. We're excited to have you on board.
</Text>
<Section style={buttonContainer}>
<Button style={button} href={loginUrl}>
Get Started
</Button>
</Section>
<Text style={footer}>
If you didn't create an account, you can safely ignore this email.
</Text>
</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: '40px 20px',
borderRadius: '5px',
maxWidth: '465px',
};
const h1 = {
color: '#1f2937',
fontSize: '24px',
fontWeight: '600',
lineHeight: '40px',
margin: '0 0 20px',
};
const text = {
color: '#4b5563',
fontSize: '14px',
lineHeight: '24px',
margin: '0 0 20px',
};
const buttonContainer = {
textAlign: 'center' as const,
margin: '30px 0',
};
const button = {
backgroundColor: '#3b82f6',
borderRadius: '5px',
color: '#fff',
fontSize: '14px',
fontWeight: '600',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '12px 30px',
};
const footer = {
color: '#9ca3af',
fontSize: '12px',
lineHeight: '16px',
margin: '20px 0 0',
};
export default WelcomeEmail;
// app/api/send-welcome/route.ts
import { Resend } from 'resend';
import { WelcomeEmail } from '@/emails/welcome';
import { NextResponse } from 'next/server';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(req: Request) {
const { email, username } = await req.json();
try {
const { data, error } = await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: [email],
subject: 'Welcome to Acme!',
react: WelcomeEmail({
username,
loginUrl: `${process.env.NEXT_PUBLIC_APP_URL}/login`,
}),
});
if (error) {
return NextResponse.json({ error }, { status: 400 });
}
return NextResponse.json({ data });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to send email' },
{ status: 500 }
);
}
}
// emails/password-reset.tsx
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Text,
} from '@react-email/components';
interface PasswordResetProps {
resetUrl: string;
expiresIn: string;
}
export function PasswordResetEmail({ resetUrl, expiresIn }: PasswordResetProps) {
return (
<Html>
<Head />
<Preview>Reset your password</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Reset Your Password</Heading>
<Text style={text}>
We received a request to reset your password. Click the button below
to create a new password.
</Text>
<Button style={button} href={resetUrl}>
Reset Password
</Button>
<Text style={text}>
This link will expire in {expiresIn}. If you didn't request a
password reset, you can safely ignore this email.
</Text>
</Container>
</Body>
</Html>
);
}
// emails/order-confirmation.tsx
import {
Body,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Section,
Text,
} from '@react-email/components';
interface OrderItem {
name: string;
quantity: number;
price: number;
}
interface OrderConfirmationProps {
orderNumber: string;
items: OrderItem[];
total: number;
shippingAddress: string;
}
export function OrderConfirmationEmail({
orderNumber,
items,
total,
shippingAddress,
}: OrderConfirmationProps) {
return (
<Html>
<Head />
<Preview>Order {orderNumber} confirmed</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Order Confirmed</Heading>
<Text style={text}>
Thanks for your order! Your order number is{' '}
<strong>{orderNumber}</strong>.
</Text>
<Section style={orderSection}>
<Heading as="h2" style={h2}>
Order Summary
</Heading>
{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={priceColumn}>
<Text style={itemPrice}>${item.price.toFixed(2)}</Text>
</Column>
</Row>
))}
<Hr style={hr} />
<Row>
<Column>
<Text style={totalLabel}>Total</Text>
</Column>
<Column style={priceColumn}>
<Text style={totalPrice}>${total.toFixed(2)}</Text>
</Column>
</Row>
</Section>
<Section>
<Heading as="h2" style={h2}>
Shipping Address
</Heading>
<Text style={text}>{shippingAddress}</Text>
</Section>
</Container>
</Body>
</Html>
);
}
const { data, error } = await resend.emails.send({
// Required
from: 'Acme <noreply@acme.com>',
to: ['user@example.com'],
subject: 'Hello',
// Content (one of these)
html: '<p>Hello</p>',
text: 'Hello',
react: EmailTemplate({ props }),
// Optional
cc: ['cc@example.com'],
bcc: ['bcc@example.com'],
replyTo: 'support@acme.com',
headers: {
'X-Custom-Header': 'value',
},
attachments: [
{
filename: 'invoice.pdf',
content: Buffer.from(pdfContent),
},
],
tags: [
{ name: 'category', value: 'transactional' },
],
});
// To multiple addresses
await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: ['user1@example.com', 'user2@example.com'],
subject: 'Team Update',
html: '<p>Hello team!</p>',
});
// Batch send (different emails)
const { data, error } = await resend.batch.send([
{
from: 'Acme <noreply@acme.com>',
to: ['user1@example.com'],
subject: 'Welcome User 1',
html: '<p>Hello User 1!</p>',
},
{
from: 'Acme <noreply@acme.com>',
to: ['user2@example.com'],
subject: 'Welcome User 2',
html: '<p>Hello User 2!</p>',
},
]);
import { readFileSync } from 'fs';
// From file
const attachment = readFileSync('./invoice.pdf');
await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: ['user@example.com'],
subject: 'Your Invoice',
html: '<p>Please find your invoice attached.</p>',
attachments: [
{
filename: 'invoice.pdf',
content: attachment,
},
],
});
// From URL
await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: ['user@example.com'],
subject: 'Your Report',
html: '<p>Please find your report attached.</p>',
attachments: [
{
filename: 'report.pdf',
path: 'https://example.com/reports/report.pdf',
},
],
});
npm install react-email -D
{
"scripts": {
"email": "email dev --dir emails"
}
}
npm run email
Opens at http://localhost:3000 to preview templates.
// app/actions/email.ts
'use server';
import { Resend } from 'resend';
import { WelcomeEmail } from '@/emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(email: string, username: string) {
try {
const { data, error } = await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: [email],
subject: 'Welcome to Acme!',
react: WelcomeEmail({ username, loginUrl: '/login' }),
});
if (error) {
return { success: false, error: error.message };
}
return { success: true, messageId: data?.id };
} catch (error) {
return { success: false, error: 'Failed to send email' };
}
}
// app/api/webhooks/resend/route.ts
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const payload = await req.json();
switch (payload.type) {
case 'email.sent':
console.log('Email sent:', payload.data.email_id);
break;
case 'email.delivered':
console.log('Email delivered:', payload.data.email_id);
break;
case 'email.bounced':
console.log('Email bounced:', payload.data.email_id);
// Handle bounce - update user record
break;
case 'email.complained':
console.log('Spam complaint:', payload.data.email_id);
// Handle complaint - unsubscribe user
break;
}
return NextResponse.json({ received: true });
}
| Mistake | Fix |
|---|---|
| Unverified domain | Verify in Resend dashboard |
| Missing from address | Use verified domain email |
| Not handling errors | Check error response |
| Inline styles missing | Use style objects in React Email |
| Large attachments | Keep under 40MB |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.