邮件与通知服务
问题
如何在 Node.js 中实现邮件发送和多渠道通知?
面试速答版
Node.js 怎么发邮件和多渠道通知? 核心是「统一通知服务 + 渠道适配器 + 异步队列」:
- 邮件:
Nodemailer+ SMTP 是基础方案,生产推荐SendGrid/AWS SES/Resend——送达率高、有反垃圾信誉。 - 短信:
Twilio、阿里云短信,主要用于验证码和告警。 - App 推送:Android 走
FCM、iOS 走APNs,国内还要适配华为/小米/OPPO 通道。 - 站内通知:
WebSocket/Socket.IO实时推送,结合数据库存离线消息。 - Webhook:系统间通知用 HTTP POST 回调(详见 Webhook 设计)。
为什么邮件必须走异步队列?
- SMTP 一次握手 + 投递可能耗时几秒,同步发送会阻塞用户请求、拖慢接口 P99。
- 注册/下单接口直接
await emailQueue.add(),把任务丢BullMQ/Kafka后立即返回,消费者异步发。 - 队列还能做重试 + 失败死信,邮件服务挂了不会丢消息。
- 模板用
MJML/React Email写,跨客户端兼容性比手撸 HTML 强。
答案
通知渠道
| 渠道 | 适用场景 | 方案 |
|---|---|---|
| 邮件 | 验证码、账单、营销 | Nodemailer + SMTP / SendGrid / AWS SES |
| 短信 | 验证码、告警 | Twilio / 阿里云短信 |
| App 推送 | 消息提醒 | Firebase FCM / APNs |
| WebSocket | 站内实时通知 | Socket.IO / ws |
| Webhook | 系统间通知 | HTTP POST 回调 |
邮件发送
email-service.ts
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
interface SendEmailOptions {
to: string;
subject: string;
html: string;
}
async function sendEmail({ to, subject, html }: SendEmailOptions) {
await transporter.sendMail({
from: '"My App" <noreply@example.com>',
to,
subject,
html,
});
}
// 使用模板
function getWelcomeEmailHtml(name: string): string {
return `
<h1>欢迎加入, ${name}!</h1>
<p>感谢你的注册。</p>
<a href="https://example.com/verify?token=xxx">验证邮箱</a>
`;
}
// 异步发送(不阻塞主流程)
app.post('/api/register', async (req, res) => {
const user = await createUser(req.body);
// 丢进队列异步发送
await emailQueue.add('welcome', { userId: user.id, email: user.email });
res.json(user);
});
通知服务架构
notification-service.ts
// 统一的通知服务,支持多渠道
interface NotificationPayload {
userId: string;
type: 'email' | 'sms' | 'push' | 'in-app';
template: string;
data: Record<string, unknown>;
}
class NotificationService {
async send(payload: NotificationPayload) {
switch (payload.type) {
case 'email':
return this.sendEmail(payload);
case 'sms':
return this.sendSMS(payload);
case 'push':
return this.sendPush(payload);
case 'in-app':
return this.sendInApp(payload);
}
}
// 批量发送(如营销邮件)
async sendBatch(payloads: NotificationPayload[]) {
// 分批入队,避免一次性发送过多
for (const batch of chunk(payloads, 100)) {
await notificationQueue.addBulk(
batch.map(p => ({ name: 'send', data: p }))
);
}
}
}
常见面试问题
Q1: 邮件发送为什么要异步?
答案:
SMTP 网络请求可能需要数秒,同步发送会阻塞用户请求。应该将邮件任务丢进消息队列异步处理,接口立即返回。
Q2: 如何避免邮件进垃圾箱?
答案:
- 配置 SPF、DKIM、DMARC 记录
- 使用专业邮件服务商(SendGrid、AWS SES)
- 维护发件人信誉,避免频繁发送
- 提供退订链接
Q3: 验证码的安全设计?
答案:
- 限制发送频率(1 分钟内不重复发送)
- 设置有效期(5-10 分钟)
- 限制验证次数(错误 5 次后失效)
- 验证后立即失效
相关链接
- 消息队列 - 异步处理
- Node.js 安全 - 邮件安全