文档
开发指南邮件系统

邮件系统

🚀 Cheatsheet (快速执行)

目标: 15分钟内配置邮件发送功能,让用户可以收到注册确认邮件

最快上手方式:

# 1. 配置邮件服务 (使用 Gmail SMTP)
# 前往 https://myaccount.google.com/apppasswords 生成应用密码

# 2. 配置环境变量
echo 'SMTP_HOST=smtp.gmail.com' >> .env.local
echo 'SMTP_PORT=587' >> .env.local
echo 'SMTP_USERNAME=你的邮箱@gmail.com' >> .env.local
echo 'SMTP_PASSWORD=你的应用密码' >> .env.local
echo 'FROM_EMAIL=你的邮箱@gmail.com' >> .env.local

# 3. 测试邮件发送
# 注册新用户,检查邮箱是否收到验证邮件

立即生效: 用户注册后自动收到邮箱验证邮件,支持密码重置邮件

❓ 为什么要配置邮件系统

对 MVP 的核心价值:

  • 用户信任: 邮箱验证让用户相信你的产品是正规的
  • 账号安全: 密码重置功能必需邮件验证
  • 用户激活: 通过邮件引导用户回到产品
  • 营销触达: 产品更新、活动通知直达用户邮箱

真实场景举例: 用户注册你的 AI 写作工具,输入邮箱后收到一封精美的欢迎邮件,引导完成设置,用户感觉很专业,提高了对产品的信任度。

🤔 为什么选择 React Email + SMTP

我们的实战经验:

  • 开发体验好: 像写 React 组件一样写邮件模板
  • 成本可控: SMTP 免费额度高,第三方服务贵
  • 样式现代: Tailwind CSS 让邮件美观专业
  • 类型安全: TypeScript 支持,减少邮件发送错误

对比其他方案:

SendGrid/Mailgun:  功能强大但收费,小项目用不上
 HTML 邮件:      维护困难,样式兼容性差
Nodemailer:       配置复杂,模板管理不方便
React Email: 现代化、易维护、免费

🧠 简要原理 & 最简案例

邮件发送流程: 触发事件 → 选择邮件模板 → 填充数据 → 通过 SMTP 发送 → 用户收到邮件

最简发送示例:

// 1. 邮件模板 (src/lib/mail/emails/Welcome.tsx)
import { Button, Html, Text } from '@react-email/components'

interface WelcomeEmailProps {
  username: string
  verifyUrl: string
}

export function WelcomeEmail({ username, verifyUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Text>Hi {username}!</Text>
      <Text>Welcome to our platform. Please verify your email:</Text>
      <Button href={verifyUrl}>Verify Email</Button>
    </Html>
  )
}

// 2. 发送邮件函数 (src/lib/mail/send.ts)
import { render } from '@react-email/render'
import { createTransport } from 'nodemailer'

export async function sendWelcomeEmail(email: string, username: string) {
  const verifyUrl = `${process.env.NEXT_PUBLIC_SITE_URL}/verify?email=${email}`
  
  const emailHtml = render(WelcomeEmail({ username, verifyUrl }))
  
  const transporter = createTransport({
    host: process.env.SMTP_HOST,
    port: Number(process.env.SMTP_PORT),
    secure: false,
    auth: {
      user: process.env.SMTP_USERNAME,
      pass: process.env.SMTP_PASSWORD,
    },
  })
  
  await transporter.sendMail({
    from: process.env.FROM_EMAIL,
    to: email,
    subject: 'Welcome! Please verify your email',
    html: emailHtml,
  })
}

🛠️ 实际操作步骤

1. 获取邮件服务凭据 (5分钟)

使用 Gmail SMTP (推荐新手):

1. 登录 Gmail 账号
2. 前往 https://myaccount.google.com/security
3. 开启两步验证
4. 前往 https://myaccount.google.com/apppasswords
5. 选择 "邮件" "其他设备"
6. 生成应用密码,复制保存

或使用 Resend (推荐生产环境):

1. 访问 https://resend.com 注册账号
2. 验证域名 (可选,用子域名也行)
3. 创建 API Key
4. 每月免费 3000 封邮件

2. 配置环境变量 (5分钟)

Gmail SMTP 配置:

# .env.local
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
FROM_EMAIL=your-email@gmail.com
FROM_NAME=你的产品名

Resend 配置:

# .env.local
RESEND_API_KEY=re_xxx_your_api_key
FROM_EMAIL=noreply@yourdomain.com
FROM_NAME=你的产品名

3. 创建邮件模板 (10分钟)

欢迎邮件模板:

// src/lib/mail/emails/WelcomeEmail.tsx
import {
  Body,
  Button,
  Container,
  Head,
  Html,
  Img,
  Link,
  Preview,
  Section,
  Text,
} from '@react-email/components'

interface WelcomeEmailProps {
  username: string
  verifyUrl: string
}

export function WelcomeEmail({ username, verifyUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>欢迎加入我们!请验证您的邮箱</Preview>
      <Body style={main}>
        <Container style={container}>
          <Section style={logoContainer}>
            <Img
              src={`${process.env.NEXT_PUBLIC_SITE_URL}/logo.png`}
              width="120"
              height="36"
              alt="Logo"
            />
          </Section>
          
          <Section style={content}>
            <Text style={paragraph}>Hi {username},</Text>
            <Text style={paragraph}>
              欢迎加入我们的平台!为了确保账号安全,请点击下方按钮验证您的邮箱地址。
            </Text>
            
            <Button style={button} href={verifyUrl}>
              验证邮箱
            </Button>
            
            <Text style={paragraph}>
              如果按钮无法点击,请复制以下链接到浏览器:
            </Text>
            <Link href={verifyUrl} style={link}>
              {verifyUrl}
            </Link>
            
            <Text style={paragraph}>
              如果您没有注册过账号,请忽略此邮件。
            </Text>
          </Section>
        </Container>
      </Body>
    </Html>
  )
}

// 样式定义
const main = {
  backgroundColor: '#f6f9fc',
  fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
}

const container = {
  backgroundColor: '#ffffff',
  margin: '0 auto',
  padding: '20px 0 48px',
  marginBottom: '64px',
}

const logoContainer = {
  textAlign: 'center' as const,
  padding: '20px 0',
}

const content = {
  padding: '0 48px',
}

const paragraph = {
  fontSize: '16px',
  lineHeight: '1.4',
  color: '#334155',
  margin: '16px 0',
}

const button = {
  backgroundColor: '#3b82f6',
  borderRadius: '5px',
  color: '#fff',
  fontSize: '16px',
  fontWeight: 'bold',
  textDecoration: 'none',
  textAlign: 'center' as const,
  display: 'block',
  width: '200px',
  padding: '14px 7px',
  margin: '20px auto',
}

const link = {
  color: '#3b82f6',
  textDecoration: 'underline',
}

4. 实现邮件发送服务 (10分钟)

邮件发送抽象层:

// src/lib/mail/send.ts
import { render } from '@react-email/render'
import { createTransport } from 'nodemailer'
import { Resend } from 'resend'

// 邮件提供商类型
type EmailProvider = 'smtp' | 'resend'

interface EmailOptions {
  to: string
  subject: string
  html: string
  text?: string
}

class MailService {
  private provider: EmailProvider
  private transporter?: any
  private resend?: Resend

  constructor() {
    // 根据环境变量选择提供商
    this.provider = process.env.RESEND_API_KEY ? 'resend' : 'smtp'
    this.initialize()
  }

  private initialize() {
    if (this.provider === 'resend') {
      this.resend = new Resend(process.env.RESEND_API_KEY!)
    } else {
      this.transporter = createTransport({
        host: process.env.SMTP_HOST,
        port: Number(process.env.SMTP_PORT),
        secure: false,
        auth: {
          user: process.env.SMTP_USERNAME,
          pass: process.env.SMTP_PASSWORD,
        },
      })
    }
  }

  async sendEmail({ to, subject, html, text }: EmailOptions) {
    const from = `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`

    try {
      if (this.provider === 'resend') {
        await this.resend!.emails.send({
          from,
          to,
          subject,
          html,
          text,
        })
      } else {
        await this.transporter!.sendMail({
          from,
          to,
          subject,
          html,
          text,
        })
      }
      
      console.log(`邮件发送成功: ${to}`)
    } catch (error) {
      console.error('邮件发送失败:', error)
      throw error
    }
  }

  // 渲染 React 邮件组件
  renderTemplate(component: React.ReactElement) {
    return {
      html: render(component),
      text: render(component, { plainText: true }),
    }
  }
}

export const mailService = new MailService()

业务邮件函数:

// src/lib/mail/templates.ts
import { mailService } from './send'
import { WelcomeEmail } from './emails/WelcomeEmail'
import { PasswordResetEmail } from './emails/PasswordResetEmail'

export async function sendWelcomeEmail(email: string, username: string) {
  const verifyUrl = `${process.env.NEXT_PUBLIC_SITE_URL}/verify?email=${encodeURIComponent(email)}`
  
  const template = mailService.renderTemplate(
    WelcomeEmail({ username, verifyUrl })
  )
  
  await mailService.sendEmail({
    to: email,
    subject: `欢迎加入 ${process.env.FROM_NAME}!`,
    ...template,
  })
}

export async function sendPasswordResetEmail(email: string, resetUrl: string) {
  const template = mailService.renderTemplate(
    PasswordResetEmail({ email, resetUrl })
  )
  
  await mailService.sendEmail({
    to: email,
    subject: '重置密码',
    ...template,
  })
}

export async function sendNotificationEmail(
  email: string, 
  title: string, 
  message: string
) {
  const template = mailService.renderTemplate(
    NotificationEmail({ title, message })
  )
  
  await mailService.sendEmail({
    to: email,
    subject: title,
    ...template,
  })
}

5. 集成到用户注册流程 (5分钟)

在注册API中发送邮件:

// app/api/auth/register/route.ts
import { sendWelcomeEmail } from '@/lib/mail/templates'

export async function POST(request: Request) {
  const { email, password, name } = await request.json()
  
  try {
    // 创建用户账号
    const user = await createUser({ email, password, name })
    
    // 发送欢迎邮件
    await sendWelcomeEmail(email, name)
    
    return Response.json({ 
      success: true, 
      message: '注册成功!请检查邮箱验证邮件' 
    })
    
  } catch (error) {
    console.error('注册失败:', error)
    return Response.json({ error: '注册失败' }, { status: 500 })
  }
}

💡 常用邮件模板

密码重置邮件

// src/lib/mail/emails/PasswordResetEmail.tsx
export function PasswordResetEmail({ email, resetUrl }: {
  email: string
  resetUrl: string
}) {
  return (
    <Html>
      <Head />
      <Preview>重置您的密码</Preview>
      <Body style={main}>
        <Container style={container}>
          <Text style={paragraph}>
            您请求重置 {email} 的密码。
          </Text>
          
          <Button style={button} href={resetUrl}>
            重置密码
          </Button>
          
          <Text style={paragraph}>
            此链接将在 24 小时后过期。如果您没有请求重置密码,请忽略此邮件。
          </Text>
        </Container>
      </Body>
    </Html>
  )
}

系统通知邮件

// src/lib/mail/emails/NotificationEmail.tsx
export function NotificationEmail({ title, message }: {
  title: string
  message: string
}) {
  return (
    <Html>
      <Head />
      <Preview>{title}</Preview>
      <Body style={main}>
        <Container style={container}>
          <Text style={title}>{title}</Text>
          <Text style={paragraph}>{message}</Text>
          
          <Button style={button} href={`${process.env.NEXT_PUBLIC_SITE_URL}/dashboard`}>
            查看详情
          </Button>
        </Container>
      </Body>
    </Html>
  )
}

🆘 常见问题解决

问题1: Gmail 发送失败

# 检查清单:
1. 是否开启了两步验证?
2. 是否使用应用密码而不是账号密码?
3. 邮箱是否被 Google 限制?

# 解决方案:
# 使用不同的邮箱服务商或 Resend

问题2: 邮件进入垃圾箱

// 优化发送邮件的内容和格式
1. 添加明确的发件人信息
2. 避免过多的推广用词
3. 确保邮件模板格式正确
4. 配置 SPF/DKIM 记录(生产环境)

问题3: 邮件模板样式问题

# 邮件客户端兼容性限制
1. 使用内联样式而不是 CSS
2. 避免使用复杂的 CSS 属性
3. 测试主流邮件客户端显示效果

📊 邮件效果监控

基础统计

// src/lib/mail/analytics.ts
export async function trackEmailSent(email: string, type: string) {
  await db.emailLog.create({
    data: {
      email,
      type,
      status: 'sent',
      sentAt: new Date()
    }
  })
}

export async function getEmailStats() {
  const stats = await db.emailLog.groupBy({
    by: ['type', 'status'],
    _count: true,
    where: {
      sentAt: {
        gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // 30天内
      }
    }
  })
  
  return stats
}

📎 延伸阅读

技术文档:

邮件服务商:

  • Resend - 开发者友好的邮件服务
  • SendGrid - 企业级邮件平台

最佳实践:


下一步: 邮件系统配置完成后,查看 [用户认证]./authentication) 了解如何在认证流程中使用邮件验证。