Resend & React Email
Email system with Resend and React Email templates
Beztack uses Resend as the email provider and React Email to create email templates using React components.
Why Resend and React Email?
We chose this combo because:
- Developer Experience: Write emails with React components
- Type Safety: Fully typed templates
- Preview: Dev preview with React Email
- Performance: Optimized HTML rendering
- Reliability: Resend handles email delivery
- Simplicity: Intuitive and easy-to-use API
- Modern: Current tech stack
Configuration
Environment Variables
Set up your Resend API key in .env:
RESEND_API_KEY=re_xxxxxxxxxxxxxPackage Structure
The @beztack/email package is in packages/email/:
packages/email/
├── emails/ # Email templates
│ ├── welcome.tsx
│ ├── password-reset.tsx
│ ├── subscription-confirmation.tsx
│ └── organization-invitation.tsx
├── lib/
│ ├── interfaces.ts # TypeScript types
│ ├── send.ts # Send logic
│ └── templates.ts # Template registry
└── index.ts # Public exportsPredefined Templates
Beztack includes four predefined email templates:
1. Welcome Email
Automatically sent when a user signs up.
import { sendEmail } from "@beztack/email";
await sendEmail({
type: "welcome",
to: "user@example.com",
data: {
username: "John Doe",
loginUrl: "https://app.example.com/login",
},
});2. Password Reset
For password reset requests.
await sendEmail({
type: "password-reset",
to: "user@example.com",
data: {
username: "John Doe",
resetUrl: "https://app.example.com/reset-password?token=xxx",
},
});3. Subscription Confirmation
Successful subscription confirmation.
await sendEmail({
type: "subscription-confirmation",
to: "user@example.com",
data: {
username: "John Doe",
planName: "Pro",
amount: "$29",
billingPeriod: "monthly",
dashboardUrl: "https://app.example.com/dashboard",
},
});4. Organization Invitation
Invitation to join an organization.
await sendEmail({
type: "organization-invitation",
to: "member@example.com",
data: {
invitedByUsername: "Jane Doe",
invitedByEmail: "jane@example.com",
organizationName: "My Company",
invitationUrl: "https://app.example.com/accept-invitation/xxx",
},
});Create a New Template
1. Create the React Component
Create a new file in packages/email/emails/:
// packages/email/emails/verification.tsx
import {
Body,
Button,
Container,
Head,
Html,
Preview,
Text,
} from "@react-email/components";
type VerificationEmailProps = {
username?: string;
verificationUrl?: string;
};
export const VerificationEmail = ({
username = "User",
verificationUrl = "https://beztack.app/verify",
}: VerificationEmailProps) => (
<Html>
<Head />
<Preview>Verify your email to activate your account</Preview>
<Body style={main}>
<Container style={container}>
<Text style={title}>Hello, {username}!</Text>
<Text style={paragraph}>
Please verify your email address by clicking the button
below:
</Text>
<Button href={verificationUrl} style={button}>
Verify Email
</Button>
<Text style={paragraph}>
If you didn't create this account, you can ignore this email.
</Text>
</Container>
</Body>
</Html>
);
export default VerificationEmail;
// Styles...
const main = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto',
};
const container = {
margin: "0 auto",
padding: "20px 0 48px",
backgroundColor: "#ffffff",
borderRadius: "5px",
maxWidth: "580px",
};
const title = {
fontSize: "24px",
fontWeight: "600",
color: "#374151",
padding: "0 40px",
marginBottom: "30px",
};
const paragraph = {
fontSize: "16px",
color: "#525f7f",
padding: "0 40px",
marginBottom: "20px",
};
const button = {
backgroundColor: "#000",
borderRadius: "5px",
color: "#fff",
fontSize: "16px",
fontWeight: "bold",
textDecoration: "none",
textAlign: "center" as const,
display: "block",
width: "200px",
padding: "14px 0",
margin: "0 auto 20px",
};2. Register the Template
Add your template to packages/email/lib/templates.ts:
import { VerificationEmail } from "../emails/verification";
// Add to EmailType type in interfaces.ts
export type EmailType =
| "welcome"
| "password-reset"
| "subscription-confirmation"
| "organization-invitation"
| "verification"; // ← New type
// Add data in send.ts
type VerificationEmailData = {
username: string;
verificationUrl: string;
};
// Add the case in getEmailTemplate
export function getEmailTemplate(
type: EmailType,
data: EmailDataMap[EmailType]
): ReactElement {
switch (type) {
// ... other cases
case "verification":
return VerificationEmail(data as VerificationEmailData);
default:
throw new Error(`Unknown email type: ${type}`);
}
}3. Update Types
Update SendEmailUnifiedProps in packages/email/lib/interfaces.ts:
export type SendEmailUnifiedProps = {
type: EmailType;
to: string;
data: {
username?: string;
verificationUrl?: string; // ← Add new field
// ... other fields
};
};4. Use the New Template
import { sendEmail } from "@beztack/email";
await sendEmail({
type: "verification",
to: "user@example.com",
data: {
username: "John Doe",
verificationUrl: "https://app.example.com/verify/xxx",
},
});Development Preview
React Email includes a dev server to preview your emails:
cd packages/email
pnpm run email:devThis will open a web interface where you can see all your templates and test them with different data.
Direct Sending with HTML or React
Send with Direct HTML
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: "noreply@beztack.app",
to: "user@example.com",
subject: "My Custom Email",
html: "<h1>Hello world</h1><p>This is an HTML email.</p>",
});Send with Custom React Component
import { render } from "@react-email/render";
import { Resend } from "resend";
import { MyCustomEmail } from "./my-custom-email";
const resend = new Resend(process.env.RESEND_API_KEY);
const html = render(<MyCustomEmail name="John" />);
await resend.emails.send({
from: "noreply@beztack.app",
to: "user@example.com",
subject: "My Custom Email",
html,
});React Email Components
React Email provides components to build emails:
Basic Components
import {
Html, // Main wrapper
Head, // Email <head>
Body, // Email <body>
Container, // Centered container
Preview, // Preview text
Text, // Text paragraph
Heading, // Title
Button, // Clickable button
Link, // Link
Hr, // Horizontal line
Img, // Image
Section, // Section
Column, // Column (for layouts)
Row, // Row (for layouts)
} from "@react-email/components";Complete Example
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Row,
Section,
Text,
} from "@react-email/components";
export const ProductUpdateEmail = ({
username = "User",
productName = "Product",
updateDescription = "We've made some improvements",
imageUrl = "https://example.com/image.png",
ctaUrl = "https://example.com",
}) => (
<Html>
<Head />
<Preview>New update for {productName}</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>New Update Available</Heading>
<Text style={text}>Hello {username},</Text>
<Img
src={imageUrl}
width="600"
height="300"
alt={productName}
style={image}
/>
<Text style={text}>{updateDescription}</Text>
<Section style={buttonContainer}>
<Button href={ctaUrl} style={button}>
View Update
</Button>
</Section>
<Section style={footer}>
<Row>
<Column>
<Link href="https://example.com" style={link}>
Website
</Link>
</Column>
<Column>
<Link href="https://example.com/unsubscribe" style={link}>
Unsubscribe
</Link>
</Column>
</Row>
</Section>
</Container>
</Body>
</Html>
);
const main = { backgroundColor: "#f6f9fc" };
const container = { margin: "0 auto", maxWidth: "600px" };
const h1 = { fontSize: "32px", fontWeight: "bold" };
const text = { fontSize: "16px", lineHeight: "26px" };
const image = { width: "100%", height: "auto" };
const buttonContainer = { textAlign: "center" as const };
const button = {
backgroundColor: "#000",
color: "#fff",
padding: "12px 20px",
borderRadius: "5px",
};
const footer = { marginTop: "30px", color: "#8898aa" };
const link = { color: "#000", textDecoration: "underline" };Best Practices
1. Inline CSS
Email clients require inline styles. React Email does this automatically:
<Text style={{ fontSize: "16px", color: "#333" }}>
Email content
</Text>2. Responsive Design
Use media queries carefully and prefer simple layouts:
const container = {
margin: "0 auto",
width: "100%",
maxWidth: "600px",
};3. Images
Always use absolute URLs for images:
<Img
src="https://cdn.example.com/logo.png"
width="200"
height="50"
alt="Logo"
/>4. Testing
Test your emails in different clients:
- Gmail
- Outlook
- Apple Mail
- Mobile (iOS/Android)
5. Accessibility
- Use alt text for images
- Proper semantic structure
- High color contrast
- Readable font sizes (minimum 16px)
6. Links
Always use complete URLs:
<Button href="https://example.com/action">
Click here
</Button>7. Email Size
Keep your emails under 100KB to avoid delivery issues.
Better Auth Integration
Emails are automatically sent on certain Better Auth events:
// In apps/api/server/utils/auth.ts
import { sendEmail } from "@beztack/email";
export const auth = betterAuth({
// ...
hooks: {
after: createAuthMiddleware(async (ctx) => {
// Send welcome email on sign up
if (ctx.path.includes("/sign-up")) {
const newSession = ctx.context.newSession;
if (newSession) {
await sendEmail({
type: "welcome",
to: newSession.user.email,
data: { username: newSession.user.name },
});
}
}
}),
},
});Local Development
Testing Configuration
During development, you can use services like:
- Resend Test Mode: Emails aren't really sent
- Ethereal Email: Free testing service
- MailHog: Local SMTP server
Environment Variables for Testing
# Development
RESEND_API_KEY=re_test_xxxxxxxxxxxxx
# Production
RESEND_API_KEY=re_xxxxxxxxxxxxxTroubleshooting
Email not sending
- Verify your Resend API key
- Check error logs
- Confirm the domain is verified in Resend
Styles not applying
- Make sure to use inline styles
- Avoid advanced CSS (flexbox, grid)
- Use properties supported by email clients
Email going to spam
- Configure SPF, DKIM, and DMARC on your domain
- Use a verified domain
- Avoid spam words in the subject
- Include an unsubscribe link