Resend sends transactional email with a developer-friendly API — new Resend(apiKey) creates the client. resend.emails.send({ from, to, subject, react: <Template /> }) renders a React component to HTML and sends it. React Email provides building blocks: Html, Head, Body, Container, Section, Row, Column, Text, Button, Img, Hr, Link, and Preview. All styles must be inline objects since email clients strip <style> tags. resend.emails.send({ attachments: [{ filename, content }] }) handles file attachments. resend.batch.send([...]) sends multiple emails in one API call. Webhooks deliver email.sent, email.delivered, email.opened, and email.bounced events. react-email CLI with email dev server previews templates at localhost with live reload. Claude Code generates React Email templates, Resend API integration, welcome email sequences, order confirmation templates, and webhook handlers for email delivery tracking.
CLAUDE.md for Resend
## Resend Stack
- Version: resend >= 4.0, @react-email/components >= 0.0.21
- Client: const resend = new Resend(process.env.RESEND_API_KEY)
- Send: resend.emails.send({ from: "[email protected]", to, subject, react: <Email /> })
- Components: Html/Head/Body/Container/Section/Text/Button/Img/Hr/Link/Preview
- Styles: inline style objects only — email clients strip <style> tags
- Preview: add <Preview> text for inbox snippet (shows before subject is opened)
- Dev: npx react-email dev — preview at localhost:3000
- Batch: resend.batch.send(emailsArray) — up to 100 emails per call
Order Confirmation Email
// emails/OrderConfirmationEmail.tsx — React Email template
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Row,
Column,
Text,
Button,
Img,
Hr,
Link,
} from "@react-email/components"
interface OrderItem {
name: string
quantity: number
priceCents: number
imageUrl?: string
}
interface OrderConfirmationProps {
customerName: string
orderNumber: string
orderDate: string
items: OrderItem[]
subtotalCents: number
shippingCents: number
totalCents: number
trackingUrl?: string
shippingAddress: {
line1: string
city: string
state: string
zip: string
}
}
const fontFamily = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"
export function OrderConfirmationEmail({
customerName,
orderNumber,
orderDate,
items,
subtotalCents,
shippingCents,
totalCents,
trackingUrl,
shippingAddress,
}: OrderConfirmationProps) {
const previewText = `Your order #${orderNumber} is confirmed — $${(totalCents / 100).toFixed(2)}`
return (
<Html lang="en">
<Head />
<Preview>{previewText}</Preview>
<Body style={{ backgroundColor: "#f8fafc", fontFamily, margin: 0, padding: 0 }}>
<Container style={{ maxWidth: 600, margin: "0 auto", padding: "40px 20px" }}>
{/* Header */}
<Section style={{ backgroundColor: "#ffffff", borderRadius: "12px 12px 0 0", padding: "32px 40px 24px" }}>
<Text style={{ fontSize: 24, fontWeight: 700, color: "#0f172a", margin: 0 }}>
Your Company
</Text>
</Section>
{/* Hero */}
<Section style={{ backgroundColor: "#3b82f6", padding: "24px 40px" }}>
<Text style={{ fontSize: 22, fontWeight: 700, color: "#ffffff", margin: "0 0 4px" }}>
Order Confirmed! 🎉
</Text>
<Text style={{ fontSize: 14, color: "#bfdbfe", margin: 0 }}>
Hi {customerName}, we've received your order and it's being processed.
</Text>
</Section>
{/* Order details */}
<Section style={{ backgroundColor: "#ffffff", padding: "24px 40px" }}>
<Row>
<Column>
<Text style={{ fontSize: 12, color: "#64748b", margin: "0 0 2px", textTransform: "uppercase" }}>
Order Number
</Text>
<Text style={{ fontSize: 16, fontWeight: 600, color: "#0f172a", margin: 0 }}>
#{orderNumber}
</Text>
</Column>
<Column>
<Text style={{ fontSize: 12, color: "#64748b", margin: "0 0 2px", textTransform: "uppercase" }}>
Order Date
</Text>
<Text style={{ fontSize: 16, fontWeight: 600, color: "#0f172a", margin: 0 }}>
{orderDate}
</Text>
</Column>
</Row>
</Section>
<Hr style={{ borderColor: "#e2e8f0", margin: 0 }} />
{/* Items */}
<Section style={{ backgroundColor: "#ffffff", padding: "24px 40px" }}>
<Text style={{ fontSize: 14, fontWeight: 600, color: "#64748b", textTransform: "uppercase", margin: "0 0 16px" }}>
Items Ordered
</Text>
{items.map((item, i) => (
<Row key={i} style={{ paddingBottom: 16 }}>
<Column style={{ width: 48 }}>
{item.imageUrl && (
<Img
src={item.imageUrl}
alt={item.name}
width={40}
height={40}
style={{ borderRadius: 6, border: "1px solid #e2e8f0" }}
/>
)}
</Column>
<Column style={{ paddingLeft: 12 }}>
<Text style={{ fontSize: 14, fontWeight: 500, color: "#0f172a", margin: "0 0 2px" }}>
{item.name}
</Text>
<Text style={{ fontSize: 12, color: "#64748b", margin: 0 }}>
Qty: {item.quantity}
</Text>
</Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ fontSize: 14, fontWeight: 600, color: "#0f172a", margin: 0 }}>
${((item.priceCents * item.quantity) / 100).toFixed(2)}
</Text>
</Column>
</Row>
))}
<Hr style={{ borderColor: "#e2e8f0" }} />
{/* Totals */}
<Row style={{ marginBottom: 4 }}>
<Column><Text style={{ fontSize: 13, color: "#64748b", margin: 0 }}>Subtotal</Text></Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ fontSize: 13, color: "#0f172a", margin: 0 }}>
${(subtotalCents / 100).toFixed(2)}
</Text>
</Column>
</Row>
<Row style={{ marginBottom: 4 }}>
<Column><Text style={{ fontSize: 13, color: "#64748b", margin: 0 }}>Shipping</Text></Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ fontSize: 13, color: "#0f172a", margin: 0 }}>
{shippingCents === 0 ? "Free" : `$${(shippingCents / 100).toFixed(2)}`}
</Text>
</Column>
</Row>
<Row>
<Column><Text style={{ fontSize: 15, fontWeight: 700, color: "#0f172a", margin: "8px 0 0" }}>Total</Text></Column>
<Column style={{ textAlign: "right" }}>
<Text style={{ fontSize: 15, fontWeight: 700, color: "#3b82f6", margin: "8px 0 0" }}>
${(totalCents / 100).toFixed(2)}
</Text>
</Column>
</Row>
</Section>
<Hr style={{ borderColor: "#e2e8f0", margin: 0 }} />
{/* Shipping address */}
<Section style={{ backgroundColor: "#ffffff", padding: "20px 40px" }}>
<Text style={{ fontSize: 12, color: "#64748b", textTransform: "uppercase", margin: "0 0 8px" }}>
Shipping To
</Text>
<Text style={{ fontSize: 14, color: "#0f172a", lineHeight: "1.6", margin: 0 }}>
{shippingAddress.line1}<br />
{shippingAddress.city}, {shippingAddress.state} {shippingAddress.zip}
</Text>
</Section>
{/* CTA */}
<Section style={{ backgroundColor: "#ffffff", borderRadius: "0 0 12px 12px", padding: "16px 40px 32px", textAlign: "center" }}>
{trackingUrl ? (
<Button
href={trackingUrl}
style={{
backgroundColor: "#3b82f6",
color: "#ffffff",
padding: "12px 32px",
borderRadius: 8,
fontSize: 14,
fontWeight: 600,
textDecoration: "none",
display: "inline-block",
}}
>
Track Your Order
</Button>
) : (
<Text style={{ fontSize: 13, color: "#64748b", margin: 0 }}>
You'll receive a shipping confirmation when your order ships.
</Text>
)}
</Section>
{/* Footer */}
<Section style={{ padding: "24px 0 0", textAlign: "center" }}>
<Text style={{ fontSize: 12, color: "#94a3b8", margin: "0 0 8px" }}>
Questions? <Link href="mailto:[email protected]" style={{ color: "#3b82f6" }}>Contact Support</Link>
</Text>
<Text style={{ fontSize: 11, color: "#cbd5e1", margin: 0 }}>
© 2024 Your Company Inc. · 123 Main St, San Francisco, CA 94105
</Text>
</Section>
</Container>
</Body>
</Html>
)
}
export default OrderConfirmationEmail
Resend Send Integration
// lib/email.ts — email sending service
import { Resend } from "resend"
import { OrderConfirmationEmail } from "@/emails/OrderConfirmationEmail"
import { WelcomeEmail } from "@/emails/WelcomeEmail"
import { PasswordResetEmail } from "@/emails/PasswordResetEmail"
const resend = new Resend(process.env.RESEND_API_KEY)
const FROM = "Your Company <[email protected]>"
export async function sendOrderConfirmation(params: {
to: string
customerName: string
order: {
number: string
date: string
items: Parameters<typeof OrderConfirmationEmail>[0]["items"]
subtotalCents: number
shippingCents: number
totalCents: number
trackingUrl?: string
shippingAddress: Parameters<typeof OrderConfirmationEmail>[0]["shippingAddress"]
}
}) {
const { data, error } = await resend.emails.send({
from: FROM,
to: params.to,
subject: `Order Confirmed — #${params.order.number}`,
react: OrderConfirmationEmail({
customerName: params.customerName,
orderNumber: params.order.number,
orderDate: params.order.date,
items: params.order.items,
subtotalCents: params.order.subtotalCents,
shippingCents: params.order.shippingCents,
totalCents: params.order.totalCents,
trackingUrl: params.order.trackingUrl,
shippingAddress: params.order.shippingAddress,
}),
})
if (error) throw new Error(`Failed to send order confirmation: ${error.message}`)
return data
}
export async function sendBulkAnnouncement(
recipients: { email: string; name: string }[],
announcement: { subject: string; body: string }
) {
const emails = recipients.map(r => ({
from: FROM,
to: r.email,
subject: announcement.subject,
html: `<p>Hi ${r.name},</p><p>${announcement.body}</p>`,
}))
// Batch up to 100 at a time
const results = []
for (let i = 0; i < emails.length; i += 100) {
const batch = emails.slice(i, i + 100)
const { data, error } = await resend.batch.send(batch)
if (error) console.error(`Batch ${i / 100} failed:`, error)
if (data) results.push(...data)
}
return results
}
For the SendGrid alternative when high-volume transactional email, advanced suppression lists, IP warming, and dedicated IP addresses are required — SendGrid has enterprise deliverability features but requires HTML string templates rather than React components, see the email platform comparison for volume-scale sending. For the Postmark alternative when specialized transactional email with the highest delivery speeds, a strict no-bulk-email policy, and detailed message streams for transactional vs broadcast separation are needed — Postmark has an excellent deliverability reputation and message stream architecture, see the transactional email guide. The Claude Skills 360 bundle includes Resend skill sets covering React Email templates, order confirmations, and batch sending. Start with the free tier to try email generation.