Email Templates in the Next.js Supabase SaaS Starter Kit

Create branded email templates with React Email in your Next.js Supabase application. Learn the template architecture, i18n support, and how to build custom templates.

MakerKit uses React Email to create type-safe, responsive email templates. Templates are stored in the @kit/email-templates package and support internationalization out of the box.

Template Architecture

The email templates package is organized as follows:

packages/email-templates/
├── src/
│ ├── components/ # Reusable email components
│ │ ├── body-style.tsx
│ │ ├── content.tsx
│ │ ├── cta-button.tsx
│ │ ├── footer.tsx
│ │ ├── header.tsx
│ │ ├── heading.tsx
│ │ └── wrapper.tsx
│ ├── emails/ # Email templates
│ │ ├── account-delete.email.tsx
│ │ ├── invite.email.tsx
│ │ └── otp.email.tsx
│ ├── lib/
│ │ └── i18n.ts # i18n initialization
│ └── locales/ # Translation files
│ └── en/
│ ├── account-delete-email.json
│ ├── invite-email.json
│ └── otp-email.json

Built-in Templates

MakerKit includes three email templates:

TemplateFunctionPurpose
Team InvitationrenderInviteEmailInvite users to join a team
Account DeletionrenderAccountDeleteEmailConfirm account deletion
OTP CoderenderOtpEmailSend one-time password codes

Using Templates

Each template exports an async render function that returns HTML and a subject line:

import { getMailer } from '@kit/mailers';
import { renderInviteEmail } from '@kit/email-templates';
async function sendInvitation() {
const { html, subject } = await renderInviteEmail({
teamName: 'Acme Corp',
teamLogo: 'https://example.com/logo.png', // optional
inviter: 'John Doe', // can be undefined if inviter is unknown
invitedUserEmail: 'jane@example.com',
link: 'https://app.example.com/invite/abc123',
productName: 'Your App',
language: 'en', // optional, defaults to NEXT_PUBLIC_DEFAULT_LOCALE
});
const mailer = await getMailer();
await mailer.sendEmail({
to: 'jane@example.com',
from: process.env.EMAIL_SENDER!,
subject,
html,
});
}

Creating Custom Templates

To create a new email template:

1. Create the Template File

Create a new file in packages/email-templates/src/emails/:

packages/email-templates/src/emails/welcome.email.tsx

import {
Body,
Head,
Html,
Preview,
Tailwind,
Text,
render,
} from '@react-email/components';
import { BodyStyle } from '../components/body-style';
import { EmailContent } from '../components/content';
import { CtaButton } from '../components/cta-button';
import { EmailFooter } from '../components/footer';
import { EmailHeader } from '../components/header';
import { EmailHeading } from '../components/heading';
import { EmailWrapper } from '../components/wrapper';
import { initializeEmailI18n } from '../lib/i18n';
interface Props {
userName: string;
productName: string;
dashboardUrl: string;
language?: string;
}
export async function renderWelcomeEmail(props: Props) {
const namespace = 'welcome-email';
const { t } = await initializeEmailI18n({
language: props.language,
namespace,
});
const previewText = t(`${namespace}:previewText`, {
productName: props.productName,
});
const subject = t(`${namespace}:subject`, {
productName: props.productName,
});
const html = await render(
<Html>
<Head>
<BodyStyle />
</Head>
<Preview>{previewText}</Preview>
<Tailwind>
<Body>
<EmailWrapper>
<EmailHeader>
<EmailHeading>
{t(`${namespace}:heading`, { productName: props.productName })}
</EmailHeading>
</EmailHeader>
<EmailContent>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:hello`, { userName: props.userName })}
</Text>
<Text className="text-[16px] leading-[24px] text-[#242424]">
{t(`${namespace}:mainText`)}
</Text>
<CtaButton href={props.dashboardUrl}>
{t(`${namespace}:ctaButton`)}
</CtaButton>
</EmailContent>
<EmailFooter>{props.productName}</EmailFooter>
</EmailWrapper>
</Body>
</Tailwind>
</Html>,
);
return {
html,
subject,
};
}

2. Create Translation File

Add a translation file in packages/email-templates/src/locales/en/:

packages/email-templates/src/locales/en/welcome-email.json

{
"subject": "Welcome to {{productName}}",
"previewText": "Welcome to {{productName}} - Let's get started",
"heading": "Welcome to {{productName}}",
"hello": "Hi {{userName}},",
"mainText": "Thanks for signing up! We're excited to have you on board. Click the button below to access your dashboard and get started.",
"ctaButton": "Go to Dashboard"
}

3. Export the Template

Add the export to the package's index file:

packages/email-templates/src/index.ts

export { renderInviteEmail } from './emails/invite.email';
export { renderAccountDeleteEmail } from './emails/account-delete.email';
export { renderOtpEmail } from './emails/otp.email';
export { renderWelcomeEmail } from './emails/welcome.email'; // Add this

4. Use the Template

import { getMailer } from '@kit/mailers';
import { renderWelcomeEmail } from '@kit/email-templates';
async function sendWelcome(user: { email: string; name: string }) {
const { html, subject } = await renderWelcomeEmail({
userName: user.name,
productName: 'Your App',
dashboardUrl: 'https://app.example.com/dashboard',
});
const mailer = await getMailer();
await mailer.sendEmail({
to: user.email,
from: process.env.EMAIL_SENDER!,
subject,
html,
});
}

Reusable Components

MakerKit provides styled components for consistent email design:

EmailWrapper

The outer container with proper styling:

<EmailWrapper>
{/* Email content */}
</EmailWrapper>

EmailHeader and EmailHeading

Header section with title:

<EmailHeader>
<EmailHeading>Your Email Title</EmailHeading>
</EmailHeader>

EmailContent

Main content area with white background:

<EmailContent>
<Text>Your email body text here.</Text>
</EmailContent>

CtaButton

Call-to-action button:

<CtaButton href="https://example.com/action">
Click Here
</CtaButton>

EmailFooter

Footer with product name:

<EmailFooter>Your Product Name</EmailFooter>

Internationalization

Templates support multiple languages through the i18n system. The language is determined by:

  1. The language prop passed to the render function
  2. The NEXT_PUBLIC_DEFAULT_LOCALE environment variable
  3. Falls back to 'en'

Adding a New Language

Create a new locale folder:

packages/email-templates/src/locales/es/

Copy and translate the JSON files:

packages/email-templates/src/locales/es/invite-email.json

{
"subject": "Te han invitado a unirte a {{teamName}}",
"heading": "Únete a {{teamName}} en {{productName}}",
"hello": "Hola {{invitedUserEmail}},",
"mainText": "<strong>{{inviter}}</strong> te ha invitado a unirte al equipo <strong>{{teamName}}</strong> en <strong>{{productName}}</strong>.",
"joinTeam": "Unirse a {{teamName}}",
"copyPasteLink": "O copia y pega este enlace en tu navegador:",
"invitationIntendedFor": "Esta invitación fue enviada a {{invitedUserEmail}}."
}

Pass the language when rendering:

const { html, subject } = await renderInviteEmail({
// ... other props
language: 'es',
});

Styling with Tailwind

React Email supports Tailwind CSS classes. The <Tailwind> wrapper enables Tailwind styling:

<Tailwind>
<Body>
<Text className="text-[16px] font-bold text-blue-600">
Styled text
</Text>
</Body>
</Tailwind>

Note that email clients have limited CSS support. Stick to basic styles:

  • Font sizes and weights
  • Colors
  • Padding and margins
  • Basic flexbox (limited support)

Avoid:

  • CSS Grid
  • Complex transforms
  • CSS variables
  • Advanced selectors

Testing Templates

Preview your templates during development:

Install React Email CLI (if not installed):

pnpm add -D react-email -w

Add a preview script to your package.json:

{
"scripts": {
"email:dev": "email dev -d packages/email-templates/src/emails"
}
}

Finally, run the preview server:

pnpm email:dev

This opens a browser where you can preview and test your email templates.

Next Steps