<< All versions
Skill v1.0.1
currentAutomated scan100/100dillonmohr8777/claude-skills-repo/email-template-builder
~1 modified
──Details
PublishedJune 18, 2026 at 12:09 AM
Content Hashsha256:299f5a2e8d9efcd6...
Git SHAbb6b2aa09068
Bump Typepatch
──Files
Files (1 file, 14.8 KB)
SKILL.md14.8 KBactive
SKILL.md · 441 lines · 14.8 KB
version: "1.0.1" name: "email-template-builder" description: "Email Template Builder"
Email Template Builder
Tier: POWERFUL Category: Engineering Team Domain: Transactional Email / Communications Infrastructure
Overview
Build complete transactional email systems: React Email templates, provider integration, preview server, i18n support, dark mode, spam optimization, and analytics tracking. Output production-ready code for Resend, Postmark, SendGrid, or AWS SES.
Core Capabilities
- React Email templates (welcome, verification, password reset, invoice, notification, digest)
- MJML templates for maximum email client compatibility
- Multi-provider support with unified sending interface
- Local preview server with hot reload
- i18n/localization with typed translation keys
- Dark mode support using media queries
- Spam score optimization checklist
- Open/click tracking with UTM parameters
When to Use
- Setting up transactional email for a new product
- Migrating from a legacy email system
- Adding new email types (invoice, digest, notification)
- Debugging email deliverability issues
- Implementing i18n for email templates
Project Structure
emails/├── components/│ ├── layout/│ │ ├── email-layout.tsx # Base layout with brand header/footer│ │ └── email-button.tsx # CTA button component│ ├── partials/│ │ ├── header.tsx│ │ └── footer.tsx├── templates/│ ├── welcome.tsx│ ├── verify-email.tsx│ ├── password-reset.tsx│ ├── invoice.tsx│ ├── notification.tsx│ └── weekly-digest.tsx├── lib/│ ├── send.ts # Unified send function│ ├── providers/│ │ ├── resend.ts│ │ ├── postmark.ts│ │ └── ses.ts│ └── tracking.ts # UTM + analytics├── i18n/│ ├── en.ts│ └── de.ts└── preview/ # Dev preview server└── server.ts
Base Email Layout
tsx
// emails/components/layout/email-layout.tsximport {Body, Container, Head, Html, Img, Preview, Section, Text, Hr, Font} from "@react-email/components"interface EmailLayoutProps {preview: stringchildren: React.ReactNode}export function EmailLayout({ preview, children }: EmailLayoutProps) {return (<Html lang="en"><Head><FontfontFamily="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.tsximport { Button, Heading, Text } from "@react-email/components"import { EmailLayout } from "../components/layout/email-layout"interface WelcomeEmailProps {name: "string"confirmUrl: stringtrialDays?: 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 WelcomeEmailconst 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.tsximport { 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: stringinvoiceDate: stringdueDate: stringitems: InvoiceItem[]total: numbercurrency: stringdownloadUrl: 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 InvoiceEmailconst 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.tsimport { 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.tsexport 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.tsexport 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 templateimport { 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.tsinterface TrackingParams {campaign: stringmedium?: stringsource?: 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 emailreturn 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
!importantto 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