🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

email-template-builder

by @alirezarezvani

Email Template Builder

Versionv1.0.0
Downloads910
Installs8
TERMINAL
clawhub install email-template-builder

πŸ“– About This Skill


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

    // 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 ( {/* Dark mode styles */} {preview} {/* Header */}

    MyApp
    {/* Content */}
    {children}
    {/* Footer */}
    MyApp Inc. Β· 123 Main St Β· San Francisco, CA 94105 Unsubscribe {" Β· "} Privacy Policy
    ) }

    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

    // 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 ( Welcome to MyApp, ${name}! Confirm your email to get started.}> Welcome to MyApp, {name}! We're excited to have you on board. You've got {trialDays} days to explore everything MyApp has to offer β€” no credit card required. First, confirm your email address to activate your account: Button not working? Copy and paste this link into your browser:
    {confirmUrl}
    Once confirmed, you can:

    • Connect your first project in 2 minutes
    • Invite your team (free for up to 3 members)
    • Set up Slack notifications
    ) }

    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

    // 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 ( Invoice ${invoiceNumber} - ${formatter.format(total / 100)}}> Invoice #{invoiceNumber} Hi {name}, Here's your invoice from MyApp. Thank you for your continued support.

    {/* Invoice Meta */}

    Invoice Date{invoiceDate} Due Date{dueDate} Amount Due{formatter.format(total / 100)}

    {/* Line Items */}

    Description Amount {items.map((item, i) => ( {item.description} {formatter.format(item.amount / 100)} ))}
    Total {formatter.format(total / 100)}

    ) }

    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

    // 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[0] } | { type: "invoice"; props: Parameters[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 ", to, subject: template.subject, html: trackedHtml, tags: [{ name: "email-type", value: payload.type }], })

    return result }


    Preview Server Setup

    // 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

    // 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

    // 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 styles; React Email handles this
  • Max width 600px β€” anything wider breaks on Gmail mobile
  • No flexbox/grid β€” use and 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
  • ⚑ When to Use

    TriggerAction
    - Migrating from a legacy email system
    - Adding new email types (invoice, digest, notification)
    - Debugging email deliverability issues
    - Implementing i18n for email templates
    ---