详细分析 ▾
运行时依赖
版本
安装命令 点击复制
技能文档
等级: 强大 类别: 工程团队 领域: 事务性邮件 / 通信基础设施
概述
构建完整的事务性邮件系统:React Email 模板、邮件服务商集成、预览服务器、i18n 支持、深色模式、垃圾邮件优化和分析追踪。输出可用于 Resend、Postmark、SendGrid 或 AWS SES 的生产级代码。
核心功能
- React Email 模板(欢迎邮件、验证邮件、密码重置、发票、通知、周报)
- MJML 模板以实现最大的邮件客户端兼容性
- 统一发送接口支持多服务商
- 带热重载的本地预览服务器
- 带类型化翻译键的 i18n/本地化
- 使用媒体查询的深色模式支持
- 垃圾邮件分数优化检查清单
- 带 UTM 参数的打开/点击追踪
使用场景
- 为新产品设置事务性邮件
- 从传统邮件系统迁移
- 添加新的邮件类型(发票、周报、通知)
- 调试邮件投递问题
- 为邮件模板实现 i18n
项目结构
emails/
├── components/
│ ├── layout/
│ │ ├── email-layout.tsx # 基础布局,包含品牌页眉/页脚
│ │ └── email-button.tsx # CTA 按钮组件
│ ├── partials/
│ │ ├── header.tsx
│ │ └── footer.tsx
├── templates/
│ ├── welcome.tsx
│ ├── verify-email.tsx
│ ├── password-reset.tsx
│ ├── invoice.tsx
│ ├── notification.tsx
│ └── weekly-digest.tsx
├── lib/
│ ├── send.ts # 统一发送函数
│ ├── providers/
│ │ ├── resend.ts
│ │ ├── postmark.ts
│ │ └── ses.ts
│ └── tracking.ts # UTM + 分析
├── i18n/
│ ├── en.ts
│ └── de.ts
└── preview/ # 开发预览服务器
└── server.ts
基础邮件布局
// 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 /}
{/ 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",
},
}
欢迎邮件
// 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", }, }
发票邮件
// 发票邮件模板 // 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 (
发票 ${invoiceNumber} - ${formatter.format(total / 100)} 发票 #{invoiceNumber} Hi {name}, 这是您来自 MyApp 的发票。感谢您一直以来的支持。 {/ 发票元数据 /}
|
发票日期 {invoiceDate} 到期日期 {dueDate} 应付金额 {formatter.format(total / 100)} {/ 明细项目 /}
{items.map((item, i) => (
描述 金额 ))}
{item.description} {formatter.format(item.amount / 100)}
|
合计 {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", }, }
统一发送函数
// 邮件发送库
// 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: "欢迎来到 MyApp — 请确认您的邮箱",
},
invoice: {
component: InvoiceEmail,
subject: 来自 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
}
预览服务器设置
// package.json 脚本
{
"scripts": {
"email:dev": "email dev --dir emails/templates --port 3001",
"email:build": "email export --dir emails/templates --outDir emails/out"
}
}// 运行:npm run email:dev
// 打开:http://localhost:3001
// 显示所有模板的实时预览和热重载
国际化支持
// 英文翻译
// 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: "确认邮箱地址",
},
}// 德文翻译
// 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",
},
}
// 在模板中使用
import { en, de } from "../i18n"
const t = locale === "de" ? de : en
垃圾邮件分数优化检查清单
- [ ] 发件人域名已配置 SPF、DKIM 和 DMARC 记录
- [ ] 发件人地址使用您自己的域名(不是 gmail.com/hotmail.com)
- [ ] 主题行少于 50 个字符,不使用全大写,不使用 "FREE!!!"
- [ ] 文本与图片比例:至少 60% 为文本
- [ ] HTML 版本附带纯文本版本
- [ ] 每封营销邮件包含退订链接(CAN-SPAM、GDPR)
- [ ] 不使用 URL 缩短器 — 使用完整的品牌链接
- [ ] 主题行中避免敏感词:"guarantee"、"no risk"、"limited time offer"
- [ ] 每封邮件一个 CTA — 不使用 5 个不同的按钮
- [ ] 每张图片都有 alt 文本
- [ ] HTML 验证通过 — 无破损标签
- [ ] 首次发送前使用 Mail-Tester.com 测试(目标:9+/10)
分析跟踪
// 邮件跟踪库
// 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()
// 为邮件中的所有链接添加 UTM 参数
return html.replace(/href="(https?:\/\/[^"]+)"/g, (match, url) => {
const separator = url.includes("?") ? "&" : "?"
return href="${url}${separator}${utmString}"
})
}
常见陷阱
- 必须使用内联样式 — 大多数邮件客户端会剥离
样式;React Email 会处理这个问题 - 最大宽度 600px — 超过这个宽度在 Gmail 移动端会显示异常
- 不支持 flexbox/grid — 使用 react-email 的
和,而不是 CSS grid - 深色模式媒体查询 — 必须使用
!important来覆盖内联样式 - 缺少纯文本版本 — 所有主流邮件提供商都有纯文本字段;务必填充它
- 事务性邮件 vs 营销邮件 — 使用单独的发件域名/IP 来保护送达率
免费技能或插件可能存在安全风险,如需更匹配、更安全的方案,建议联系付费定制