Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
351 lines
8.7 KiB
Plaintext
351 lines
8.7 KiB
Plaintext
---
|
|
status: "published"
|
|
label: "Email Templates"
|
|
description: "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."
|
|
title: "Email Templates in the Next.js Supabase SaaS Starter Kit"
|
|
order: 2
|
|
---
|
|
|
|
MakerKit uses [React Email](https://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:
|
|
|
|
| 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:
|
|
|
|
```tsx
|
|
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/`:
|
|
|
|
```tsx {% title="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('previewText', {
|
|
productName: props.productName,
|
|
});
|
|
|
|
const subject = t('subject', {
|
|
productName: props.productName,
|
|
});
|
|
|
|
const html = await render(
|
|
<Html>
|
|
<Head>
|
|
<BodyStyle />
|
|
</Head>
|
|
|
|
<Preview>{previewText}</Preview>
|
|
|
|
<Tailwind>
|
|
<Body>
|
|
<EmailWrapper>
|
|
<EmailHeader>
|
|
<EmailHeading>
|
|
{t('heading', { productName: props.productName })}
|
|
</EmailHeading>
|
|
</EmailHeader>
|
|
|
|
<EmailContent>
|
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
|
{t('hello', { userName: props.userName })}
|
|
</Text>
|
|
|
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
|
{t('mainText')}
|
|
</Text>
|
|
|
|
<CtaButton href={props.dashboardUrl}>
|
|
{t('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/`:
|
|
|
|
```json {% title="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:
|
|
|
|
```tsx {% title="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
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```tsx
|
|
<EmailWrapper>
|
|
{/* Email content */}
|
|
</EmailWrapper>
|
|
```
|
|
|
|
### EmailHeader and EmailHeading
|
|
|
|
Header section with title:
|
|
|
|
```tsx
|
|
<EmailHeader>
|
|
<EmailHeading>Your Email Title</EmailHeading>
|
|
</EmailHeader>
|
|
```
|
|
|
|
### EmailContent
|
|
|
|
Main content area with white background:
|
|
|
|
```tsx
|
|
<EmailContent>
|
|
<Text>Your email body text here.</Text>
|
|
</EmailContent>
|
|
```
|
|
|
|
### CtaButton
|
|
|
|
Call-to-action button:
|
|
|
|
```tsx
|
|
<CtaButton href="https://example.com/action">
|
|
Click Here
|
|
</CtaButton>
|
|
```
|
|
|
|
### EmailFooter
|
|
|
|
Footer with product name:
|
|
|
|
```tsx
|
|
<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. Falls back to `'en'` if no language specified
|
|
|
|
### Adding a New Language
|
|
|
|
Create a new locale folder:
|
|
|
|
```
|
|
packages/email-templates/src/locales/es/
|
|
```
|
|
|
|
Copy and translate the JSON files:
|
|
|
|
```json {% title="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:
|
|
|
|
```tsx
|
|
const { html, subject } = await renderInviteEmail({
|
|
// ... other props
|
|
language: 'es',
|
|
});
|
|
```
|
|
|
|
## Styling with Tailwind
|
|
|
|
React Email supports Tailwind CSS classes. The `<Tailwind>` wrapper enables Tailwind styling:
|
|
|
|
```tsx
|
|
<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
|
|
|
|
Please use the Dev Tool to see how emails look like.
|
|
|
|
## Next Steps
|
|
|
|
- [Send emails](/docs/next-supabase-turbo/sending-emails) using your templates
|
|
- [Configure Supabase Auth emails](/docs/next-supabase-turbo/authentication-emails) with custom templates
|
|
- [Test emails locally](/docs/next-supabase-turbo/emails/inbucket) with Mailpit
|