Skip to main content

Add Email Notifications to Any SaaS Boilerplate 2026

ยทStarterPick Team
emailresendreact-emailguidenotifications2026

TL;DR

Resend + React Email is the modern email stack for SaaS in 2026. React Email lets you build email templates in TypeScript with previews in the browser. Resend handles delivery with great developer experience. Total setup time: 1-2 days. This guide covers the 5 most important email types for SaaS and their implementation.


Setup

npm install resend @react-email/components @react-email/render
// lib/email.ts
import { Resend } from 'resend';

export const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendEmail({
  to,
  subject,
  react,
}: {
  to: string;
  subject: string;
  react: React.ReactElement;
}) {
  const { data, error } = await resend.emails.send({
    from: process.env.EMAIL_FROM!, // 'YourSaaS <noreply@yoursaas.com>'
    to,
    subject,
    react,
  });

  if (error) {
    console.error('Email send failed:', error);
    throw new Error(`Email failed: ${error.message}`);
  }

  return data;
}

Email Template Components

Create reusable email layout:

// emails/components/EmailLayout.tsx
import {
  Body, Container, Head, Html, Preview, Section,
  Tailwind, Text, Link, Img
} from '@react-email/components';

export function EmailLayout({
  preview,
  children,
}: {
  preview: string;
  children: React.ReactNode;
}) {
  return (
    <Tailwind>
      <Html>
        <Head />
        <Preview>{preview}</Preview>
        <Body className="bg-white font-sans">
          <Container className="mx-auto py-8 px-4 max-w-[600px]">
            <Section className="mb-8">
              <Img
                src="https://yoursaas.com/logo.png"
                width="120"
                height="40"
                alt="YourSaaS"
              />
            </Section>
            <Section className="bg-white rounded-lg border border-gray-200 p-8">
              {children}
            </Section>
            <Section className="mt-8 text-center">
              <Text className="text-gray-400 text-sm">
                ยฉ 2026 YourSaaS. All rights reserved.
              </Text>
              <Link
                href="https://yoursaas.com/unsubscribe"
                className="text-gray-400 text-sm"
              >
                Unsubscribe
              </Link>
            </Section>
          </Container>
        </Body>
      </Html>
    </Tailwind>
  );
}

Email 1: Welcome Email

// emails/WelcomeEmail.tsx
import { Heading, Text, Button, Link } from '@react-email/components';
import { EmailLayout } from './components/EmailLayout';

interface WelcomeEmailProps {
  userName: string;
  dashboardUrl: string;
}

export function WelcomeEmail({ userName, dashboardUrl }: WelcomeEmailProps) {
  return (
    <EmailLayout preview={`Welcome to YourSaaS, ${userName}!`}>
      <Heading className="text-2xl font-bold text-gray-900 mb-4">
        Welcome, {userName}! ๐ŸŽ‰
      </Heading>
      <Text className="text-gray-600 mb-4">
        Thanks for signing up for YourSaaS. You're all set to [do the core value].
      </Text>
      <Text className="text-gray-600 mb-6">
        Here's how to get started:
      </Text>
      <ul>
        <li>Step 1: Complete your profile</li>
        <li>Step 2: Create your first project</li>
        <li>Step 3: Invite your team</li>
      </ul>
      <Button
        href={dashboardUrl}
        className="bg-indigo-600 text-white rounded-lg px-6 py-3 text-sm font-medium"
      >
        Go to Dashboard โ†’
      </Button>
      <Text className="text-gray-400 text-sm mt-6">
        Questions? Reply to this email โ€” we read every one.
      </Text>
    </EmailLayout>
  );
}

// Sending the welcome email
export async function sendWelcomeEmail(user: { email: string; name: string }) {
  return sendEmail({
    to: user.email,
    subject: `Welcome to YourSaaS!`,
    react: (
      <WelcomeEmail
        userName={user.name || 'there'}
        dashboardUrl={`${process.env.NEXTAUTH_URL}/dashboard`}
      />
    ),
  });
}

Email 2: Payment Receipt

// emails/PaymentReceiptEmail.tsx
import { Heading, Text, Section, Row, Column } from '@react-email/components';
import { EmailLayout } from './components/EmailLayout';

interface PaymentReceiptEmailProps {
  userName: string;
  amount: number;
  planName: string;
  invoiceUrl: string;
  nextBillingDate: string;
}

export function PaymentReceiptEmail({
  userName,
  amount,
  planName,
  invoiceUrl,
  nextBillingDate,
}: PaymentReceiptEmailProps) {
  return (
    <EmailLayout preview={`Payment received โ€” $${(amount / 100).toFixed(2)}`}>
      <Heading className="text-2xl font-bold text-gray-900 mb-2">
        Payment Confirmed โœ“
      </Heading>
      <Text className="text-gray-600 mb-6">
        Hi {userName}, your payment has been processed successfully.
      </Text>
      <Section className="bg-gray-50 rounded-lg p-4 mb-6">
        <Row>
          <Column><Text className="text-gray-600">Plan</Text></Column>
          <Column><Text className="text-gray-900 font-medium text-right">{planName}</Text></Column>
        </Row>
        <Row>
          <Column><Text className="text-gray-600">Amount</Text></Column>
          <Column><Text className="text-gray-900 font-medium text-right">${(amount / 100).toFixed(2)}</Text></Column>
        </Row>
        <Row>
          <Column><Text className="text-gray-600">Next billing date</Text></Column>
          <Column><Text className="text-gray-900 font-medium text-right">{nextBillingDate}</Text></Column>
        </Row>
      </Section>
    </EmailLayout>
  );
}

Email 3: Payment Failed (Dunning)

// emails/PaymentFailedEmail.tsx
export function PaymentFailedEmail({
  userName,
  updatePaymentUrl,
  retryDate,
}: {
  userName: string;
  updatePaymentUrl: string;
  retryDate: string;
}) {
  return (
    <EmailLayout preview="Action required: Your payment failed">
      <Heading className="text-2xl font-bold text-red-600 mb-4">
        Payment Failed
      </Heading>
      <Text className="text-gray-600 mb-4">
        Hi {userName}, we couldn't process your payment. Your subscription will
        remain active while we retry.
      </Text>
      <Text className="text-gray-600 mb-6">
        We'll automatically retry on <strong>{retryDate}</strong>. To avoid any
        service interruption, please update your payment method now.
      </Text>
      <Button href={updatePaymentUrl} className="bg-red-600 text-white rounded-lg px-6 py-3">
        Update Payment Method โ†’
      </Button>
    </EmailLayout>
  );
}

Triggering Emails from Stripe Webhooks

// app/api/stripe/webhook/route.ts
case 'invoice.payment_succeeded':
  const invoice = event.data.object as Stripe.Invoice;
  const user = await getUserByStripeCustomerId(invoice.customer as string);

  await sendEmail({
    to: user.email,
    subject: 'Payment confirmed',
    react: (
      <PaymentReceiptEmail
        userName={user.name ?? 'there'}
        amount={invoice.amount_paid}
        planName={getPlanName(invoice.lines.data[0].price?.id ?? '')}
        invoiceUrl: invoice.hosted_invoice_url ?? '',
        nextBillingDate: formatDate(new Date((invoice.lines.data[0].period?.end ?? 0) * 1000)),
      />
    ),
  });
  break;

case 'invoice.payment_failed':
  const failedInvoice = event.data.object as Stripe.Invoice;
  const failedUser = await getUserByStripeCustomerId(failedInvoice.customer as string);

  await sendEmail({
    to: failedUser.email,
    subject: 'โš ๏ธ Action required: Payment failed',
    react: (
      <PaymentFailedEmail
        userName={failedUser.name ?? 'there'}
        updatePaymentUrl={await createPortalUrl(failedUser.stripeCustomerId!)}
        retryDate={formatDate(getNextRetryDate(failedInvoice))}
      />
    ),
  });
  break;

Development Preview

# Preview emails in the browser (no Resend account needed)
npx email dev

# Opens http://localhost:3000 with email preview
# Hot reloads as you edit templates

Time Budget

ComponentDuration
Resend setup + email utility0.5 day
Email layout template0.5 day
Welcome email0.5 day
Payment receipt email0.5 day
Payment failed email0.5 day
Webhook integration0.5 day
Total3 days

Find boilerplates with email systems built-in on StarterPick.

Check out this boilerplate

View ShipFast on StarterPick โ†’

Comments