SecureStartKit
SecurityFeaturesPricingDocsBlogChangelog
Sign inBuy Now
Getting Started
Installation
Configuration
Deployment

Components

  • Hero
  • Pricing
  • Features

Features

  • Authentication
  • Payments
  • Emails
  • Database
  • Blog
  • Security Headers
  • Claude Code Skills

Recipes

  • Add a Server Action
  • Add a Database Table
  • Add an OAuth Provider
  • Add an Email Template
  • Customize the Auth Flow
  • Add an Admin Metric

Add an Email Template

Create a React Email template, add a typed send helper in lib/resend/send.ts, trigger it from a Server Action or webhook.

What you are building

A new transactional email template (e.g., team invitation, weekly digest, password-change notification). The template is a React component, the send helper is a typed function in lib/resend/send.ts, and the trigger is a Server Action or webhook handler.

Step 1: create the template

Templates live in emails/. Use React Email components for cross-client compatibility (Gmail, Outlook, Apple Mail, mobile clients). Standard HTML and CSS often render inconsistently in email clients; React Email components are pre-tested.

// emails/team-invite.tsx
import {
  Html,
  Head,
  Body,
  Container,
  Heading,
  Text,
  Button,
  Hr,
} from '@react-email/components'

interface TeamInviteProps {
  inviterName: string
  teamName: string
  acceptUrl: string
}

export default function TeamInvite({
  inviterName,
  teamName,
  acceptUrl,
}: TeamInviteProps) {
  return (
    <Html>
      <Head />
      <Body style={{ fontFamily: 'system-ui, sans-serif', padding: '20px' }}>
        <Container style={{ maxWidth: '600px' }}>
          <Heading>You are invited to {teamName}</Heading>
          <Text>
            {inviterName} invited you to join the {teamName} workspace on
            SecureStartKit.
          </Text>
          <Button
            href={acceptUrl}
            style={{
              backgroundColor: '#6366f1',
              color: '#ffffff',
              padding: '12px 24px',
              borderRadius: '6px',
              textDecoration: 'none',
            }}
          >
            Accept invitation
          </Button>
          <Hr style={{ margin: '32px 0' }} />
          <Text style={{ color: '#666', fontSize: '12px' }}>
            If you were not expecting this invitation, you can safely ignore
            this email.
          </Text>
        </Container>
      </Body>
    </Html>
  )
}

Two things to know about email HTML:

  • Inline styles are required. Most email clients strip <style> tags. Style attributes on each element are the safe path.
  • Use system fonts. Custom web fonts work in some clients and fail silently in others. system-ui, sans-serif renders consistently everywhere.

Step 2: preview the template

Boot the React Email dev server:

npx email dev

This opens http://localhost:3001 with every template in emails/ rendered using mocked props. Iterate on the design without sending real email. Add multiple Props permutations at the bottom of your template file (e.g., long names, missing fields, edge cases) so the preview covers the failure modes too.

Step 3: add a typed send helper

Add a function to lib/resend/send.ts:

// lib/resend/send.ts
import TeamInvite from '@/emails/team-invite'
import { resend } from './client'
import config from '@/config'

interface SendTeamInviteParams {
  to: string
  inviterName: string
  teamName: string
  acceptUrl: string
}

export async function sendTeamInviteEmail(params: SendTeamInviteParams) {
  const { data, error } = await resend.emails.send({
    from: config.email.from,
    to: params.to,
    subject: `${params.inviterName} invited you to ${params.teamName}`,
    react: TeamInvite({
      inviterName: params.inviterName,
      teamName: params.teamName,
      acceptUrl: params.acceptUrl,
    }),
  })

  if (error) {
    console.error('Failed to send team invite email:', error)
    return { error: 'Failed to send invite email' }
  }

  return { data }
}

The helper is typed against the template's props, so a missing field fails at the type level rather than at the SMTP layer. Return a generic error to the caller; log the underlying Resend error server-side.

Step 4: trigger from a Server Action or webhook

// actions/team.ts
'use server'

import { sendTeamInviteEmail } from '@/lib/resend/send'

export async function inviteTeamMember(/* ... */) {
  // ... validate input, get user, insert invite row ...

  const inviteUrl = `${process.env.NEXT_PUBLIC_APP_URL}/team/accept?token=${token}`

  await sendTeamInviteEmail({
    to: parsed.data.email,
    inviterName: user.user_metadata?.full_name || user.email!,
    teamName: team.name,
    acceptUrl: inviteUrl,
  })

  return { success: true }
}

For parallel sends (e.g., delivery email to the buyer + notification to the admin), use Promise.all so a slow recipient does not delay the other:

await Promise.all([
  sendPurchaseDeliveryEmail(buyerEmail, name, productId),
  sendPurchaseNotificationEmail(buyerEmail, name, productId, amount),
])

The Stripe webhook handler uses this pattern for the purchase delivery + notification pair.

Domain verification

For real email to reach inboxes (not the Resend onboarding sender), verify your domain in the Resend dashboard. Resend will ask you to add DNS records (TXT for SPF, DKIM, DMARC). The domain stays in "Pending verification" until DNS propagates; usually 5-30 minutes. Until verification, your from address defaults to Resend's shared sender and your deliverability suffers.

For the deeper deliverability fundamentals (SPF, DKIM, DMARC, sender reputation), see How to send emails in Next.js with React Email and Resend (2026).

Common mistakes

  • Sending email from a Client Component. The Resend API key is server-only. Sends must run in a Server Action, an API route, or a webhook handler. A 'use client' file that imports resend will leak the key into the browser bundle.
  • Not handling the error case. Resend can return errors (invalid to, suppression list, rate limit). Always check the error in the response and return a generic error to the caller.
  • Hardcoding the from address in the template. Use config.email.from so changing your sending address only touches config.ts, not every template.
  • Email without a plain-text fallback. React Email auto-generates a plain-text version, but only if the rendered HTML is parseable. Test that the plain-text version is sensible by viewing the source in Gmail or your email client.

What to read next

  • Emails feature docs for the broader email surface and the included templates.
  • Add a Server Action for the canonical pattern that triggers most emails.