Skill: alirezarezvani/email-template-builder (version 1.0.0)

--- 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.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 <[REDACTED_SECRET]>", 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: [REDACTED_SECRET] heading: (name: [REDACTED_SECRET] 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


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)