集成上线:接一次 Resend,Func 里直接发邮件和验证码
编辑器新增「集成」面板。接入 Resend 后在 Func 里用 ctx.email 发送事务邮件与验证码;密钥保存在服务端不进代码,验证码的有效期、一次性、常数时间比对、错误次数与限频都由平台保证。

发验证码这件事看着简单:生成六位数、存起来、比一下。但随机数够不够随机、有没有过期、能不能重复使用、比对会不会泄漏前缀、猜错几次该作废、同一个邮箱能不能被无限轰炸——六件事里错任何一件都是真实的安全问题,而且错了通常不会报错,只会静默地不安全。
所以这次没有只加一个「发邮件」的接口,而是把整件事做完:编辑器里接一次 Resend,Func 里一行代码发验证码,容易写错的部分由平台保证。
接入:在编辑器里连一次
编辑器后端 → 集成是这次新增的面板。填入 Resend 的 API Key 保存时,平台会真的调用一次 Resend 校验它——密钥无效当场就报错,而不是等到线上发信时才收到 403。
校验通过后,平台把 Resend 账号里已验证的发件域名拉回来让你选,而不是让你手填一个可能根本发不出去的地址。配完可以直接发一封测试邮件,把「配置到底对不对」变成当场能看见的事实。
密钥保存在服务端,不进入 Func 沙箱,也不会出现在代码、日志和返回值里。
使用:ctx.email 三个方法
发一封普通邮件:
ctx.email.send({ to: "someone@example.com", subject: "新的预约", html: "<p>…</p>" })
发验证码、校验验证码:
ctx.email.sendCode({ to: input.email, scene: "login" })
const ok = ctx.email.verifyCode({ to: input.email, scene: "login", code: input.code })
scene 按用途给验证码分命名空间,所以同一个邮箱的登录码和改密码的码互不干扰。
这些不用你写
6 位随机数字码,位数可配。
10 分钟过期,最长可配到 60 分钟。
一次性:校验通过立即作废,同一个码不能用第二次。
常数时间比对,不通过响应快慢泄漏前缀。
猜错 5 次作废,而且猜错重试不会延长有效期。
限频:同一邮箱同一 scene 每 10 分钟最多 5 封;单项目每天默认 500 封,这个上限可以在集成里按自己的服务商配额调高或调低。
所以不用再用 Math.random() 生成验证码、把码塞进缓存自己管过期、自己数错误次数——这些都已经做完了。
平台没封装的,你仍然能自己接
集成默认不把密钥交给 Func。如果要用 Resend 的其他接口(联系人、批量发送、附件),或者干脆换一家自己发,可以打开集成里的把密钥暴露给 Func 代码开关:密钥会额外注入 process.env.RESEND_API_KEY,之后用 fetch 想怎么调都行。开关默认关闭——逃生舱是一个明确的选择,不是默认泄露。
关于支付
顺便说清楚:支付没有托管集成,也没有 ctx.payment。各家支付平台的参数结构和回调语义差别太大,硬抽一层只会变成一个更难懂的转发器。支付仍然在 Func 里自己写,我们有一篇完整示例:使用 Func 接入支付宝电脑网站支付。
完整文档
接入步骤、三个方法的参数、错误与边界都写在这里:使用集成发送邮件与验证码。
