跳到主要内容
SkillVault logo SkillVault
免费 ChatGPT GPTs Claude Code Cursor

email-template-builder

Build complete transactional email systems: React Email templates, provider integration (Resend, Postmark, SendGrid, AWS SES), preview server, i18n support, dark mode, spam optimization, analytics tracking. Use when adding transactional email to a new product, migrating between email providers, refactoring legacy email templates for accessibility, or adding internationalization to existing templates.

0 次下载 0 次浏览 更新于 Oct 31, 2026 v1.0.0
email-template-builder skill cover

下方技能配置(提示词、变量、安装步骤)保留英文原文。.aiskill 文件直接面向 AI 智能体,统一使用英文以确保跨平台行为一致。

概述

Build complete transactional email systems: React Email templates, provider integration (Resend, Postmark, SendGrid, AWS SES), preview server, i18n support, dark mode, spam optimization, analytics tracking. Use when adding transactional email to a new product, migrating between email providers, refactoring legacy email templates for accessibility, or adding internationalization to existing templates.

Source: https://github.com/alirezarezvani/claude-skills · MIT · © 2025 Alireza Rezvani

系统提示词

定义智能体角色、规则与输出格式的指令块。

system-prompt.txt
# 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

用户提示词模板

填充变量后发送,即可运行该技能: {{variables}}

user-template.txt

变量

名称 必填 说明 示例

完整技能配置

标准 .aiskill 文件,提供 JSON 与 YAML 两种格式。

email-template-builder.aiskill (JSON)
{
  "format": "aiskill/v1",
  "skill": {
    "name": "email-template-builder",
    "slug": "email-template-builder",
    "version": "1.0.0",
    "description": "Build complete transactional email systems: React Email templates, provider integration (Resend, Postmark, SendGrid, AWS SES), preview server, i18n support, dark mode, spam optimization, analytics tracking. Use when adding transactional email to a new product, migrating between email providers, refactoring legacy email templates for accessibility, or adding internationalization to existing templates.",
    "category": "Design",
    "tags": [
      "design-creative",
      "email",
      "frontend"
    ],
    "platforms": [
      "chatgpt",
      "claude-code",
      "cursor"
    ],
    "tested_environment": "Agent Skills convention (SKILL.md) — works with Claude Code, Cursor, ChatGPT/Codex and compatible agents",
    "license": "MIT"
  },
  "prompts": {
    "system": "# Email Template Builder\r\n\r\n**Tier:** POWERFUL  \r\n**Category:** Engineering Team  \r\n**Domain:** Transactional Email / Communications Infrastructure\r\n\r\n---\r\n\r\n## Overview\r\n\r\nBuild 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.\r\n\r\n---\r\n\r\n## Core Capabilities\r\n\r\n- React Email templates (welcome, verification, password reset, invoice, notification, digest)\r\n- MJML templates for maximum email client compatibility\r\n- Multi-provider support with unified sending interface\r\n- Local preview server with hot reload\r\n- i18n/localization with typed translation keys\r\n- Dark mode support using media queries\r\n- Spam score optimization checklist\r\n- Open/click tracking with UTM parameters\r\n\r\n---\r\n\r\n## When to Use\r\n\r\n- Setting up transactional email for a new product\r\n- Migrating from a legacy email system\r\n- Adding new email types (invoice, digest, notification)\r\n- Debugging email deliverability issues\r\n- Implementing i18n for email templates\r\n\r\n---\r\n\r\n## Project Structure\r\n\r\n```\r\nemails/\r\n├── components/\r\n│   ├── layout/\r\n│   │   ├── email-layout.tsx       # Base layout with brand header/footer\r\n│   │   └── email-button.tsx       # CTA button component\r\n│   ├── partials/\r\n│   │   ├── header.tsx\r\n│   │   └── footer.tsx\r\n├── templates/\r\n│   ├── welcome.tsx\r\n│   ├── verify-email.tsx\r\n│   ├── password-reset.tsx\r\n│   ├── invoice.tsx\r\n│   ├── notification.tsx\r\n│   └── weekly-digest.tsx\r\n├── lib/\r\n│   ├── send.ts                    # Unified send function\r\n│   ├── providers/\r\n│   │   ├── resend.ts\r\n│   │   ├── postmark.ts\r\n│   │   └── ses.ts\r\n│   └── tracking.ts                # UTM + analytics\r\n├── i18n/\r\n│   ├── en.ts\r\n│   └── de.ts\r\n└── preview/                       # Dev preview server\r\n    └── server.ts\r\n```\r\n\r\n---\r\n\r\n## Base Email Layout\r\n\r\n```tsx\r\n// emails/components/layout/email-layout.tsx\r\nimport {\r\n  Body, Container, Head, Html, Img, Preview, Section, Text, Hr, Font\r\n} from \"@react-email/components\"\r\n\r\ninterface EmailLayoutProps {\r\n  preview: string\r\n  children: React.ReactNode\r\n}\r\n\r\nexport function EmailLayout({ preview, children }: EmailLayoutProps) {\r\n  return (\r\n    <Html lang=\"en\">\r\n      <Head>\r\n        <Font\r\n          fontFamily=\"Inter\"\r\n          fallbackFontFamily=\"Arial\"\r\n          webFont={{ url: \"https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2\", format: \"woff2\" }}\r\n          fontWeight={400}\r\n          fontStyle=\"normal\"\r\n        />\r\n        {/* Dark mode styles */}\r\n        <style>{`\r\n          @media (prefers-color-scheme: dark) {\r\n            .email-body { background-color: #0f0f0f !important; }\r\n            .email-container { background-color: #1a1a1a !important; }\r\n            .email-text { color: #e5e5e5 !important; }\r\n            .email-heading { color: #ffffff !important; }\r\n            .email-divider { border-color: #333333 !important; }\r\n          }\r\n        `}</style>\r\n      </Head>\r\n      <Preview>{preview}</Preview>\r\n      <Body className=\"email-body\" style={styles.body}>\r\n        <Container className=\"email-container\" style={styles.container}>\r\n          {/* Header */}\r\n          <Section style={styles.header}>\r\n            <Img src=\"https://yourapp.com/logo.png\" width={120} height={40} alt=\"MyApp\" />\r\n          </Section>\r\n          \r\n          {/* Content */}\r\n          <Section style={styles.content}>\r\n            {children}\r\n          </Section>\r\n          \r\n          {/* Footer */}\r\n          <Hr style={styles.divider} />\r\n          <Section style={styles.footer}>\r\n            <Text style={styles.footerText}>\r\n              MyApp Inc. · 123 Main St · San Francisco, CA 94105\r\n            </Text>\r\n            <Text style={styles.footerText}>\r\n              <a href=\"{{unsubscribe_url}}\" style={styles.link}>Unsubscribe</a>\r\n              {\" · \"}\r\n              <a href=\"https://yourapp.com/privacy\" style={styles.link}>Privacy Policy</a>\r\n            </Text>\r\n          </Section>\r\n        </Container>\r\n      </Body>\r\n    </Html>\r\n  )\r\n}\r\n\r\nconst styles = {\r\n  body: { backgroundColor: \"#f5f5f5\", fontFamily: \"Inter, Arial, sans-serif\" },\r\n  container: { maxWidth: \"600px\", margin: \"0 auto\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", overflow: \"hidden\" },\r\n  header: { padding: \"24px 32px\", borderBottom: \"1px solid #e5e5e5\" },\r\n  content: { padding: \"32px\" },\r\n  divider: { borderColor: \"#e5e5e5\", margin: \"0 32px\" },\r\n  footer: { padding: \"24px 32px\" },\r\n  footerText: { fontSize: \"12px\", color: \"#6b7280\", textAlign: \"center\" as const, margin: \"4px 0\" },\r\n  link: { color: \"#6b7280\", textDecoration: \"underline\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Welcome Email\r\n\r\n```tsx\r\n// emails/templates/welcome.tsx\r\nimport { Button, Heading, Text } from \"@react-email/components\"\r\nimport { EmailLayout } from \"../components/layout/email-layout\"\r\n\r\ninterface WelcomeEmailProps {\r\n  name: \"string\"\r\n  confirmUrl: string\r\n  trialDays?: number\r\n}\r\n\r\nexport function WelcomeEmail({ name, confirmUrl, trialDays = 14 }: WelcomeEmailProps) {\r\n  return (\r\n    <EmailLayout preview={`Welcome to MyApp, ${name}! Confirm your email to get started.`}>\r\n      <Heading style={styles.h1}>Welcome to MyApp, {name}!</Heading>\r\n      <Text style={styles.text}>\r\n        We're excited to have you on board. You've got {trialDays} days to explore everything MyApp has to offer — no credit card required.\r\n      </Text>\r\n      <Text style={styles.text}>\r\n        First, confirm your email address to activate your account:\r\n      </Text>\r\n      <Button href={confirmUrl} style={styles.button}>\r\n        Confirm Email Address\r\n      </Button>\r\n      <Text style={styles.hint}>\r\n        Button not working? Copy and paste this link into your browser:\r\n        <br />\r\n        <a href={confirmUrl} style={styles.link}>{confirmUrl}</a>\r\n      </Text>\r\n      <Text style={styles.text}>\r\n        Once confirmed, you can:\r\n      </Text>\r\n      <ul style={styles.list}>\r\n        <li>Connect your first project in 2 minutes</li>\r\n        <li>Invite your team (free for up to 3 members)</li>\r\n        <li>Set up Slack notifications</li>\r\n      </ul>\r\n    </EmailLayout>\r\n  )\r\n}\r\n\r\nexport default WelcomeEmail\r\n\r\nconst styles = {\r\n  h1: { fontSize: \"28px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\r\n  text: { fontSize: \"16px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 16px\" },\r\n  button: { backgroundColor: \"#4f46e5\", color: \"#ffffff\", borderRadius: \"6px\", fontSize: \"16px\", fontWeight: \"600\", padding: \"12px 24px\", textDecoration: \"none\", display: \"inline-block\", margin: \"8px 0 24px\" },\r\n  hint: { fontSize: \"13px\", color: \"#6b7280\" },\r\n  link: { color: \"#4f46e5\" },\r\n  list: { fontSize: \"16px\", lineHeight: \"1.8\", color: \"#374151\", paddingLeft: \"20px\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Invoice Email\r\n\r\n```tsx\r\n// emails/templates/invoice.tsx\r\nimport { Row, Column, Section, Heading, Text, Hr, Button } from \"@react-email/components\"\r\nimport { EmailLayout } from \"../components/layout/email-layout\"\r\n\r\ninterface InvoiceItem { description: string; amount: number }\r\n\r\ninterface InvoiceEmailProps {\r\n  name: \"string\"\r\n  invoiceNumber: string\r\n  invoiceDate: string\r\n  dueDate: string\r\n  items: InvoiceItem[]\r\n  total: number\r\n  currency: string\r\n  downloadUrl: string\r\n}\r\n\r\nexport function InvoiceEmail({ name, invoiceNumber, invoiceDate, dueDate, items, total, currency = \"USD\", downloadUrl }: InvoiceEmailProps) {\r\n  const formatter = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency })\r\n\r\n  return (\r\n    <EmailLayout preview={`Invoice ${invoiceNumber} - ${formatter.format(total / 100)}`}>\r\n      <Heading style={styles.h1}>Invoice #{invoiceNumber}</Heading>\r\n      <Text style={styles.text}>Hi {name},</Text>\r\n      <Text style={styles.text}>Here's your invoice from MyApp. Thank you for your continued support.</Text>\r\n\r\n      {/* Invoice Meta */}\r\n      <Section style={styles.metaBox}>\r\n        <Row>\r\n          <Column><Text style={styles.metaLabel}>Invoice Date</Text><Text style={styles.metaValue}>{invoiceDate}</Text></Column>\r\n          <Column><Text style={styles.metaLabel}>Due Date</Text><Text style={styles.metaValue}>{dueDate}</Text></Column>\r\n          <Column><Text style={styles.metaLabel}>Amount Due</Text><Text style={styles.metaValueLarge}>{formatter.format(total / 100)}</Text></Column>\r\n        </Row>\r\n      </Section>\r\n\r\n      {/* Line Items */}\r\n      <Section style={styles.table}>\r\n        <Row style={styles.tableHeader}>\r\n          <Column><Text style={styles.tableHeaderText}>Description</Text></Column>\r\n          <Column><Text style={{ ...styles.tableHeaderText, textAlign: \"right\" }}>Amount</Text></Column>\r\n        </Row>\r\n        {items.map((item, i) => (\r\n          <Row key={i} style={i % 2 === 0 ? styles.tableRowEven : styles.tableRowOdd}>\r\n            <Column><Text style={styles.tableCell}>{item.description}</Text></Column>\r\n            <Column><Text style={{ ...styles.tableCell, textAlign: \"right\" }}>{formatter.format(item.amount / 100)}</Text></Column>\r\n          </Row>\r\n        ))}\r\n        <Hr style={styles.divider} />\r\n        <Row>\r\n          <Column><Text style={styles.totalLabel}>Total</Text></Column>\r\n          <Column><Text style={styles.totalValue}>{formatter.format(total / 100)}</Text></Column>\r\n        </Row>\r\n      </Section>\r\n\r\n      <Button href={downloadUrl} style={styles.button}>Download PDF Invoice</Button>\r\n    </EmailLayout>\r\n  )\r\n}\r\n\r\nexport default InvoiceEmail\r\n\r\nconst styles = {\r\n  h1: { fontSize: \"24px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\r\n  text: { fontSize: \"15px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 12px\" },\r\n  metaBox: { backgroundColor: \"#f9fafb\", borderRadius: \"8px\", padding: \"16px\", margin: \"16px 0\" },\r\n  metaLabel: { fontSize: \"12px\", color: \"#6b7280\", fontWeight: \"600\", textTransform: \"uppercase\" as const, margin: \"0 0 4px\" },\r\n  metaValue: { fontSize: \"14px\", color: \"#111827\", margin: 0 },\r\n  metaValueLarge: { fontSize: \"20px\", fontWeight: \"700\", color: \"#4f46e5\", margin: 0 },\r\n  table: { width: \"100%\", margin: \"16px 0\" },\r\n  tableHeader: { backgroundColor: \"#f3f4f6\", borderRadius: \"4px\" },\r\n  tableHeaderText: { fontSize: \"12px\", fontWeight: \"600\", color: \"#374151\", padding: \"8px 12px\", textTransform: \"uppercase\" as const },\r\n  tableRowEven: { backgroundColor: \"#ffffff\" },\r\n  tableRowOdd: { backgroundColor: \"#f9fafb\" },\r\n  tableCell: { fontSize: \"14px\", color: \"#374151\", padding: \"10px 12px\" },\r\n  divider: { borderColor: \"#e5e5e5\", margin: \"8px 0\" },\r\n  totalLabel: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", padding: \"8px 12px\" },\r\n  totalValue: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", textAlign: \"right\" as const, padding: \"8px 12px\" },\r\n  button: { backgroundColor: \"#4f46e5\", color: \"#fff\", borderRadius: \"6px\", padding: \"12px 24px\", fontSize: \"15px\", fontWeight: \"600\", textDecoration: \"none\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Unified Send Function\r\n\r\n```typescript\r\n// emails/lib/send.ts\r\nimport { Resend } from \"resend\"\r\nimport { render } from \"@react-email/render\"\r\nimport { WelcomeEmail } from \"../templates/welcome\"\r\nimport { InvoiceEmail } from \"../templates/invoice\"\r\nimport { addTrackingParams } from \"./tracking\"\r\n\r\nconst resend = new Resend(process.env.RESEND_API_KEY)\r\n\r\ntype EmailPayload =\r\n  | { type: \"welcome\"; props: Parameters<typeof WelcomeEmail>[0] }\r\n  | { type: \"invoice\"; props: Parameters<typeof InvoiceEmail>[0] }\r\n\r\nexport async function sendEmail(to: string, payload: EmailPayload) {\r\n  const templates = {\r\n    welcome: { component: WelcomeEmail, subject: \"Welcome to MyApp — confirm your email\" },\r\n    invoice: { component: InvoiceEmail, subject: `Invoice from MyApp` },\r\n  }\r\n\r\n  const template = templates[payload.type]\r\n  const html = render(template.component(payload.props as any))\r\n  const trackedHtml = addTrackingParams(html, { campaign: payload.type })\r\n\r\n  const result = await resend.emails.send({\r\n    from: \"MyApp <hello@yourapp.com>\",\r\n    to,\r\n    subject: template.subject,\r\n    html: trackedHtml,\r\n    tags: [{ name: \"email-type\", value: payload.type }],\r\n  })\r\n\r\n  return result\r\n}\r\n```\r\n\r\n---\r\n\r\n## Preview Server Setup\r\n\r\n```typescript\r\n// package.json scripts\r\n{\r\n  \"scripts\": {\r\n    \"email:dev\": \"email dev --dir emails/templates --port 3001\",\r\n    \"email:build\": \"email export --dir emails/templates --outDir emails/out\"\r\n  }\r\n}\r\n\r\n// Run: npm run email:dev\r\n// Opens: http://localhost:3001\r\n// Shows all templates with live preview and hot reload\r\n```\r\n\r\n---\r\n\r\n## i18n Support\r\n\r\n```typescript\r\n// emails/i18n/en.ts\r\nexport const en = {\r\n  welcome: {\r\n    preview: (name: string) => `Welcome to MyApp, ${name}!`,\r\n    heading: (name: string) => `Welcome to MyApp, ${name}!`,\r\n    body: (days: number) => `You've got ${days} days to explore everything.`,\r\n    cta: \"Confirm Email Address\",\r\n  },\r\n}\r\n\r\n// emails/i18n/de.ts\r\nexport const de = {\r\n  welcome: {\r\n    preview: (name: string) => `Willkommen bei MyApp, ${name}!`,\r\n    heading: (name: string) => `Willkommen bei MyApp, ${name}!`,\r\n    body: (days: number) => `Du hast ${days} Tage Zeit, alles zu erkunden.`,\r\n    cta: \"E-Mail-Adresse bestätigen\",\r\n  },\r\n}\r\n\r\n// Usage in template\r\nimport { en, de } from \"../i18n\"\r\nconst t = locale === \"de\" ? de : en\r\n```\r\n\r\n---\r\n\r\n## Spam Score Optimization Checklist\r\n\r\n- [ ] Sender domain has SPF, DKIM, and DMARC records configured\r\n- [ ] From address uses your own domain (not gmail.com/hotmail.com)\r\n- [ ] Subject line under 50 characters, no ALL CAPS, no \"FREE!!!\"\r\n- [ ] Text-to-image ratio: at least 60% text\r\n- [ ] Plain text version included alongside HTML\r\n- [ ] Unsubscribe link in every marketing email (CAN-SPAM, GDPR)\r\n- [ ] No URL shorteners — use full branded links\r\n- [ ] No red-flag words: \"guarantee\", \"no risk\", \"limited time offer\" in subject\r\n- [ ] Single CTA per email — no 5 different buttons\r\n- [ ] Image alt text on every image\r\n- [ ] HTML validates — no broken tags\r\n- [ ] Test with Mail-Tester.com before first send (target: 9+/10)\r\n\r\n---\r\n\r\n## Analytics Tracking\r\n\r\n```typescript\r\n// emails/lib/tracking.ts\r\ninterface TrackingParams {\r\n  campaign: string\r\n  medium?: string\r\n  source?: string\r\n}\r\n\r\nexport function addTrackingParams(html: string, params: TrackingParams): string {\r\n  const utmString = new URLSearchParams({\r\n    utm_source: params.source ?? \"email\",\r\n    utm_medium: params.medium ?? \"transactional\",\r\n    utm_campaign: params.campaign,\r\n  }).toString()\r\n\r\n  // Add UTM params to all links in the email\r\n  return html.replace(/href=\"(https?:\\/\\/[^\"]+)\"/g, (match, url) => {\r\n    const separator = url.includes(\"?\") ? \"&\" : \"?\"\r\n    return `href=\"${url}${separator}${utmString}\"`\r\n  })\r\n}\r\n```\r\n\r\n---\r\n\r\n## Common Pitfalls\r\n\r\n- **Inline styles required** — most email clients strip `<head>` styles; React Email handles this\r\n- **Max width 600px** — anything wider breaks on Gmail mobile\r\n- **No flexbox/grid** — use `<Row>` and `<Column>` from react-email, not CSS grid\r\n- **Dark mode media queries** — must use `!important` to override inline styles\r\n- **Missing plain text** — all major providers have a plain text field; always populate it\r\n- **Transactional vs marketing** — use separate sending domains/IPs to protect deliverability",
    "user_template": ""
  },
  "variables": [],
  "install": {
    "steps": [
      {
        "title": "Download & unzip",
        "detail": "Download the .zip package from SkillVault and unzip it. The email-template-builder/ folder contains SKILL.md plus any companion scripts, references and assets."
      },
      {
        "title": "Copy into your agent skills directory",
        "detail": "Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code), .codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or reload the session."
      },
      {
        "title": "Use it",
        "detail": "Describe the task in natural language; the agent loads the skill automatically when the request matches the skill description."
      }
    ]
  },
  "usage": {
    "steps": []
  },
  "metadata": {
    "author": "SkillVault",
    "source": "https://skillvault.top/skills/email-template-builder",
    "exported_at": "2026-11-03T07:44:18.299Z"
  }
}
email-template-builder.aiskill (YAML)
format: aiskill/v1
skill:
  name: email-template-builder
  slug: email-template-builder
  version: 1.0.0
  description: >-
    Build complete transactional email systems: React Email templates, provider integration (Resend,
    Postmark, SendGrid, AWS SES), preview server, i18n support, dark mode, spam optimization,
    analytics tracking. Use when adding transactional email to a new product, migrating between
    email providers, refactoring legacy email templates for accessibility, or adding
    internationalization to existing templates.
  category: Design
  tags:
    - design-creative
    - email
    - frontend
  platforms:
    - chatgpt
    - claude-code
    - cursor
  tested_environment: >-
    Agent Skills convention (SKILL.md) — works with Claude Code, Cursor, ChatGPT/Codex and
    compatible agents
  license: MIT
prompts:
  system: "# Email Template Builder\r\n\r\n**Tier:** POWERFUL  \r\n**Category:** Engineering Team  \r\n**Domain:** Transactional Email / Communications Infrastructure\r\n\r\n---\r\n\r\n## Overview\r\n\r\nBuild 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.\r\n\r\n---\r\n\r\n## Core Capabilities\r\n\r\n- React Email templates (welcome, verification, password reset, invoice, notification, digest)\r\n- MJML templates for maximum email client compatibility\r\n- Multi-provider support with unified sending interface\r\n- Local preview server with hot reload\r\n- i18n/localization with typed translation keys\r\n- Dark mode support using media queries\r\n- Spam score optimization checklist\r\n- Open/click tracking with UTM parameters\r\n\r\n---\r\n\r\n## When to Use\r\n\r\n- Setting up transactional email for a new product\r\n- Migrating from a legacy email system\r\n- Adding new email types (invoice, digest, notification)\r\n- Debugging email deliverability issues\r\n- Implementing i18n for email templates\r\n\r\n---\r\n\r\n## Project Structure\r\n\r\n```\r\nemails/\r\n├── components/\r\n│   ├── layout/\r\n│   │   ├── email-layout.tsx       # Base layout with brand header/footer\r\n│   │   └── email-button.tsx       # CTA button component\r\n│   ├── partials/\r\n│   │   ├── header.tsx\r\n│   │   └── footer.tsx\r\n├── templates/\r\n│   ├── welcome.tsx\r\n│   ├── verify-email.tsx\r\n│   ├── password-reset.tsx\r\n│   ├── invoice.tsx\r\n│   ├── notification.tsx\r\n│   └── weekly-digest.tsx\r\n├── lib/\r\n│   ├── send.ts                    # Unified send function\r\n│   ├── providers/\r\n│   │   ├── resend.ts\r\n│   │   ├── postmark.ts\r\n│   │   └── ses.ts\r\n│   └── tracking.ts                # UTM + analytics\r\n├── i18n/\r\n│   ├── en.ts\r\n│   └── de.ts\r\n└── preview/                       # Dev preview server\r\n    └── server.ts\r\n```\r\n\r\n---\r\n\r\n## Base Email Layout\r\n\r\n```tsx\r\n// emails/components/layout/email-layout.tsx\r\nimport {\r\n  Body, Container, Head, Html, Img, Preview, Section, Text, Hr, Font\r\n} from \"@react-email/components\"\r\n\r\ninterface EmailLayoutProps {\r\n  preview: string\r\n  children: React.ReactNode\r\n}\r\n\r\nexport function EmailLayout({ preview, children }: EmailLayoutProps) {\r\n  return (\r\n    <Html lang=\"en\">\r\n      <Head>\r\n        <Font\r\n          fontFamily=\"Inter\"\r\n          fallbackFontFamily=\"Arial\"\r\n          webFont={{ url: \"https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2\", format: \"woff2\" }}\r\n          fontWeight={400}\r\n          fontStyle=\"normal\"\r\n        />\r\n        {/* Dark mode styles */}\r\n        <style>{`\r\n          @media (prefers-color-scheme: dark) {\r\n            .email-body { background-color: #0f0f0f !important; }\r\n            .email-container { background-color: #1a1a1a !important; }\r\n            .email-text { color: #e5e5e5 !important; }\r\n            .email-heading { color: #ffffff !important; }\r\n            .email-divider { border-color: #333333 !important; }\r\n          }\r\n        `}</style>\r\n      </Head>\r\n      <Preview>{preview}</Preview>\r\n      <Body className=\"email-body\" style={styles.body}>\r\n        <Container className=\"email-container\" style={styles.container}>\r\n          {/* Header */}\r\n          <Section style={styles.header}>\r\n            <Img src=\"https://yourapp.com/logo.png\" width={120} height={40} alt=\"MyApp\" />\r\n          </Section>\r\n          \r\n          {/* Content */}\r\n          <Section style={styles.content}>\r\n            {children}\r\n          </Section>\r\n          \r\n          {/* Footer */}\r\n          <Hr style={styles.divider} />\r\n          <Section style={styles.footer}>\r\n            <Text style={styles.footerText}>\r\n              MyApp Inc. · 123 Main St · San Francisco, CA 94105\r\n            </Text>\r\n            <Text style={styles.footerText}>\r\n              <a href=\"{{unsubscribe_url}}\" style={styles.link}>Unsubscribe</a>\r\n              {\" · \"}\r\n              <a href=\"https://yourapp.com/privacy\" style={styles.link}>Privacy Policy</a>\r\n            </Text>\r\n          </Section>\r\n        </Container>\r\n      </Body>\r\n    </Html>\r\n  )\r\n}\r\n\r\nconst styles = {\r\n  body: { backgroundColor: \"#f5f5f5\", fontFamily: \"Inter, Arial, sans-serif\" },\r\n  container: { maxWidth: \"600px\", margin: \"0 auto\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", overflow: \"hidden\" },\r\n  header: { padding: \"24px 32px\", borderBottom: \"1px solid#e5e5e5\" },\r\n  content: { padding: \"32px\" },\r\n  divider: { borderColor: \"#e5e5e5\", margin: \"0 32px\" },\r\n  footer: { padding: \"24px 32px\" },\r\n  footerText: { fontSize: \"12px\", color: \"#6b7280\", textAlign: \"center\" as const, margin: \"4px 0\" },\r\n  link: { color: \"#6b7280\", textDecoration: \"underline\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Welcome Email\r\n\r\n```tsx\r\n// emails/templates/welcome.tsx\r\nimport { Button, Heading, Text } from \"@react-email/components\"\r\nimport { EmailLayout } from \"../components/layout/email-layout\"\r\n\r\ninterface WelcomeEmailProps {\r\n  name: \"string\"\r\n  confirmUrl: string\r\n  trialDays?: number\r\n}\r\n\r\nexport function WelcomeEmail({ name, confirmUrl, trialDays = 14 }: WelcomeEmailProps) {\r\n  return (\r\n    <EmailLayout preview={`Welcome to MyApp, ${name}! Confirm your email to get started.`}>\r\n      <Heading style={styles.h1}>Welcome to MyApp, {name}!</Heading>\r\n      <Text style={styles.text}>\r\n        We're excited to have you on board. You've got {trialDays} days to explore everything MyApp has to offer — no credit card required.\r\n      </Text>\r\n      <Text style={styles.text}>\r\n        First, confirm your email address to activate your account:\r\n      </Text>\r\n      <Button href={confirmUrl} style={styles.button}>\r\n        Confirm Email Address\r\n      </Button>\r\n      <Text style={styles.hint}>\r\n        Button not working? Copy and paste this link into your browser:\r\n        <br />\r\n        <a href={confirmUrl} style={styles.link}>{confirmUrl}</a>\r\n      </Text>\r\n      <Text style={styles.text}>\r\n        Once confirmed, you can:\r\n      </Text>\r\n      <ul style={styles.list}>\r\n        <li>Connect your first project in 2 minutes</li>\r\n        <li>Invite your team (free for up to 3 members)</li>\r\n        <li>Set up Slack notifications</li>\r\n      </ul>\r\n    </EmailLayout>\r\n  )\r\n}\r\n\r\nexport default WelcomeEmail\r\n\r\nconst styles = {\r\n  h1: { fontSize: \"28px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\r\n  text: { fontSize: \"16px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 16px\" },\r\n  button: { backgroundColor: \"#4f46e5\", color: \"#ffffff\", borderRadius: \"6px\", fontSize: \"16px\", fontWeight: \"600\", padding: \"12px 24px\", textDecoration: \"none\", display: \"inline-block\", margin: \"8px 0 24px\" },\r\n  hint: { fontSize: \"13px\", color: \"#6b7280\" },\r\n  link: { color: \"#4f46e5\" },\r\n  list: { fontSize: \"16px\", lineHeight: \"1.8\", color: \"#374151\", paddingLeft: \"20px\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Invoice Email\r\n\r\n```tsx\r\n// emails/templates/invoice.tsx\r\nimport { Row, Column, Section, Heading, Text, Hr, Button } from \"@react-email/components\"\r\nimport { EmailLayout } from \"../components/layout/email-layout\"\r\n\r\ninterface InvoiceItem { description: string; amount: number }\r\n\r\ninterface InvoiceEmailProps {\r\n  name: \"string\"\r\n  invoiceNumber: string\r\n  invoiceDate: string\r\n  dueDate: string\r\n  items: InvoiceItem[]\r\n  total: number\r\n  currency: string\r\n  downloadUrl: string\r\n}\r\n\r\nexport function InvoiceEmail({ name, invoiceNumber, invoiceDate, dueDate, items, total, currency = \"USD\", downloadUrl }: InvoiceEmailProps) {\r\n  const formatter = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency })\r\n\r\n  return (\r\n    <EmailLayout preview={`Invoice ${invoiceNumber} - ${formatter.format(total / 100)}`}>\r\n      <Heading style={styles.h1}>Invoice #{invoiceNumber}</Heading>\r\n      <Text style={styles.text}>Hi {name},</Text>\r\n      <Text style={styles.text}>Here's your invoice from MyApp. Thank you for your continued support.</Text>\r\n\r\n      {/* Invoice Meta */}\r\n      <Section style={styles.metaBox}>\r\n        <Row>\r\n          <Column><Text style={styles.metaLabel}>Invoice Date</Text><Text style={styles.metaValue}>{invoiceDate}</Text></Column>\r\n          <Column><Text style={styles.metaLabel}>Due Date</Text><Text style={styles.metaValue}>{dueDate}</Text></Column>\r\n          <Column><Text style={styles.metaLabel}>Amount Due</Text><Text style={styles.metaValueLarge}>{formatter.format(total / 100)}</Text></Column>\r\n        </Row>\r\n      </Section>\r\n\r\n      {/* Line Items */}\r\n      <Section style={styles.table}>\r\n        <Row style={styles.tableHeader}>\r\n          <Column><Text style={styles.tableHeaderText}>Description</Text></Column>\r\n          <Column><Text style={{ ...styles.tableHeaderText, textAlign: \"right\" }}>Amount</Text></Column>\r\n        </Row>\r\n        {items.map((item, i) => (\r\n          <Row key={i} style={i % 2 === 0 ? styles.tableRowEven : styles.tableRowOdd}>\r\n            <Column><Text style={styles.tableCell}>{item.description}</Text></Column>\r\n            <Column><Text style={{ ...styles.tableCell, textAlign: \"right\" }}>{formatter.format(item.amount / 100)}</Text></Column>\r\n          </Row>\r\n        ))}\r\n        <Hr style={styles.divider} />\r\n        <Row>\r\n          <Column><Text style={styles.totalLabel}>Total</Text></Column>\r\n          <Column><Text style={styles.totalValue}>{formatter.format(total / 100)}</Text></Column>\r\n        </Row>\r\n      </Section>\r\n\r\n      <Button href={downloadUrl} style={styles.button}>Download PDF Invoice</Button>\r\n    </EmailLayout>\r\n  )\r\n}\r\n\r\nexport default InvoiceEmail\r\n\r\nconst styles = {\r\n  h1: { fontSize: \"24px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\r\n  text: { fontSize: \"15px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 12px\" },\r\n  metaBox: { backgroundColor: \"#f9fafb\", borderRadius: \"8px\", padding: \"16px\", margin: \"16px 0\" },\r\n  metaLabel: { fontSize: \"12px\", color: \"#6b7280\", fontWeight: \"600\", textTransform: \"uppercase\" as const, margin: \"0 0 4px\" },\r\n  metaValue: { fontSize: \"14px\", color: \"#111827\", margin: 0 },\r\n  metaValueLarge: { fontSize: \"20px\", fontWeight: \"700\", color: \"#4f46e5\", margin: 0 },\r\n  table: { width: \"100%\", margin: \"16px 0\" },\r\n  tableHeader: { backgroundColor: \"#f3f4f6\", borderRadius: \"4px\" },\r\n  tableHeaderText: { fontSize: \"12px\", fontWeight: \"600\", color: \"#374151\", padding: \"8px 12px\", textTransform: \"uppercase\" as const },\r\n  tableRowEven: { backgroundColor: \"#ffffff\" },\r\n  tableRowOdd: { backgroundColor: \"#f9fafb\" },\r\n  tableCell: { fontSize: \"14px\", color: \"#374151\", padding: \"10px 12px\" },\r\n  divider: { borderColor: \"#e5e5e5\", margin: \"8px 0\" },\r\n  totalLabel: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", padding: \"8px 12px\" },\r\n  totalValue: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", textAlign: \"right\" as const, padding: \"8px 12px\" },\r\n  button: { backgroundColor: \"#4f46e5\", color: \"#fff\", borderRadius: \"6px\", padding: \"12px 24px\", fontSize: \"15px\", fontWeight: \"600\", textDecoration: \"none\" },\r\n}\r\n```\r\n\r\n---\r\n\r\n## Unified Send Function\r\n\r\n```typescript\r\n// emails/lib/send.ts\r\nimport { Resend } from \"resend\"\r\nimport { render } from \"@react-email/render\"\r\nimport { WelcomeEmail } from \"../templates/welcome\"\r\nimport { InvoiceEmail } from \"../templates/invoice\"\r\nimport { addTrackingParams } from \"./tracking\"\r\n\r\nconst resend = new Resend(process.env.RESEND_API_KEY)\r\n\r\ntype EmailPayload =\r\n  | { type: \"welcome\"; props: Parameters<typeof WelcomeEmail>[0] }\r\n  | { type: \"invoice\"; props: Parameters<typeof InvoiceEmail>[0] }\r\n\r\nexport async function sendEmail(to: string, payload: EmailPayload) {\r\n  const templates = {\r\n    welcome: { component: WelcomeEmail, subject: \"Welcome to MyApp — confirm your email\" },\r\n    invoice: { component: InvoiceEmail, subject: `Invoice from MyApp` },\r\n  }\r\n\r\n  const template = templates[payload.type]\r\n  const html = render(template.component(payload.props as any))\r\n  const trackedHtml = addTrackingParams(html, { campaign: payload.type })\r\n\r\n  const result = await resend.emails.send({\r\n    from: \"MyApp <hello@yourapp.com>\",\r\n    to,\r\n    subject: template.subject,\r\n    html: trackedHtml,\r\n    tags: [{ name: \"email-type\", value: payload.type }],\r\n  })\r\n\r\n  return result\r\n}\r\n```\r\n\r\n---\r\n\r\n## Preview Server Setup\r\n\r\n```typescript\r\n// package.json scripts\r\n{\r\n  \"scripts\": {\r\n    \"email:dev\": \"email dev --dir emails/templates --port 3001\",\r\n    \"email:build\": \"email export --dir emails/templates --outDir emails/out\"\r\n  }\r\n}\r\n\r\n// Run: npm run email:dev\r\n// Opens: http://localhost:3001\r\n// Shows all templates with live preview and hot reload\r\n```\r\n\r\n---\r\n\r\n## i18n Support\r\n\r\n```typescript\r\n// emails/i18n/en.ts\r\nexport const en = {\r\n  welcome: {\r\n    preview: (name: string) => `Welcome to MyApp, ${name}!`,\r\n    heading: (name: string) => `Welcome to MyApp, ${name}!`,\r\n    body: (days: number) => `You've got ${days} days to explore everything.`,\r\n    cta: \"Confirm Email Address\",\r\n  },\r\n}\r\n\r\n// emails/i18n/de.ts\r\nexport const de = {\r\n  welcome: {\r\n    preview: (name: string) => `Willkommen bei MyApp, ${name}!`,\r\n    heading: (name: string) => `Willkommen bei MyApp, ${name}!`,\r\n    body: (days: number) => `Du hast ${days} Tage Zeit, alles zu erkunden.`,\r\n    cta: \"E-Mail-Adresse bestätigen\",\r\n  },\r\n}\r\n\r\n// Usage in template\r\nimport { en, de } from \"../i18n\"\r\nconst t = locale === \"de\" ? de : en\r\n```\r\n\r\n---\r\n\r\n## Spam Score Optimization Checklist\r\n\r\n- [ ] Sender domain has SPF, DKIM, and DMARC records configured\r\n- [ ] From address uses your own domain (not gmail.com/hotmail.com)\r\n- [ ] Subject line under 50 characters, no ALL CAPS, no \"FREE!!!\"\r\n- [ ] Text-to-image ratio: at least 60% text\r\n- [ ] Plain text version included alongside HTML\r\n- [ ] Unsubscribe link in every marketing email (CAN-SPAM, GDPR)\r\n- [ ] No URL shorteners — use full branded links\r\n- [ ] No red-flag words: \"guarantee\", \"no risk\", \"limited time offer\" in subject\r\n- [ ] Single CTA per email — no 5 different buttons\r\n- [ ] Image alt text on every image\r\n- [ ] HTML validates — no broken tags\r\n- [ ] Test with Mail-Tester.com before first send (target: 9+/10)\r\n\r\n---\r\n\r\n## Analytics Tracking\r\n\r\n```typescript\r\n// emails/lib/tracking.ts\r\ninterface TrackingParams {\r\n  campaign: string\r\n  medium?: string\r\n  source?: string\r\n}\r\n\r\nexport function addTrackingParams(html: string, params: TrackingParams): string {\r\n  const utmString = new URLSearchParams({\r\n    utm_source: params.source ?? \"email\",\r\n    utm_medium: params.medium ?? \"transactional\",\r\n    utm_campaign: params.campaign,\r\n  }).toString()\r\n\r\n  // Add UTM params to all links in the email\r\n  return html.replace(/href=\"(https?:\\/\\/[^\"]+)\"/g, (match, url) => {\r\n    const separator = url.includes(\"?\") ? \"&\" : \"?\"\r\n    return `href=\"${url}${separator}${utmString}\"`\r\n  })\r\n}\r\n```\r\n\r\n---\r\n\r\n## Common Pitfalls\r\n\r\n- **Inline styles required** — most email clients strip `<head>` styles; React Email handles this\r\n- **Max width 600px** — anything wider breaks on Gmail mobile\r\n- **No flexbox/grid** — use `<Row>` and `<Column>` from react-email, not CSS grid\r\n- **Dark mode media queries** — must use `!important` to override inline styles\r\n- **Missing plain text** — all major providers have a plain text field; always populate it\r\n- **Transactional vs marketing** — use separate sending domains/IPs to protect deliverability"
  user_template: ""
variables: []
install:
  steps:
    - title: Download & unzip
      detail: >-
        Download the .zip package from SkillVault and unzip it. The email-template-builder/ folder
        contains SKILL.md plus any companion scripts, references and assets.
    - title: Copy into your agent skills directory
      detail: >-
        Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code),
        .codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or
        reload the session.
    - title: Use it
      detail: >-
        Describe the task in natural language; the agent loads the skill automatically when the
        request matches the skill description.
usage:
  steps: []
metadata:
  author: SkillVault
  source: https://skillvault.top/skills/email-template-builder
  exported_at: "2026-11-03T07:44:18.299Z"

安装指南

  1. 1

    Download & unzip

    Download the .zip package from SkillVault and unzip it. The email-template-builder/ folder contains SKILL.md plus any companion scripts, references and assets.

  2. 2

    Copy into your agent skills directory

    Move the folder into your agent's skills directory — e.g. ~/.claude/skills/ (Claude Code), .codex/skills/, .cursor/skills/ or your project's .claude/skills/ folder — then restart or reload the session.

  3. 3

    Use it

    Describe the task in natural language; the agent loads the skill automatically when the request matches the skill description.

使用方法

平台兼容性

ChatGPT GPTs

OpenAI

将系统提示词粘贴到 GPT 的「Instructions」字段,并把用户模板作为起始消息使用。

官方文档

Claude Code

Anthropic

将导出的 .aiskill JSON 保存为项目中的技能文件,或将系统提示词粘贴进 CLAUDE.md / 斜杠命令 Markdown 文件。

官方文档

Cursor

Anysphere

将系统提示词复制到 .cursor/rules/*.mdc(或设置中的 Project Rules),并在智能体提示词中引用用户模板。

官方文档

相关技能