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.jsonBuilt-in Templates
MakerKit includes three email templates:
| Template | Function | Purpose |
|---|---|---|
| Team Invitation | renderInviteEmail | Invite users to join a team |
| Account Deletion | renderAccountDeleteEmail | Confirm account deletion |
| OTP Code | renderOtpEmail | Send 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 this4. 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:
- The
languageprop passed to the render function - The
NEXT_PUBLIC_DEFAULT_LOCALEenvironment variable - 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 -wAdd 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:devThis opens a browser where you can preview and test your email templates.
Monorepo Considerations
The React Email dev server may have issues resolving monorepo dependencies like @kit/i18n. If you encounter import errors, you may need to build the dependent packages first or use a simpler template without i18n for preview purposes.
Next Steps
- Send emails using your templates
- Configure Supabase Auth emails with custom templates
- Test emails locally with Mailpit