← 返回博客
发邮件验证码OTPSMTP开发者
用邮件发验证码:API 接入完整示例
用 SMTP 或事务邮件 API 发邮件验证码的完整实现,包括密码学安全的随机数生成、Redis 存储校验、速率限制代码示例。
更新于 2026年10月12日

用邮件发验证码(OTP)是常见的身份验证手段:用户注册或登录时,系统发一封带 6 位数字的邮件,用户输入后完成验证。实现这个功能需要选对发信方式。
两种主流方案
事务邮件 API(Resend、SendGrid、Postmark 等):专为程序化发信设计,提供 SDK 和 REST API,支持高吞吐量,内置送达率优化,适合生产环境。缺点是有额外费用,国内用户访问速度因服务商而异。
SMTP 中继:用自己的域名邮箱或服务商提供的 SMTP 发信,适合量小或已有邮件托管服务的场景。Rovela 的邮箱支持 SMTP,可以直接用来发系统邮件。
用 Rovela SMTP 发验证码
连接参数:
- 主机:
mx.rovela.dev - 端口:465(SSL/TLS)或 587(STARTTLS)
- 用户名:你的邮件地址
- 密码:在控制台生成的应用专用密码
Node.js 示例(nodemailer):
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'mx.rovela.dev',
port: 465,
secure: true,
auth: {
user: 'noreply@yourcompany.com',
pass: process.env.SMTP_PASS, // 应用专用密码,不要硬编码
},
});
async function sendOtp(to, code) {
await transporter.sendMail({
from: '"Your App" <noreply@yourcompany.com>',
to,
subject: '你的验证码',
text: `你的验证码是 ${code},5 分钟内有效。`,
html: `<p>你的验证码是 <strong>${code}</strong>,5 分钟内有效。</p>`,
});
}
OTP 生成
验证码用密码学安全的随机数生成,不要用 Math.random():
const crypto = require('crypto');
function generateOtp(length = 6) {
// 生成 length 位纯数字 OTP
const max = Math.pow(10, length);
const buf = crypto.randomBytes(4);
return String(buf.readUInt32BE(0) % max).padStart(length, '0');
}
存储与校验
验证码存在服务端(数据库或 Redis),关联用户标识和过期时间。校验时做以下检查:
- 验证码存在且未过期(建议 5–10 分钟)
- 验证码匹配(用常数时间比较,防止时序攻击)
- 使用后立即删除(一次性)
- 限制同一邮箱的发码频率(防止滥发)
Redis 示例:
const redis = require('redis');
const client = redis.createClient();
async function storeOtp(email, code, ttlSeconds = 300) {
const key = `otp:${email}`;
await client.set(key, code, { EX: ttlSeconds });
}
async function verifyOtp(email, inputCode) {
const key = `otp:${email}`;
const stored = await client.get(key);
if (!stored) return { ok: false, reason: 'expired' };
// 常数时间比较
const match = crypto.timingSafeEqual(
Buffer.from(stored), Buffer.from(inputCode)
);
if (!match) return { ok: false, reason: 'wrong_code' };
await client.del(key); // 用完即删
return { ok: true };
}
发信频率限制
同一邮箱地址 60 秒内只允许发一次,防止被用来骚扰他人信箱或消耗发信额度:
async function canSendOtp(email) {
const lockKey = `otp_lock:${email}`;
const set = await client.set(lockKey, '1', { NX: true, EX: 60 });
return set !== null; // null 表示 key 已存在,即冷却中
}