Skip to content

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 resend
json
// 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

  1. Always include plain text version
  2. Test across email clients
  3. Keep templates under 100KB
  4. Use web-safe fonts
  5. Include unsubscribe links
  6. Handle bounces and complaints
  7. 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.

Built with ❤️ for the AI Coding community, by Praney Behl