Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Email Template Builder
.claude/skills/email-template-builder/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-20 | ✗→✓ | ▲ Improved | — | — |
Tier: POWERFUL Category: Engineering Team Domain: Transactional Email / Communications Infrastructure
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.
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.tstsx// 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" }, }
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" }, }
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" }, }
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 }
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
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
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}"` }) }
<head> styles; React Email handles this<Row> and <Column> from react-email, not CSS grid!important to override inline styles| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.