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.
Overview
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
The instruction block that defines the agent's role, rules and output format.
# 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 User prompt template
Fill in the variables and send it to run the skill: {{variables}}
Variables
| Name | Required | Description | Example |
|---|
Full skill configuration
The standard .aiskill file in JSON and 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:42:25.872Z"
}
} 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:42:25.872Z"
Installation guide
- 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
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
Use it
Describe the task in natural language; the agent loads the skill automatically when the request matches the skill description.
How to use
Platform compatibility
ChatGPT GPTs
OpenAI
Paste the system prompt into the GPT "Instructions" field and keep the user template as your starter message.
Official docsClaude Code
Anthropic
Save the exported .aiskill JSON as a skill file in your project or paste the system prompt into CLAUDE.md / a slash-command markdown file.
Official docsCursor
Anysphere
Copy the system prompt into .cursor/rules/*.mdc (or Project Rules in Settings) and reference the user template in your agent prompts.
Official docsRelated skills
apple-hig-expert
Audits and designs iOS/macOS/watchOS/visionOS interfaces against the Apple Human Interface Guidelines, including the Liquid Glass design language (announced WWDC25, shipped with iOS 26/macOS Tahoe, Sept 2025). Use when reviewing an Apple-platform mockup or app for HIG compliance, checking contrast or tap-target sizes, or designing native-feeling Apple UI (e.g., 'audit my iOS app against the HIG', 'is this text readable on Liquid Glass?').
ui-design-system
UI design system toolkit for Senior UI Designer including design token generation, component documentation, responsive design calculations, and developer handoff tools. Use when creating design systems, generating design tokens, maintaining visual consistency, or facilitating design-dev collaboration and developer handoff.
Landing Page Architect
Designs high-converting landing pages end-to-end: section wireframe, full copy draft, CTA strategy and A/B test plan. Yearly Pro exclusive.