邮件发送服务,支持多服务商和模板系统
@mono/email
邮件发送服务包,提供多邮件服务商抽象、模板支持和类型安全的 API。基于工厂模式,轻松切换邮件服务商。
特性
- 多服务商支持 - 支持 Resend,可扩展 SendGrid、SMTP 等
- 类型安全 - 完整的 TypeScript 类型定义
- 简洁 API - 工厂模式创建服务商,统一发送接口
- 错误处理 - 结构化的错误响应
- 灵活配置 - 自定义发件人、回复地址等
安装
pnpm add @mono/email快速开始
import { createEmailProvider } from '@mono/email';
// 创建 Resend 服务商实例
const emailProvider = createEmailProvider('resend', {
apiKey: process.env.RESEND_API_KEY!,
defaultFrom: 'noreply@yourdomain.com',
});
// 发送邮件
const result = await emailProvider.send({
to: 'user@example.com',
subject: '欢迎加入!',
html: '<h1>欢迎</h1><p>感谢您的注册!</p>',
});
if (result.success) {
console.log(`邮件已发送,ID: ${result.messageId}`);
} else {
console.error(`发送失败: ${result.error}`);
}API 参考
createEmailProvider(type, config)
创建邮件服务商实例。
参数:
type:'resend'- 服务商类型config.apiKey: string - API 密钥config.defaultFrom?: string - 默认发件人
返回: EmailProvider
const provider = createEmailProvider('resend', {
apiKey: process.env.RESEND_API_KEY!,
defaultFrom: 'noreply@yourdomain.com',
});sendEmail(provider, options)
辅助函数,使用指定服务商发送邮件。
import { createEmailProvider, sendEmail } from '@mono/email';
const provider = createEmailProvider('resend', { apiKey: '...' });
await sendEmail(provider, {
to: 'user@example.com',
subject: '通知',
html: '<p>内容</p>',
});EmailOptions
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
to | string | string[] | 是 | 收件人 |
subject | string | 是 | 主题 |
html | string | 否 | HTML 内容 |
text | string | 否 | 纯文本内容 |
from | string | 否 | 发件人(覆盖默认) |
replyTo | string | 否 | 回复地址 |
EmailResponse
| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 是否成功 |
messageId | string? | 邮件 ID |
error | string? | 错误信息 |
实战示例
示例 1: 验证码邮件
// lib/email.ts
import { createEmailProvider } from '@mono/email';
export const emailProvider = createEmailProvider('resend', {
apiKey: process.env.RESEND_API_KEY!,
defaultFrom: 'auth@yourdomain.com',
});
export async function sendVerificationEmail(email: string, code: string) {
return emailProvider.send({
to: email,
subject: '邮箱验证码',
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h2>邮箱验证</h2>
<p>您的验证码是:</p>
<div style="font-size: 32px; font-weight: bold; padding: 16px;
background: #f5f5f5; border-radius: 8px; text-align: center;">
${code}
</div>
<p style="color: #666;">验证码 10 分钟内有效,请勿泄露给他人。</p>
</div>
`,
});
}示例 2: 密码重置邮件
// lib/email.ts
export async function sendResetPasswordEmail(email: string, resetUrl: string) {
return emailProvider.send({
to: email,
subject: '密码重置',
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h2>密码重置</h2>
<p>您请求了密码重置,点击下方按钮设置新密码:</p>
<a href="${resetUrl}"
style="display: inline-block; padding: 12px 24px;
background: #000; color: #fff; text-decoration: none;
border-radius: 8px;">
重置密码
</a>
<p style="color: #666; margin-top: 16px;">
如果您没有请求密码重置,请忽略此邮件。链接 1 小时内有效。
</p>
</div>
`,
});
}示例 3: 联系表单 API 路由
// app/api/contact/route.ts
import { emailProvider } from '@/lib/email';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const { name, email, message } = await req.json();
const result = await emailProvider.send({
to: 'admin@yourdomain.com',
subject: `联系表单: ${name}`,
html: `
<p><strong>姓名:</strong> ${name}</p>
<p><strong>邮箱:</strong> ${email}</p>
<p><strong>消息:</strong></p>
<p>${message}</p>
`,
replyTo: email,
});
if (!result.success) {
return NextResponse.json({ error: '发送失败' }, { status: 500 });
}
return NextResponse.json({ success: true });
}示例 4: 邀请成员邮件
// app/api/invite/route.ts
import { emailProvider } from '@/lib/email';
import { auth } from '@mono/auth';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const session = await auth.api.getSession({ headers: req.headers });
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { email, role } = await req.json();
const inviteUrl = `${process.env.NEXT_PUBLIC_APP_URL}/invite?token=xxx`;
const result = await emailProvider.send({
to: email,
subject: `${session.user.name} 邀请您加入团队`,
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<h2>团队邀请</h2>
<p>${session.user.name} 邀请您以 <strong>${role}</strong> 身份加入团队。</p>
<a href="${inviteUrl}"
style="display: inline-block; padding: 12px 24px;
background: #000; color: #fff; text-decoration: none;
border-radius: 8px; margin: 16px 0;">
接受邀请
</a>
<p style="color: #666;">邀请链接 7 天内有效。</p>
</div>
`,
});
if (!result.success) {
return NextResponse.json({ error: '邮件发送失败' }, { status: 500 });
}
return NextResponse.json({ success: true });
}示例 5: 批量通知
import { emailProvider } from '@/lib/email';
interface Recipient {
email: string;
name: string;
}
export async function sendBatchNotification(
recipients: Recipient[],
title: string,
content: string,
) {
const results = await Promise.allSettled(
recipients.map((r) =>
emailProvider.send({
to: r.email,
subject: title,
html: `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
<p>Hi ${r.name},</p>
${content}
</div>
`,
}),
),
);
const succeeded = results.filter(
(r) => r.status === 'fulfilled' && r.value.success,
).length;
return { total: recipients.length, succeeded, failed: recipients.length - succeeded };
}示例 6: 自定义服务商
import type { EmailProvider, EmailOptions, EmailResponse } from '@mono/email';
// 实现自定义 SMTP 服务商
class SmtpProvider implements EmailProvider {
private transporter: any;
constructor(config: { host: string; port: number; auth: { user: string; pass: string } }) {
// 使用 nodemailer 等库初始化
// this.transporter = nodemailer.createTransport(config);
}
async send(options: EmailOptions): Promise<EmailResponse> {
try {
// 发送邮件逻辑
return { success: true, messageId: 'xxx' };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
}
// 使用自定义服务商
const smtpProvider = new SmtpProvider({
host: 'smtp.example.com',
port: 587,
auth: { user: 'user', pass: 'pass' },
});
await smtpProvider.send({
to: 'user@example.com',
subject: 'Test',
html: '<p>Hello</p>',
});环境变量
# Resend API 密钥(必需)
RESEND_API_KEY=re_xxxxxxxxxxxxx
# 默认发件人(可选)
EMAIL_DEFAULT_FROM=noreply@yourdomain.com集成指南
1. 初始化服务
// lib/email.ts
import { createEmailProvider } from '@mono/email';
export const emailProvider = createEmailProvider('resend', {
apiKey: process.env.RESEND_API_KEY!,
defaultFrom: process.env.EMAIL_DEFAULT_FROM || 'noreply@yourdomain.com',
});2. 与认证系统集成
// lib/auth/email.ts
import { emailProvider } from '@/lib/email';
export const emailHandlers = {
sendVerificationEmail: async (email: string, url: string) => {
await emailProvider.send({
to: email,
subject: '验证您的邮箱',
html: `<a href="${url}">点击验证</a>`,
});
},
sendResetPasswordEmail: async (email: string, url: string) => {
await emailProvider.send({
to: email,
subject: '重置密码',
html: `<a href="${url}">重置密码</a>`,
});
},
};3. DNS 配置
在 Resend 中添加域名后,配置以下 DNS 记录:
| 类型 | 名称 | 值 |
|---|---|---|
| MX | @ | feedback-smtp.region.amazonses.com |
| TXT | @ | v=spf1 include:amazonses.com ~all |
| CNAME | resend._domainkey | 由 Resend 提供 |
最佳实践
- 统一初始化 - 在
lib/email.ts中创建单例实例 - 使用环境变量 - 不要硬编码 API 密钥和发件人
- 错误处理 - 始终检查
result.success - 双格式内容 - 同时提供
html和text提高兼容性 - 域名验证 - 确保发件人域名配置了 SPF/DKIM/DMARC
故障排查
邮件发送失败
- 检查
RESEND_API_KEY是否正确 - 确认发件人域名已在 Resend 验证
- 检查收件人地址格式
邮件进入垃圾箱
- 完成 SPF、DKIM、DMARC 配置
- 避免邮件中过多图片或可疑链接
- 使用认证域名发送
批量发送限制
Resend 免费版限制 100 封/天,建议:
- 生产环境使用付费版本
- 实现发送队列避免突发大量请求
- 监控发送失败率
相关文档
- Resend 文档
- @mono/auth 包 - 认证邮件
- @mono/sms 包 - 短信服务
许可证
MIT