← 返回博客
发邮件验证码OTPSMTP开发者

用邮件发验证码:API 接入完整示例

用 SMTP 或事务邮件 API 发邮件验证码的完整实现,包括密码学安全的随机数生成、Redis 存储校验、速率限制代码示例。

更新于 2026年10月12日
用邮件发验证码:API 接入完整示例

用邮件发验证码(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),关联用户标识和过期时间。校验时做以下检查:

  1. 验证码存在且未过期(建议 5–10 分钟)
  2. 验证码匹配(用常数时间比较,防止时序攻击)
  3. 使用后立即删除(一次性)
  4. 限制同一邮箱的发码频率(防止滥发)

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 已存在,即冷却中
}