implement-email-template
Access: /template implement-email-template or /t implement-email-template
Creates beautiful, responsive email templates for your MakerKit application using React Email, including transactional emails and marketing campaigns.
What It Does
The implement-email-template command helps you:
- Set up React Email for component-based emails
- Create responsive email templates
- Implement email sending functionality
- Add email preview and testing tools
- Configure email providers (SendGrid, Resend, etc.)
- Create email automation workflows
Usage
bash
/template implement-email-template "Description"
# or
/t implement-email-template "Description"When prompted, specify:
- Email types to create (welcome, invoice, notification, etc.)
- Email service provider
- Brand colors and styling
- Testing email addresses
Prerequisites
- A MakerKit project with authentication
- Email service provider account
- Commercial MakerKit license from MakerKit
What Gets Created
1. React Email Setup
Configure React Email:
bash
# Install dependencies
pnpm add @react-email/components react-email resendjson
// package.json scripts
{
"scripts": {
"email:dev": "email dev --port 3001",
"email:build": "email build",
"email:preview": "email preview"
}
}2. Base Email Template
Reusable email layout:
typescript
// emails/components/base-layout.tsx
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Section,
Text,
} from '@react-email/components';
interface BaseLayoutProps {
preview: string;
heading?: string;
children: React.ReactNode;
}
export function BaseLayout({
preview,
heading,
children
}: BaseLayoutProps) {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={main}>
<Container style={container}>
{/* Header */}
<Section style={header}>
<Img
src="https://your-app.com/logo.png"
width="150"
height="50"
alt="Your App"
style={logo}
/>
</Section>
{/* Content */}
{heading && (
<Heading style={h1}>{heading}</Heading>
)}
{children}
{/* Footer */}
<Section style={footer}>
<Text style={footerText}>
© {new Date().getFullYear()} Your App. All rights reserved.
</Text>
<Link href="https://your-app.com/unsubscribe" style={link}>
Unsubscribe
</Link>
</Section>
</Container>
</Body>
</Html>
);
}
// Styles
const main = {
backgroundColor: '#f6f9fc',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
marginBottom: '64px',
borderRadius: '5px',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
};
const header = {
padding: '24px',
borderBottom: '1px solid #e6ebf1',
};
const logo = {
margin: '0 auto',
};
const h1 = {
color: '#333',
fontSize: '24px',
fontWeight: 'bold',
padding: '0 24px',
margin: '30px 0',
textAlign: 'center' as const,
};
const footer = {
padding: '24px',
borderTop: '1px solid #e6ebf1',
marginTop: '32px',
};
const footerText = {
color: '#8898aa',
fontSize: '12px',
lineHeight: '16px',
textAlign: 'center' as const,
margin: '0',
};
const link = {
color: '#5e6ad2',
fontSize: '12px',
textDecoration: 'underline',
display: 'block',
textAlign: 'center' as const,
marginTop: '8px',
};3. Welcome Email Template
New user onboarding:
typescript
// emails/welcome.tsx
import { Button, Text } from '@react-email/components';
import { BaseLayout } from './components/base-layout';
interface WelcomeEmailProps {
userName: string;
confirmationUrl: string;
}
export default function WelcomeEmail({
userName = 'there',
confirmationUrl = 'https://your-app.com/confirm',
}: WelcomeEmailProps) {
return (
<BaseLayout
preview="Welcome to Your App - Confirm your email"
heading={`Welcome, ${userName}!`}
>
<Text style={paragraph}>
Thanks for signing up for Your App. We're excited to have you on board!
</Text>
<Text style={paragraph}>
To get started, please confirm your email address by clicking the button below:
</Text>
<Section style={buttonContainer}>
<Button href={confirmationUrl} style={button}>
Confirm Email Address
</Button>
</Section>
<Text style={paragraph}>
If you didn't create an account, you can safely ignore this email.
</Text>
<Section style={tipSection}>
<Heading as="h2" style={h2}>
Here's what you can do next:
</Heading>
<ul style={list}>
<li>Complete your profile</li>
<li>Create your first project</li>
<li>Invite team members</li>
<li>Explore our documentation</li>
</ul>
</Section>
</BaseLayout>
);
}
// Additional styles
const paragraph = {
color: '#525f7f',
fontSize: '16px',
lineHeight: '24px',
padding: '0 24px',
margin: '16px 0',
};
const buttonContainer = {
padding: '27px 24px',
textAlign: 'center' as const,
};
const button = {
backgroundColor: '#5e6ad2',
borderRadius: '5px',
color: '#fff',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '12px 20px',
};
const tipSection = {
backgroundColor: '#f6f9fc',
borderRadius: '5px',
padding: '20px',
margin: '0 24px',
};
const h2 = {
color: '#333',
fontSize: '18px',
fontWeight: 'bold',
margin: '0 0 12px',
};
const list = {
color: '#525f7f',
fontSize: '14px',
lineHeight: '24px',
margin: '0',
paddingLeft: '20px',
};4. Invoice Email Template
Billing and payment emails:
typescript
// emails/invoice.tsx
import { Column, Row, Section, Text } from '@react-email/components';
import { BaseLayout } from './components/base-layout';
interface InvoiceEmailProps {
customerName: string;
invoiceNumber: string;
invoiceDate: string;
dueDate: string;
items: Array<{
description: string;
quantity: number;
price: number;
}>;
subtotal: number;
tax: number;
total: number;
paymentUrl: string;
}
export default function InvoiceEmail({
customerName,
invoiceNumber,
invoiceDate,
dueDate,
items,
subtotal,
tax,
total,
paymentUrl,
}: InvoiceEmailProps) {
return (
<BaseLayout
preview={`Invoice ${invoiceNumber} - $${total.toFixed(2)} due`}
heading="Invoice"
>
<Section style={invoiceHeader}>
<Row>
<Column>
<Text style={label}>Invoice Number</Text>
<Text style={value}>{invoiceNumber}</Text>
</Column>
<Column>
<Text style={label}>Invoice Date</Text>
<Text style={value}>{invoiceDate}</Text>
</Column>
<Column>
<Text style={label}>Due Date</Text>
<Text style={value}>{dueDate}</Text>
</Column>
</Row>
</Section>
<Section style={customerSection}>
<Text style={label}>Bill To</Text>
<Text style={value}>{customerName}</Text>
</Section>
<Section style={itemsSection}>
<table style={table}>
<thead>
<tr>
<th style={tableHeader}>Description</th>
<th style={tableHeader}>Qty</th>
<th style={tableHeader}>Price</th>
<th style={tableHeader}>Total</th>
</tr>
</thead>
<tbody>
{items.map((item, i) => (
<tr key={i}>
<td style={tableCell}>{item.description}</td>
<td style={tableCellCenter}>{item.quantity}</td>
<td style={tableCellRight}>
${item.price.toFixed(2)}
</td>
<td style={tableCellRight}>
${(item.quantity * item.price).toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</Section>
<Section style={totalsSection}>
<Row>
<Column style={totalsLabel}>Subtotal</Column>
<Column style={totalsValue}>${subtotal.toFixed(2)}</Column>
</Row>
<Row>
<Column style={totalsLabel}>Tax</Column>
<Column style={totalsValue}>${tax.toFixed(2)}</Column>
</Row>
<Row style={totalRow}>
<Column style={totalLabel}>Total Due</Column>
<Column style={totalValue}>${total.toFixed(2)}</Column>
</Row>
</Section>
<Section style={buttonContainer}>
<Button href={paymentUrl} style={payButton}>
Pay Invoice
</Button>
</Section>
</BaseLayout>
);
}
// Invoice-specific styles
const invoiceHeader = {
padding: '20px 24px',
backgroundColor: '#f6f9fc',
borderRadius: '5px',
margin: '0 24px',
};
const customerSection = {
padding: '20px 24px',
};
const itemsSection = {
padding: '0 24px',
};
const totalsSection = {
padding: '20px 24px',
borderTop: '2px solid #e6ebf1',
margin: '0 24px',
};
const table = {
width: '100%',
borderCollapse: 'collapse' as const,
};
const tableHeader = {
textAlign: 'left' as const,
padding: '12px 0',
borderBottom: '1px solid #e6ebf1',
fontSize: '12px',
fontWeight: 'bold',
color: '#8898aa',
textTransform: 'uppercase' as const,
};
const tableCell = {
padding: '12px 0',
borderBottom: '1px solid #f3f4f6',
fontSize: '14px',
color: '#525f7f',
};
const tableCellCenter = {
...tableCell,
textAlign: 'center' as const,
};
const tableCellRight = {
...tableCell,
textAlign: 'right' as const,
};
const payButton = {
backgroundColor: '#22c55e',
borderRadius: '5px',
color: '#fff',
fontSize: '16px',
fontWeight: 'bold',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'inline-block',
padding: '12px 30px',
};5. Email Service Integration
Send emails using Resend:
typescript
// lib/email/send-email.ts
import { Resend } from 'resend';
import WelcomeEmail from '@/emails/welcome';
import InvoiceEmail from '@/emails/invoice';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail({
to,
userName,
confirmationUrl,
}: {
to: string;
userName: string;
confirmationUrl: string;
}) {
try {
const { data, error } = await resend.emails.send({
from: 'Your App <noreply@your-app.com>',
to,
subject: 'Welcome to Your App',
react: WelcomeEmail({ userName, confirmationUrl }),
});
if (error) {
console.error('Failed to send welcome email:', error);
throw error;
}
return data;
} catch (error) {
console.error('Email send error:', error);
throw error;
}
}
export async function sendInvoiceEmail({
to,
invoice,
}: {
to: string;
invoice: Invoice;
}) {
const { data, error } = await resend.emails.send({
from: 'Your App Billing <billing@your-app.com>',
to,
subject: `Invoice ${invoice.number} - $${invoice.total.toFixed(2)} due`,
react: InvoiceEmail({
customerName: invoice.customerName,
invoiceNumber: invoice.number,
invoiceDate: formatDate(invoice.date),
dueDate: formatDate(invoice.dueDate),
items: invoice.items,
subtotal: invoice.subtotal,
tax: invoice.tax,
total: invoice.total,
paymentUrl: invoice.paymentUrl,
}),
attachments: [
{
filename: `invoice-${invoice.number}.pdf`,
content: await generateInvoicePDF(invoice),
},
],
});
return data;
}6. Email Queue
Queue emails for reliable delivery:
typescript
// lib/email/email-queue.ts
import { Queue } from 'bullmq';
import { Redis } from '@upstash/redis';
const emailQueue = new Queue('emails', {
connection: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
},
});
export async function queueEmail(
type: string,
to: string,
data: any,
options?: {
delay?: number;
priority?: number;
}
) {
await emailQueue.add(
type,
{
to,
data,
timestamp: new Date().toISOString(),
},
{
delay: options?.delay,
priority: options?.priority,
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
}
);
}
// Worker to process emails
import { Worker } from 'bullmq';
const emailWorker = new Worker(
'emails',
async (job) => {
const { type, to, data } = job.data;
switch (type) {
case 'welcome':
await sendWelcomeEmail({ to, ...data });
break;
case 'invoice':
await sendInvoiceEmail({ to, invoice: data });
break;
// Add more email types
}
},
{
connection: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
},
}
);7. Email Templates Management
Admin interface for email templates:
typescript
// app/admin/emails/page.tsx
export default function EmailTemplatesPage() {
const templates = [
{ id: 'welcome', name: 'Welcome Email', category: 'Onboarding' },
{ id: 'invoice', name: 'Invoice', category: 'Billing' },
{ id: 'password-reset', name: 'Password Reset', category: 'Auth' },
{ id: 'team-invite', name: 'Team Invitation', category: 'Teams' },
];
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">Email Templates</h1>
<Button onClick={() => window.open('/emails', '_blank')}>
Open Email Studio
</Button>
</div>
<div className="grid gap-4">
{templates.map((template) => (
<Card key={template.id}>
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>{template.name}</CardTitle>
<Badge variant="secondary">{template.category}</Badge>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => previewEmail(template.id)}
>
Preview
</Button>
<Button
variant="outline"
size="sm"
onClick={() => sendTestEmail(template.id)}
>
Send Test
</Button>
</div>
</div>
</CardHeader>
</Card>
))}
</div>
</div>
);
}8. Email Automation
Trigger emails based on events:
typescript
// lib/email/automations.ts
export async function setupEmailAutomations() {
// New user signup
supabase
.channel('user-signups')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'users',
},
async (payload) => {
await queueEmail('welcome', payload.new.email, {
userName: payload.new.display_name,
confirmationUrl: generateConfirmationUrl(payload.new.id),
});
}
)
.subscribe();
// Subscription created
supabase
.channel('subscriptions')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'subscriptions',
},
async (payload) => {
const user = await getUser(payload.new.user_id);
await queueEmail('subscription-confirmed', user.email, {
planName: payload.new.plan_name,
billingCycle: payload.new.billing_cycle,
});
}
)
.subscribe();
}9. Email Analytics
Track email metrics:
typescript
// lib/email/analytics.ts
export async function trackEmailEvent(
emailId: string,
event: 'sent' | 'delivered' | 'opened' | 'clicked' | 'bounced',
metadata?: any
) {
await supabase.from('email_events').insert({
email_id: emailId,
event,
metadata,
timestamp: new Date().toISOString(),
});
}
// Email tracking pixel
export function EmailTrackingPixel({ emailId }: { emailId: string }) {
return (
<img
src={`https://your-app.com/api/emails/track/${emailId}/open`}
width="1"
height="1"
alt=""
style={{ display: 'block' }}
/>
);
}10. Testing Utilities
Test email templates:
typescript
// test/emails/email.test.tsx
import { render } from '@react-email/components';
import WelcomeEmail from '@/emails/welcome';
describe('Email Templates', () => {
it('renders welcome email correctly', async () => {
const html = render(
WelcomeEmail({
userName: 'John Doe',
confirmationUrl: 'https://example.com/confirm',
})
);
expect(html).toContain('Welcome, John Doe!');
expect(html).toContain('https://example.com/confirm');
});
it('generates valid HTML', async () => {
const html = render(WelcomeEmail({ userName: 'Test' }));
// Validate HTML structure
const dom = new JSDOM(html);
const doc = dom.window.document;
expect(doc.querySelector('h1')).toBeTruthy();
expect(doc.querySelector('a[href]')).toBeTruthy();
});
});Email Best Practices
- Always include plain text version
- Test across email clients
- Keep templates under 100KB
- Use web-safe fonts
- Include unsubscribe links
- Handle bounces and complaints
- Monitor delivery rates
License Requirement
Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.
