Skip to Content
⭐ CraftJS is open source. Star on GitHub →
DocsEmail

Email

CraftJS uses Resend  with React Email  for beautiful, type-safe transactional emails.

Features

  • React Components - Write emails as React components
  • Type Safety - Full TypeScript support
  • Previews - Preview emails in development
  • Templates - Pre-built email templates
  • Deliverability - High deliverability with Resend

Configuration

Environment Variables

RESEND_API_KEY="re_..." RESEND_FROM_EMAIL="noreply@yourdomain.com"

Sign up at Resend  and verify your domain for production use.

Email Client

Configure the client in src/lib/email/client.ts:

import { Resend } from "resend" import { env } from "@/env" export const resend = new Resend(env.RESEND_API_KEY) export const fromEmail = env.RESEND_FROM_EMAIL

Creating Email Templates

Basic Template

// lib/email/templates/welcome.tsx import { Html, Head, Body, Container, Section, Text, Button, Img, Link, Hr, } from "@react-email/components" interface WelcomeEmailProps { name: string loginUrl: string } export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) { return ( <Html> <Head /> <Body style={main}> <Container style={container}> <Img src="https://yourapp.com/logo.png" width="48" height="48" alt="CraftJS" /> <Text style={title}>Welcome to CraftJS!</Text> <Text style={text}> Hi {name}, </Text> <Text style={text}> Thanks for signing up! We're excited to have you on board. Get started by exploring your dashboard. </Text> <Section style={buttonContainer}> <Button style={button} href={loginUrl}> Go to Dashboard </Button> </Section> <Hr style={hr} /> <Text style={footer}> If you didn't create an account, you can safely ignore this email. </Text> </Container> </Body> </Html> ) } // Styles 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 title = { fontSize: "24px", fontWeight: "bold", color: "#1a1a1a", marginTop: "24px", } const text = { fontSize: "16px", color: "#4a4a4a", lineHeight: "24px", } const buttonContainer = { textAlign: "center" as const, marginTop: "24px", } const button = { backgroundColor: "#2563eb", color: "#ffffff", padding: "12px 24px", borderRadius: "6px", textDecoration: "none", fontWeight: "500", } const hr = { borderColor: "#e6e6e6", marginTop: "32px", } const footer = { fontSize: "12px", color: "#8898aa", marginTop: "24px", }

Verification Email

// lib/email/templates/verification.tsx import { Html, Head, Body, Container, Section, Text, Button, Code, } from "@react-email/components" interface VerificationEmailProps { verificationUrl: string code?: string } export function VerificationEmail({ verificationUrl, code }: VerificationEmailProps) { return ( <Html> <Head /> <Body style={main}> <Container style={container}> <Text style={title}>Verify your email</Text> <Text style={text}> Click the button below to verify your email address. </Text> <Section style={buttonContainer}> <Button style={button} href={verificationUrl}> Verify Email </Button> </Section> {code && ( <> <Text style={text}> Or enter this code manually: </Text> <Code style={codeStyle}>{code}</Code> </> )} <Text style={footer}> This link will expire in 24 hours. </Text> </Container> </Body> </Html> ) } const codeStyle = { display: "inline-block", padding: "16px 32px", fontSize: "32px", fontWeight: "bold", backgroundColor: "#f4f4f5", borderRadius: "8px", letterSpacing: "4px", } // ... other styles same as above

Password Reset Email

// lib/email/templates/password-reset.tsx import { Html, Head, Body, Container, Text, Button, Section, } from "@react-email/components" interface PasswordResetEmailProps { name: string resetUrl: string } export function PasswordResetEmail({ name, resetUrl }: PasswordResetEmailProps) { return ( <Html> <Head /> <Body style={main}> <Container style={container}> <Text style={title}>Reset your password</Text> <Text style={text}> Hi {name}, </Text> <Text style={text}> We received a request to reset your password. Click the button below to choose a new password. </Text> <Section style={buttonContainer}> <Button style={button} href={resetUrl}> Reset Password </Button> </Section> <Text style={text}> If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged. </Text> <Text style={footer}> This link will expire in 1 hour. </Text> </Container> </Body> </Html> ) }

Sending Emails

Basic Send

import { resend, fromEmail } from "@/lib/email/client" import { WelcomeEmail } from "@/lib/email/templates/welcome" await resend.emails.send({ from: fromEmail, to: "user@example.com", subject: "Welcome to CraftJS!", react: WelcomeEmail({ name: "John", loginUrl: "https://yourapp.com/login", }), })

Send with Attachments

await resend.emails.send({ from: fromEmail, to: "user@example.com", subject: "Your invoice", react: InvoiceEmail({ ... }), attachments: [ { filename: "invoice.pdf", content: pdfBuffer, }, ], })

Send to Multiple Recipients

// CC and BCC await resend.emails.send({ from: fromEmail, to: "user@example.com", cc: ["cc@example.com"], bcc: ["bcc@example.com"], subject: "Team update", react: TeamUpdateEmail({ ... }), }) // Batch send const emails = users.map((user) => ({ from: fromEmail, to: user.email, subject: "Newsletter", react: NewsletterEmail({ name: user.name }), })) await resend.batch.send(emails)

Email Functions

Create helper functions in src/lib/email/send.ts:

import { resend, fromEmail } from "./client" import { WelcomeEmail } from "./templates/welcome" import { VerificationEmail } from "./templates/verification" import { PasswordResetEmail } from "./templates/password-reset" export async function sendWelcomeEmail( to: string, name: string, loginUrl: string ) { return resend.emails.send({ from: `CraftJS <${fromEmail}>`, to, subject: "Welcome to CraftJS!", react: WelcomeEmail({ name, loginUrl }), }) } export async function sendVerificationEmail( to: string, verificationUrl: string ) { return resend.emails.send({ from: `CraftJS <${fromEmail}>`, to, subject: "Verify your email", react: VerificationEmail({ verificationUrl }), }) } export async function sendPasswordResetEmail( to: string, name: string, resetUrl: string ) { return resend.emails.send({ from: `CraftJS <${fromEmail}>`, to, subject: "Reset your password", react: PasswordResetEmail({ name, resetUrl }), }) }

Preview Emails

Development Preview

Create an API route to preview emails:

// app/api/email/preview/route.ts import { NextResponse } from "next/server" import { render } from "@react-email/render" import { WelcomeEmail } from "@/lib/email/templates/welcome" export async function GET(req: Request) { const { searchParams } = new URL(req.url) const template = searchParams.get("template") let html = "" switch (template) { case "welcome": html = await render( WelcomeEmail({ name: "John Doe", loginUrl: "https://example.com/login", }) ) break default: return NextResponse.json({ error: "Template not found" }, { status: 404 }) } return new Response(html, { headers: { "Content-Type": "text/html" }, }) }

Visit /api/email/preview?template=welcome to preview.

Using React Email CLI

# Install React Email CLI pnpm add -D react-email # Start email preview server pnpm email dev

This starts a preview server at http://localhost:3001.

Integration with Auth

Connect email sending to Better Auth:

// lib/auth/server.ts import { sendVerificationEmail, sendPasswordResetEmail } from "@/lib/email/send" export const auth = betterAuth({ // ... emailAndPassword: { enabled: true, requireEmailVerification: true, sendVerificationEmail: async ({ user, url }) => { await sendVerificationEmail(user.email, url) }, sendResetPasswordEmail: async ({ user, url }) => { await sendPasswordResetEmail(user.email, user.name ?? "User", url) }, }, })

Background Email Sending

For better performance, send emails via background jobs:

// trigger/tasks.ts import { task } from "@trigger.dev/sdk/v3" import { sendWelcomeEmail } from "@/lib/email/send" export const sendWelcomeEmailTask = task({ id: "send-welcome-email", run: async (payload: { to: string; name: string; loginUrl: string }) => { await sendWelcomeEmail(payload.to, payload.name, payload.loginUrl) }, }) // Usage await sendWelcomeEmailTask.trigger({ to: user.email, name: user.name, loginUrl: "https://yourapp.com/login", })

Error Handling

import { ResendError } from "resend" try { await resend.emails.send({ from: fromEmail, to: "user@example.com", subject: "Test", react: TestEmail(), }) } catch (error) { if (error instanceof ResendError) { console.error("Resend error:", error.message) if (error.statusCode === 429) { // Rate limited - retry later } } throw error }

Best Practices

Tips for production email:

  1. Verify your domain - Required for production sending
  2. Use a subdomain - e.g., mail.yourdomain.com
  3. Set up DKIM/SPF - Improves deliverability
  4. Test with real emails - Use a test domain
  5. Handle bounces - Set up webhook for bounce notifications
  6. Include unsubscribe links - Required for marketing emails

Next Steps

Last updated on