[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-3dde80b5-338f-4329-9325-2a776827a563":3,"$fslaQKzaIV0sSahbc_UeACFihBdgUBKLnXRWz0ierbjI":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"3dde80b5-338f-4329-9325-2a776827a563","email-template-builder","构建完整的交易电子邮件系统：React电子邮件模板、提供商集成（Resend、Postmark、SendGrid、AWS SES）、预览服务器、国际化支持、暗黑模式、垃圾邮件优化、分析跟踪。用于在新产品中添加交易电子邮件、在电子邮件提供商之间迁移、重构遗留电子邮件模板以提高可访问性，或向现有模板添加国际化。","cat_coding_frontend","mod_coding","alirezarezvani,coding","---\nname: \"email-template-builder\"\ndescription: \"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.\"\n---\n\n# Email Template Builder\n\n**Tier:** POWERFUL  \n**Category:** Engineering Team  \n**Domain:** Transactional Email \u002F Communications Infrastructure\n\n---\n\n## Overview\n\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.\n\n---\n\n## Core Capabilities\n\n- React Email templates (welcome, verification, password reset, invoice, notification, digest)\n- MJML templates for maximum email client compatibility\n- Multi-provider support with unified sending interface\n- Local preview server with hot reload\n- i18n\u002Flocalization with typed translation keys\n- Dark mode support using media queries\n- Spam score optimization checklist\n- Open\u002Fclick tracking with UTM parameters\n\n---\n\n## When to Use\n\n- Setting up transactional email for a new product\n- Migrating from a legacy email system\n- Adding new email types (invoice, digest, notification)\n- Debugging email deliverability issues\n- Implementing i18n for email templates\n\n---\n\n## Project Structure\n\n```\nemails\u002F\n├── components\u002F\n│   ├── layout\u002F\n│   │   ├── email-layout.tsx       # Base layout with brand header\u002Ffooter\n│   │   └── email-button.tsx       # CTA button component\n│   ├── partials\u002F\n│   │   ├── header.tsx\n│   │   └── footer.tsx\n├── templates\u002F\n│   ├── welcome.tsx\n│   ├── verify-email.tsx\n│   ├── password-reset.tsx\n│   ├── invoice.tsx\n│   ├── notification.tsx\n│   └── weekly-digest.tsx\n├── lib\u002F\n│   ├── send.ts                    # Unified send function\n│   ├── providers\u002F\n│   │   ├── resend.ts\n│   │   ├── postmark.ts\n│   │   └── ses.ts\n│   └── tracking.ts                # UTM + analytics\n├── i18n\u002F\n│   ├── en.ts\n│   └── de.ts\n└── preview\u002F                       # Dev preview server\n    └── server.ts\n```\n\n---\n\n## Base Email Layout\n\n```tsx\n\u002F\u002F emails\u002Fcomponents\u002Flayout\u002Femail-layout.tsx\nimport {\n  Body, Container, Head, Html, Img, Preview, Section, Text, Hr, Font\n} from \"@react-email\u002Fcomponents\"\n\ninterface EmailLayoutProps {\n  preview: string\n  children: React.ReactNode\n}\n\nexport function EmailLayout({ preview, children }: EmailLayoutProps) {\n  return (\n    \u003CHtml lang=\"en\">\n      \u003CHead>\n        \u003CFont\n          fontFamily=\"Inter\"\n          fallbackFontFamily=\"Arial\"\n          webFont={{ url: \"https:\u002F\u002Ffonts.gstatic.com\u002Fs\u002Finter\u002Fv13\u002FUcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2\", format: \"woff2\" }}\n          fontWeight={400}\n          fontStyle=\"normal\"\n        \u002F>\n        {\u002F* Dark mode styles *\u002F}\n        \u003Cstyle>{`\n          @media (prefers-color-scheme: dark) {\n            .email-body { background-color: #0f0f0f !important; }\n            .email-container { background-color: #1a1a1a !important; }\n            .email-text { color: #e5e5e5 !important; }\n            .email-heading { color: #ffffff !important; }\n            .email-divider { border-color: #333333 !important; }\n          }\n        `}\u003C\u002Fstyle>\n      \u003C\u002FHead>\n      \u003CPreview>{preview}\u003C\u002FPreview>\n      \u003CBody className=\"email-body\" style={styles.body}>\n        \u003CContainer className=\"email-container\" style={styles.container}>\n          {\u002F* Header *\u002F}\n          \u003CSection style={styles.header}>\n            \u003CImg src=\"https:\u002F\u002Fyourapp.com\u002Flogo.png\" width={120} height={40} alt=\"MyApp\" \u002F>\n          \u003C\u002FSection>\n          \n          {\u002F* Content *\u002F}\n          \u003CSection style={styles.content}>\n            {children}\n          \u003C\u002FSection>\n          \n          {\u002F* Footer *\u002F}\n          \u003CHr style={styles.divider} \u002F>\n          \u003CSection style={styles.footer}>\n            \u003CText style={styles.footerText}>\n              MyApp Inc. · 123 Main St · San Francisco, CA 94105\n            \u003C\u002FText>\n            \u003CText style={styles.footerText}>\n              \u003Ca href=\"{{unsubscribe_url}}\" style={styles.link}>Unsubscribe\u003C\u002Fa>\n              {\" · \"}\n              \u003Ca href=\"https:\u002F\u002Fyourapp.com\u002Fprivacy\" style={styles.link}>Privacy Policy\u003C\u002Fa>\n            \u003C\u002FText>\n          \u003C\u002FSection>\n        \u003C\u002FContainer>\n      \u003C\u002FBody>\n    \u003C\u002FHtml>\n  )\n}\n\nconst styles = {\n  body: { backgroundColor: \"#f5f5f5\", fontFamily: \"Inter, Arial, sans-serif\" },\n  container: { maxWidth: \"600px\", margin: \"0 auto\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", overflow: \"hidden\" },\n  header: { padding: \"24px 32px\", borderBottom: \"1px solid #e5e5e5\" },\n  content: { padding: \"32px\" },\n  divider: { borderColor: \"#e5e5e5\", margin: \"0 32px\" },\n  footer: { padding: \"24px 32px\" },\n  footerText: { fontSize: \"12px\", color: \"#6b7280\", textAlign: \"center\" as const, margin: \"4px 0\" },\n  link: { color: \"#6b7280\", textDecoration: \"underline\" },\n}\n```\n\n---\n\n## Welcome Email\n\n```tsx\n\u002F\u002F emails\u002Ftemplates\u002Fwelcome.tsx\nimport { Button, Heading, Text } from \"@react-email\u002Fcomponents\"\nimport { EmailLayout } from \"..\u002Fcomponents\u002Flayout\u002Femail-layout\"\n\ninterface WelcomeEmailProps {\n  name: \"string\"\n  confirmUrl: string\n  trialDays?: number\n}\n\nexport function WelcomeEmail({ name, confirmUrl, trialDays = 14 }: WelcomeEmailProps) {\n  return (\n    \u003CEmailLayout preview={`Welcome to MyApp, ${name}! Confirm your email to get started.`}>\n      \u003CHeading style={styles.h1}>Welcome to MyApp, {name}!\u003C\u002FHeading>\n      \u003CText style={styles.text}>\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.\n      \u003C\u002FText>\n      \u003CText style={styles.text}>\n        First, confirm your email address to activate your account:\n      \u003C\u002FText>\n      \u003CButton href={confirmUrl} style={styles.button}>\n        Confirm Email Address\n      \u003C\u002FButton>\n      \u003CText style={styles.hint}>\n        Button not working? Copy and paste this link into your browser:\n        \u003Cbr \u002F>\n        \u003Ca href={confirmUrl} style={styles.link}>{confirmUrl}\u003C\u002Fa>\n      \u003C\u002FText>\n      \u003CText style={styles.text}>\n        Once confirmed, you can:\n      \u003C\u002FText>\n      \u003Cul style={styles.list}>\n        \u003Cli>Connect your first project in 2 minutes\u003C\u002Fli>\n        \u003Cli>Invite your team (free for up to 3 members)\u003C\u002Fli>\n        \u003Cli>Set up Slack notifications\u003C\u002Fli>\n      \u003C\u002Ful>\n    \u003C\u002FEmailLayout>\n  )\n}\n\nexport default WelcomeEmail\n\nconst styles = {\n  h1: { fontSize: \"28px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\n  text: { fontSize: \"16px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 16px\" },\n  button: { backgroundColor: \"#4f46e5\", color: \"#ffffff\", borderRadius: \"6px\", fontSize: \"16px\", fontWeight: \"600\", padding: \"12px 24px\", textDecoration: \"none\", display: \"inline-block\", margin: \"8px 0 24px\" },\n  hint: { fontSize: \"13px\", color: \"#6b7280\" },\n  link: { color: \"#4f46e5\" },\n  list: { fontSize: \"16px\", lineHeight: \"1.8\", color: \"#374151\", paddingLeft: \"20px\" },\n}\n```\n\n---\n\n## Invoice Email\n\n```tsx\n\u002F\u002F emails\u002Ftemplates\u002Finvoice.tsx\nimport { Row, Column, Section, Heading, Text, Hr, Button } from \"@react-email\u002Fcomponents\"\nimport { EmailLayout } from \"..\u002Fcomponents\u002Flayout\u002Femail-layout\"\n\ninterface InvoiceItem { description: string; amount: number }\n\ninterface InvoiceEmailProps {\n  name: \"string\"\n  invoiceNumber: string\n  invoiceDate: string\n  dueDate: string\n  items: InvoiceItem[]\n  total: number\n  currency: string\n  downloadUrl: string\n}\n\nexport function InvoiceEmail({ name, invoiceNumber, invoiceDate, dueDate, items, total, currency = \"USD\", downloadUrl }: InvoiceEmailProps) {\n  const formatter = new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency })\n\n  return (\n    \u003CEmailLayout preview={`Invoice ${invoiceNumber} - ${formatter.format(total \u002F 100)}`}>\n      \u003CHeading style={styles.h1}>Invoice #{invoiceNumber}\u003C\u002FHeading>\n      \u003CText style={styles.text}>Hi {name},\u003C\u002FText>\n      \u003CText style={styles.text}>Here's your invoice from MyApp. Thank you for your continued support.\u003C\u002FText>\n\n      {\u002F* Invoice Meta *\u002F}\n      \u003CSection style={styles.metaBox}>\n        \u003CRow>\n          \u003CColumn>\u003CText style={styles.metaLabel}>Invoice Date\u003C\u002FText>\u003CText style={styles.metaValue}>{invoiceDate}\u003C\u002FText>\u003C\u002FColumn>\n          \u003CColumn>\u003CText style={styles.metaLabel}>Due Date\u003C\u002FText>\u003CText style={styles.metaValue}>{dueDate}\u003C\u002FText>\u003C\u002FColumn>\n          \u003CColumn>\u003CText style={styles.metaLabel}>Amount Due\u003C\u002FText>\u003CText style={styles.metaValueLarge}>{formatter.format(total \u002F 100)}\u003C\u002FText>\u003C\u002FColumn>\n        \u003C\u002FRow>\n      \u003C\u002FSection>\n\n      {\u002F* Line Items *\u002F}\n      \u003CSection style={styles.table}>\n        \u003CRow style={styles.tableHeader}>\n          \u003CColumn>\u003CText style={styles.tableHeaderText}>Description\u003C\u002FText>\u003C\u002FColumn>\n          \u003CColumn>\u003CText style={{ ...styles.tableHeaderText, textAlign: \"right\" }}>Amount\u003C\u002FText>\u003C\u002FColumn>\n        \u003C\u002FRow>\n        {items.map((item, i) => (\n          \u003CRow key={i} style={i % 2 === 0 ? styles.tableRowEven : styles.tableRowOdd}>\n            \u003CColumn>\u003CText style={styles.tableCell}>{item.description}\u003C\u002FText>\u003C\u002FColumn>\n            \u003CColumn>\u003CText style={{ ...styles.tableCell, textAlign: \"right\" }}>{formatter.format(item.amount \u002F 100)}\u003C\u002FText>\u003C\u002FColumn>\n          \u003C\u002FRow>\n        ))}\n        \u003CHr style={styles.divider} \u002F>\n        \u003CRow>\n          \u003CColumn>\u003CText style={styles.totalLabel}>Total\u003C\u002FText>\u003C\u002FColumn>\n          \u003CColumn>\u003CText style={styles.totalValue}>{formatter.format(total \u002F 100)}\u003C\u002FText>\u003C\u002FColumn>\n        \u003C\u002FRow>\n      \u003C\u002FSection>\n\n      \u003CButton href={downloadUrl} style={styles.button}>Download PDF Invoice\u003C\u002FButton>\n    \u003C\u002FEmailLayout>\n  )\n}\n\nexport default InvoiceEmail\n\nconst styles = {\n  h1: { fontSize: \"24px\", fontWeight: \"700\", color: \"#111827\", margin: \"0 0 16px\" },\n  text: { fontSize: \"15px\", lineHeight: \"1.6\", color: \"#374151\", margin: \"0 0 12px\" },\n  metaBox: { backgroundColor: \"#f9fafb\", borderRadius: \"8px\", padding: \"16px\", margin: \"16px 0\" },\n  metaLabel: { fontSize: \"12px\", color: \"#6b7280\", fontWeight: \"600\", textTransform: \"uppercase\" as const, margin: \"0 0 4px\" },\n  metaValue: { fontSize: \"14px\", color: \"#111827\", margin: 0 },\n  metaValueLarge: { fontSize: \"20px\", fontWeight: \"700\", color: \"#4f46e5\", margin: 0 },\n  table: { width: \"100%\", margin: \"16px 0\" },\n  tableHeader: { backgroundColor: \"#f3f4f6\", borderRadius: \"4px\" },\n  tableHeaderText: { fontSize: \"12px\", fontWeight: \"600\", color: \"#374151\", padding: \"8px 12px\", textTransform: \"uppercase\" as const },\n  tableRowEven: { backgroundColor: \"#ffffff\" },\n  tableRowOdd: { backgroundColor: \"#f9fafb\" },\n  tableCell: { fontSize: \"14px\", color: \"#374151\", padding: \"10px 12px\" },\n  divider: { borderColor: \"#e5e5e5\", margin: \"8px 0\" },\n  totalLabel: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", padding: \"8px 12px\" },\n  totalValue: { fontSize: \"16px\", fontWeight: \"700\", color: \"#111827\", textAlign: \"right\" as const, padding: \"8px 12px\" },\n  button: { backgroundColor: \"#4f46e5\", color: \"#fff\", borderRadius: \"6px\", padding: \"12px 24px\", fontSize: \"15px\", fontWeight: \"600\", textDecoration: \"none\" },\n}\n```\n\n---\n\n## Unified Send Function\n\n```typescript\n\u002F\u002F emails\u002Flib\u002Fsend.ts\nimport { Resend } from \"resend\"\nimport { render } from \"@react-email\u002Frender\"\nimport { WelcomeEmail } from \"..\u002Ftemplates\u002Fwelcome\"\nimport { InvoiceEmail } from \"..\u002Ftemplates\u002Finvoice\"\nimport { addTrackingParams } from \".\u002Ftracking\"\n\nconst resend = new Resend(process.env.RESEND_API_KEY)\n\ntype EmailPayload =\n  | { type: \"welcome\"; props: Parameters\u003Ctypeof WelcomeEmail>[0] }\n  | { type: \"invoice\"; props: Parameters\u003Ctypeof InvoiceEmail>[0] }\n\nexport async function sendEmail(to: string, payload: EmailPayload) {\n  const templates = {\n    welcome: { component: WelcomeEmail, subject: \"Welcome to MyApp — confirm your email\" },\n    invoice: { component: InvoiceEmail, subject: `Invoice from MyApp` },\n  }\n\n  const template = templates[payload.type]\n  const html = render(template.component(payload.props as any))\n  const trackedHtml = addTrackingParams(html, { campaign: payload.type })\n\n  const result = await resend.emails.send({\n    from: \"MyApp \u003Chello@yourapp.com>\",\n    to,\n    subject: template.subject,\n    html: trackedHtml,\n    tags: [{ name: \"email-type\", value: payload.type }],\n  })\n\n  return result\n}\n```\n\n---\n\n## Preview Server Setup\n\n```typescript\n\u002F\u002F package.json scripts\n{\n  \"scripts\": {\n    \"email:dev\": \"email dev --dir emails\u002Ftemplates --port 3001\",\n    \"email:build\": \"email export --dir emails\u002Ftemplates --outDir emails\u002Fout\"\n  }\n}\n\n\u002F\u002F Run: npm run email:dev\n\u002F\u002F Opens: http:\u002F\u002Flocalhost:3001\n\u002F\u002F Shows all templates with live preview and hot reload\n```\n\n---\n\n## i18n Support\n\n```typescript\n\u002F\u002F emails\u002Fi18n\u002Fen.ts\nexport const en = {\n  welcome: {\n    preview: (name: \"string-welcome-to-myapp-name\"\n    heading: (name: \"string-welcome-to-myapp-name\"\n    body: (days: number) => `You've got ${days} days to explore everything.`,\n    cta: \"Confirm Email Address\",\n  },\n}\n\n\u002F\u002F emails\u002Fi18n\u002Fde.ts\nexport const de = {\n  welcome: {\n    preview: (name: \"string-willkommen-bei-myapp-name\"\n    heading: (name: \"string-willkommen-bei-myapp-name\"\n    body: (days: number) => `Du hast ${days} Tage Zeit, alles zu erkunden.`,\n    cta: \"E-Mail-Adresse bestätigen\",\n  },\n}\n\n\u002F\u002F Usage in template\nimport { en, de } from \"..\u002Fi18n\"\nconst t = locale === \"de\" ? de : en\n```\n\n---\n\n## Spam Score Optimization Checklist\n\n- [ ] Sender domain has SPF, DKIM, and DMARC records configured\n- [ ] From address uses your own domain (not gmail.com\u002Fhotmail.com)\n- [ ] Subject line under 50 characters, no ALL CAPS, no \"FREE!!!\"\n- [ ] Text-to-image ratio: at least 60% text\n- [ ] Plain text version included alongside HTML\n- [ ] Unsubscribe link in every marketing email (CAN-SPAM, GDPR)\n- [ ] No URL shorteners — use full branded links\n- [ ] No red-flag words: \"guarantee\", \"no risk\", \"limited time offer\" in subject\n- [ ] Single CTA per email — no 5 different buttons\n- [ ] Image alt text on every image\n- [ ] HTML validates — no broken tags\n- [ ] Test with Mail-Tester.com before first send (target: 9+\u002F10)\n\n---\n\n## Analytics Tracking\n\n```typescript\n\u002F\u002F emails\u002Flib\u002Ftracking.ts\ninterface TrackingParams {\n  campaign: string\n  medium?: string\n  source?: string\n}\n\nexport function addTrackingParams(html: string, params: TrackingParams): string {\n  const utmString = new URLSearchParams({\n    utm_source: params.source ?? \"email\",\n    utm_medium: params.medium ?? \"transactional\",\n    utm_campaign: params.campaign,\n  }).toString()\n\n  \u002F\u002F Add UTM params to all links in the email\n  return html.replace(\u002Fhref=\"(https?:\\\u002F\\\u002F[^\"]+)\"\u002Fg, (match, url) => {\n    const separator = url.includes(\"?\") ? \"&\" : \"?\"\n    return `href=\"${url}${separator}${utmString}\"`\n  })\n}\n```\n\n---\n\n## Common Pitfalls\n\n- **Inline styles required** — most email clients strip `\u003Chead>` styles; React Email handles this\n- **Max width 600px** — anything wider breaks on Gmail mobile\n- **No flexbox\u002Fgrid** — use `\u003CRow>` and `\u003CColumn>` from react-email, not CSS grid\n- **Dark mode media queries** — must use `!important` to override inline styles\n- **Missing plain text** — all major providers have a plain text field; always populate it\n- **Transactional vs marketing** — use separate sending domains\u002FIPs to protect deliverability\n","","imported","https:\u002F\u002Fgithub.com\u002Falirezarezvani\u002Fclaude-skills","user_system_seed","SkillOPIC",true,81,731,"2026-05-16 13:56:14",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"前端开发","frontend","mdi-language-html5","HTML\u002FCSS\u002FJavaScript\u002F框架相关",1,96,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"30f863be-276a-411e-854c-d0a176cb25c4","1.0.0","email-template-builder.zip",5234,"uploads\u002Fskills\u002F3dde80b5-338f-4329-9325-2a776827a563\u002Femail-template-builder.zip","03b5f49d42e15ad4b1b9597d9c014f351d2b666e9fc50ea5d4cfec3ce68bbcd4","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":15552}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]