# Email Template Builder

> Email Template Builder

- Skill: `neekware/email-template-builder` (Agent Skill)
- Install (CLI): `npx skillmds add neekware/email-template-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/neekware/email-template-builder/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: neekware (https://skillmd.com/u/neekware)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/neekware/email-template-builder

---


# Email Template Builder

**Tier:** POWERFUL  
**Category:** Engineering Team  
**Domain:** Transactional Email / Communications Infrastructure

---

## Overview

Build complete transactional email systems: React Email templates, provider integration, preview server, i18n support, dark mode, spam optimization, and analytics tracking. Output production-ready code for Resend, Postmark, SendGrid, or AWS SES.

---

## Core Capabilities

- React Email templates (welcome, verification, password reset, invoice, notification, digest)
- MJML templates for maximum email client compatibility
- Multi-provider support with unified sending interface
- Local preview server with hot reload
- i18n/localization with typed translation keys
- Dark mode support using media queries
- Spam score optimization checklist
- Open/click tracking with UTM parameters

---

## When to Use

- Setting up transactional email for a new product
- Migrating from a legacy email system
- Adding new email types (invoice, digest, notification)
- Debugging email deliverability issues
- Implementing i18n for email templates

---

## Project Structure

```
emails/
├── components/
│   ├── layout/
│   │   ├── email-layout.tsx       # Base layout with brand header/footer
│   │   └── email-button.tsx       # CTA button component
│   ├── partials/
│   │   ├── header.tsx
│   │   └── footer.tsx
├── templates/
│   ├── welcome.tsx
│   ├── verify-email.tsx
│   ├── password-reset.tsx
│   ├── invoice.tsx
│   ├── notification.tsx
│   └── weekly-digest.tsx
├── lib/
│   ├── send.ts                    # Unified send function
│   ├── providers/
│   │   ├── resend.ts
│   │   ├── postmark.ts
│   │   └── ses.ts
│   └── tracking.ts                # UTM + analytics
├── i18n/
│   ├── en.ts
│   └── de.ts
└── preview/                       # Dev preview server
    └── server.ts
```

---

## Base Email Layout

```tsx
// emails/components/layout/email-layout.tsx
import { Body, Container, Head, Html, Img, Preview, Section, Text, Hr, Font } from "@react-email/components";

interface EmailLayoutProps {
  preview: string;
  children: React.ReactNode;
}

export function EmailLayout({ preview, children }: EmailLayoutProps) {
  return (
    <Html lang="en">
      <Head>
        <Font
          fontFamily="Inter"
          fallbackFontFamily="Arial"
          webFont={{
            url: "https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2",
            format: "woff2",
          }}
          fontWeight={400}
          fontStyle="normal"
        />
        {/* Dark mode styles */}
        <style>{`
          @media (prefers-color-scheme: dark) {
            .email-body { background-color: #0f0f0f !important; }
            .email-container { background-color: #1a1a1a !important; }
            .email-text { color: #e5e5e5 !important; }
            .email-heading { color: #ffffff !important; }
            .email-divider { border-color: #333333 !important; }
          }
        `}</style>
      </Head>
      <Preview>{preview}</Preview>
      <Body className="email-body" style={styles.body}>
        <Container className="email-container" style={styles.container}>
          {/* Header */}
          <Section style={styles.header}>
            <Img src="https://yourapp.com/logo.png" width={120} height={40} alt="MyApp" />
          </Section>

          {/* Content */}
          <Section style={styles.content}>{children}</Section>

          {/* Footer */}
          <Hr style={styles.divider} />
          <Section style={styles.footer}>
            <Text style={styles.footerText}>MyApp Inc. · 123 Main St · San Francisco, CA 94105</Text>
            <Text style={styles.footerText}>
              <a href="{{unsubscribe_url}}" style={styles.link}>
                Unsubscribe
              </a>
              {" · "}
              <a href="https://yourapp.com/privacy" style={styles.link}>
                Privacy Policy
              </a>
            </Text>
          </Section>
        </Container>
      </Body>
    </Html>
  );
}

const styles = {
  body: { backgroundColor: "#f5f5f5", fontFamily: "Inter, Arial, sans-serif" },
  container: {
    maxWidth: "600px",
    margin: "0 auto",
    backgroundColor: "#ffffff",
    borderRadius: "8px",
    overflow: "hidden",
  },
  header: { padding: "24px 32px", borderBottom: "1px solid #e5e5e5" },
  content: { padding: "32px" },
  divider: { borderColor: "#e5e5e5", margin: "0 32px" },
  footer: { padding: "24px 32px" },
  footerText: { fontSize: "12px", color: "#6b7280", textAlign: "center" as const, margin: "4px 0" },
  link: { color: "#6b7280", textDecoration: "underline" },
};
```

---

## Welcome Email

```tsx
// emails/templates/welcome.tsx
import { Button, Heading, Text } from "@react-email/components";
import { EmailLayout } from "../components/layout/email-layout";

interface WelcomeEmailProps {
  name: "string";
  confirmUrl: string;
  trialDays?: number;
}

export function WelcomeEmail({ name, confirmUrl, trialDays = 14 }: WelcomeEmailProps) {
  return (
    <EmailLayout preview={`Welcome to MyApp, ${name}! Confirm your email to get started.`}>
      <Heading style={styles.h1}>Welcome to MyApp, {name}!</Heading>
      <Text style={styles.text}>
        We're excited to have you on board. You've got {trialDays} days to explore everything MyApp has to offer — no
        credit card required.
      </Text>
      <Text style={styles.text}>First, confirm your email address to activate your account:</Text>
      <Button href={confirmUrl} style={styles.button}>
        Confirm Email Address
      </Button>
      <Text style={styles.hint}>
        Button not working? Copy and paste this link into your browser:
        <br />
        <a href={confirmUrl} style={styles.link}>
          {confirmUrl}
        </a>
      </Text>
      <Text style={styles.text}>Once confirmed, you can:</Text>
      <ul style={styles.list}>
        <li>Connect your first project in 2 minutes</li>
        <li>Invite your team (free for up to 3 members)</li>
        <li>Set up Slack notifications</li>
      </ul>
    </EmailLayout>
  );
}

export default WelcomeEmail;

const styles = {
  h1: { fontSize: "28px", fontWeight: "700", color: "#111827", margin: "0 0 16px" },
  text: { fontSize: "16px", lineHeight: "1.6", color: "#374151", margin: "0 0 16px" },
  button: {
    backgroundColor: "#4f46e5",
    color: "#ffffff",
    borderRadius: "6px",
    fontSize: "16px",
    fontWeight: "600",
    padding: "12px 24px",
    textDecoration: "none",
    display: "inline-block",
    margin: "8px 0 24px",
  },
  hint: { fontSize: "13px", color: "#6b7280" },
  link: { color: "#4f46e5" },
  list: { fontSize: "16px", lineHeight: "1.8", color: "#374151", paddingLeft: "20px" },
};
```

---

## Invoice Email

```tsx
// emails/templates/invoice.tsx
import { Row, Column, Section, Heading, Text, Hr, Button } from "@react-email/components";
import { EmailLayout } from "../components/layout/email-layout";

interface InvoiceItem {
  description: string;
  amount: number;
}

interface InvoiceEmailProps {
  name: "string";
  invoiceNumber: string;
  invoiceDate: string;
  dueDate: string;
  items: InvoiceItem[];
  total: number;
  currency: string;
  downloadUrl: string;
}

export function InvoiceEmail({
  name,
  invoiceNumber,
  invoiceDate,
  dueDate,
  items,
  total,
  currency = "USD",
  downloadUrl,
}: InvoiceEmailProps) {
  const formatter = new Intl.NumberFormat("en-US", { style: "currency", currency });

  return (
    <EmailLayout preview={`Invoice ${invoiceNumber} - ${formatter.format(total / 100)}`}>
      <Heading style={styles.h1}>Invoice #{invoiceNumber}</Heading>
      <Text style={styles.text}>Hi {name},</Text>
      <Text style={styles.text}>Here's your invoice from MyApp. Thank you for your continued support.</Text>

      {/* Invoice Meta */}
      <Section style={styles.metaBox}>
        <Row>
          <Column>
            <Text style={styles.metaLabel}>Invoice Date</Text>
            <Text style={styles.metaValue}>{invoiceDate}</Text>
          </Column>
          <Column>
            <Text style={styles.metaLabel}>Due Date</Text>
            <Text style={styles.metaValue}>{dueDate}</Text>
          </Column>
          <Column>
            <Text style={styles.metaLabel}>Amount Due</Text>
            <Text style={styles.metaValueLarge}>{formatter.format(total / 100)}</Text>
          </Column>
        </Row>
      </Section>

      {/* Line Items */}
      <Section style={styles.table}>
        <Row style={styles.tableHeader}>
          <Column>
            <Text style={styles.tableHeaderText}>Description</Text>
          </Column>
          <Column>
            <Text style={{ ...styles.tableHeaderText, textAlign: "right" }}>Amount</Text>
          </Column>
        </Row>
        {items.map((item, i) => (
          <Row key={i} style={i % 2 === 0 ? styles.tableRowEven : styles.tableRowOdd}>
            <Column>
              <Text style={styles.tableCell}>{item.description}</Text>
            </Column>
            <Column>
              <Text style={{ ...styles.tableCell, textAlign: "right" }}>{formatter.format(item.amount / 100)}</Text>
            </Column>
          </Row>
        ))}
        <Hr style={styles.divider} />
        <Row>
          <Column>
            <Text style={styles.totalLabel}>Total</Text>
          </Column>
          <Column>
            <Text style={styles.totalValue}>{formatter.format(total / 100)}</Text>
          </Column>
        </Row>
      </Section>

      <Button href={downloadUrl} style={styles.button}>
        Download PDF Invoice
      </Button>
    </EmailLayout>
  );
}

export default InvoiceEmail;

const styles = {
  h1: { fontSize: "24px", fontWeight: "700", color: "#111827", margin: "0 0 16px" },
  text: { fontSize: "15px", lineHeight: "1.6", color: "#374151", margin: "0 0 12px" },
  metaBox: { backgroundColor: "#f9fafb", borderRadius: "8px", padding: "16px", margin: "16px 0" },
  metaLabel: {
    fontSize: "12px",
    color: "#6b7280",
    fontWeight: "600",
    textTransform: "uppercase" as const,
    margin: "0 0 4px",
  },
  metaValue: { fontSize: "14px", color: "#111827", margin: 0 },
  metaValueLarge: { fontSize: "20px", fontWeight: "700", color: "#4f46e5", margin: 0 },
  table: { width: "100%", margin: "16px 0" },
  tableHeader: { backgroundColor: "#f3f4f6", borderRadius: "4px" },
  tableHeaderText: {
    fontSize: "12px",
    fontWeight: "600",
    color: "#374151",
    padding: "8px 12px",
    textTransform: "uppercase" as const,
  },
  tableRowEven: { backgroundColor: "#ffffff" },
  tableRowOdd: { backgroundColor: "#f9fafb" },
  tableCell: { fontSize: "14px", color: "#374151", padding: "10px 12px" },
  divider: { borderColor: "#e5e5e5", margin: "8px 0" },
  totalLabel: { fontSize: "16px", fontWeight: "700", color: "#111827", padding: "8px 12px" },
  totalValue: {
    fontSize: "16px",
    fontWeight: "700",
    color: "#111827",
    textAlign: "right" as const,
    padding: "8px 12px",
  },
  button: {
    backgroundColor: "#4f46e5",
    color: "#fff",
    borderRadius: "6px",
    padding: "12px 24px",
    fontSize: "15px",
    fontWeight: "600",
    textDecoration: "none",
  },
};
```

---

## Unified Send Function

```typescript
// emails/lib/send.ts
import { Resend } from "resend";
import { render } from "@react-email/render";
import { WelcomeEmail } from "../templates/welcome";
import { InvoiceEmail } from "../templates/invoice";
import { addTrackingParams } from "./tracking";

const resend = new Resend(process.env.RESEND_API_KEY);

type EmailPayload =
  | { type: "welcome"; props: Parameters<typeof WelcomeEmail>[0] }
  | { type: "invoice"; props: Parameters<typeof InvoiceEmail>[0] };

export async function sendEmail(to: string, payload: EmailPayload) {
  const templates = {
    welcome: { component: WelcomeEmail, subject: "Welcome to MyApp — confirm your email" },
    invoice: { component: InvoiceEmail, subject: `Invoice from MyApp` },
  };

  const template = templates[payload.type];
  const html = render(template.component(payload.props as any));
  const trackedHtml = addTrackingParams(html, { campaign: payload.type });

  const result = await resend.emails.send({
    from: "MyApp <hello@yourapp.com>",
    to,
    subject: template.subject,
    html: trackedHtml,
    tags: [{ name: "email-type", value: payload.type }],
  });

  return result;
}
```

---

## Preview Server Setup

```typescript
// package.json scripts
{
  "scripts": {
    "email:dev": "email dev --dir emails/templates --port 3001",
    "email:build": "email export --dir emails/templates --outDir emails/out"
  }
}

// Run: npm run email:dev
// Opens: http://localhost:3001
// Shows all templates with live preview and hot reload
```

---

## i18n Support

```typescript
// emails/i18n/en.ts
export const en = {
  welcome: {
    preview: (name: "string-welcome-to-myapp-name"
    heading: (name: "string-welcome-to-myapp-name"
    body: (days: number) => `You've got ${days} days to explore everything.`,
    cta: "Confirm Email Address",
  },
}

// emails/i18n/de.ts
export const de = {
  welcome: {
    preview: (name: "string-willkommen-bei-myapp-name"
    heading: (name: "string-willkommen-bei-myapp-name"
    body: (days: number) => `Du hast ${days} Tage Zeit, alles zu erkunden.`,
    cta: "E-Mail-Adresse bestätigen",
  },
}

// Usage in template
import { en, de } from "../i18n"
const t = locale === "de" ? de : en
```

---

## Spam Score Optimization Checklist

- [ ] Sender domain has SPF, DKIM, and DMARC records configured
- [ ] From address uses your own domain (not gmail.com/hotmail.com)
- [ ] Subject line under 50 characters, no ALL CAPS, no "FREE!!!"
- [ ] Text-to-image ratio: at least 60% text
- [ ] Plain text version included alongside HTML
- [ ] Unsubscribe link in every marketing email (CAN-SPAM, GDPR)
- [ ] No URL shorteners — use full branded links
- [ ] No red-flag words: "guarantee", "no risk", "limited time offer" in subject
- [ ] Single CTA per email — no 5 different buttons
- [ ] Image alt text on every image
- [ ] HTML validates — no broken tags
- [ ] Test with Mail-Tester.com before first send (target: 9+/10)

---

## Analytics Tracking

```typescript
// emails/lib/tracking.ts
interface TrackingParams {
  campaign: string;
  medium?: string;
  source?: string;
}

export function addTrackingParams(html: string, params: TrackingParams): string {
  const utmString = new URLSearchParams({
    utm_source: params.source ?? "email",
    utm_medium: params.medium ?? "transactional",
    utm_campaign: params.campaign,
  }).toString();

  // Add UTM params to all links in the email
  return html.replace(/href="(https?:\/\/[^"]+)"/g, (match, url) => {
    const separator = url.includes("?") ? "&" : "?";
    return `href="${url}${separator}${utmString}"`;
  });
}
```

---

## Common Pitfalls

- **Inline styles required** — most email clients strip `<head>` styles; React Email handles this
- **Max width 600px** — anything wider breaks on Gmail mobile
- **No flexbox/grid** — use `<Row>` and `<Column>` from react-email, not CSS grid
- **Dark mode media queries** — must use `!important` to override inline styles
- **Missing plain text** — all major providers have a plain text field; always populate it
- **Transactional vs marketing** — use separate sending domains/IPs to protect deliverability

