Beztack
Emails

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_xxxxxxxxxxxxx

Package 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 exports

Predefined 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:dev

This 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)

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:

  1. Resend Test Mode: Emails aren't really sent
  2. Ethereal Email: Free testing service
  3. MailHog: Local SMTP server

Environment Variables for Testing

# Development
RESEND_API_KEY=re_test_xxxxxxxxxxxxx

# Production
RESEND_API_KEY=re_xxxxxxxxxxxxx

Troubleshooting

Email not sending

  1. Verify your Resend API key
  2. Check error logs
  3. Confirm the domain is verified in Resend

Styles not applying

  1. Make sure to use inline styles
  2. Avoid advanced CSS (flexbox, grid)
  3. Use properties supported by email clients

Email going to spam

  1. Configure SPF, DKIM, and DMARC on your domain
  2. Use a verified domain
  3. Avoid spam words in the subject
  4. Include an unsubscribe link

Resources