node-cron 事务邮件 + Rovela(2026):进程内 Cron 与按窗口幂等键
用 Node.js node-cron 对接 Rovela POST /emails:服务端 sending_access,按调度窗口 Idempotency-Key,适合摘要与运维报告。
node-cron 负责调度 — Rovela 负责投递
node-cron 是 Node.js 上轻量的进程内 cron 调度器。调用 cron.schedule(expression, callback, options) 并保持进程常驻;表达式匹配时回调在同一进程执行。没有独立 Beat,也没有 Redis 任务存储(除非你自己加)。
适合摘要、运维报告等——正文依赖触发时实时状态。你的回调调用 POST https://api.rovela.dev/emails 时,Rovela 负责排队、DKIM 与投递。node-cron 管节奏;回调管聚合、sending_access 与按窗口 Idempotency-Key。
本指南串联 node-cron → callback → Rovela:
- 声明
cron.schedule("0 9 * * *", …, { timezone: "UTC" }) - 密钥仅放服务端环境
- 回调内按窗口聚合
- 窗口级幂等键发送
- 在重试与误双调度下避免重复发信
Rovela 不会创建 node-cron 计划或保活你的 Node 进程。
与下列文章角度不同:
- Quartz 调度事务邮件 — Java Quartz
- NestJS Schedule Cron 邮件 — NestJS @Cron
- Laravel Task Scheduler 邮件 — Laravel Schedule::
- APScheduler Cron 邮件 — Python APScheduler
- Spring @Scheduled 邮件 — Spring @Scheduled
- Resque-scheduler 邮件 — Ruby Resque-scheduler
- RQ Scheduler 邮件 — Python RQ Scheduler
- delayed_job 周期邮件 — Ruby delayed_job
- Solid Queue 周期邮件 — Rails Solid Queue
- Hangfire Recurring 事务邮件 — .NET Hangfire
- Sidekiq-Cron 事务邮件 — Sidekiq-Cron
- Celery Beat 事务邮件 — Celery Beat
- Agenda.js 定时任务邮件 — agenda
- BullMQ 可重复任务邮件 — BullMQ
- Temporal Schedule 邮件 — Temporal
- Trigger.dev Cron 邮件 — Trigger.dev
- Inngest Cron 事务邮件 — Inngest
- Kubernetes CronJob 邮件 — K8s
- Vercel Cron 摘要邮件 — Vercel
- 定时发送邮件 — scheduled_at
也不同于 BullMQ / Agenda(持久队列)与 NestJS @Cron——本文只讲 纯 Node + node-cron。
node-cron 调度什么,Rovela 发送什么
| 关注点 | 归属 |
|---|---|
| Cron、时区、任务引用 | cron.schedule |
| 进程常驻 | PM2 / systemd / Docker |
| 抛错重试 | 你的代码 |
ROVELA_API_KEY | 进程环境 |
| 查库 / 渲染 | 你的回调 |
POST /emails、DKIM | Rovela |
Idempotency-Key | 你的回调 |
约束
- 进程必须常驻;多副本会各自调度——邮件 cron 应落在单一 worker 或加分布式锁。
- 常见五段 Unix cron;时区用
timezone选项(IANA)。 - 进程宕机时默认不补跑 misfire。
sending_access仅服务端;优先 HTTPSPOST /emails。
何时用
要小依赖、长驻 Node worker、触发时新鲜指标,且能保证单一调度进程时用 node-cron。正文已知用 scheduled_at。需要持久队列用 BullMQ / Agenda;Nest 应用内用 @Cron。
注册与发送
import cron from "node-cron";
cron.schedule("0 9 * * *", async () => { await sendDigest(); }, { timezone: "UTC", name: "daily-digest-email" });
回调内:idempotencyKey = node-cron-daily-digest:${day},fetch("https://api.rovela.dev/emails", { headers: { Authorization: Bearer …, "Idempotency-Key": … }})。切勿记录 API Key。完整示例见英文版。
窗口级幂等键
推荐:node-cron-daily-digest:{yyyy-mm-dd}、node-cron-ops-pulse:{yyyy-mm-dd}T{HH}-{MM}、node-cron-tokyo-ops:{yyyy-mm-dd}、node-cron-tenant-digest:{tenantId}:{yyyy-mm-dd}。
故障与清单
| 现象 | 处理 |
|---|---|
| 从不触发 | 保活进程 |
| 双发 | 单一 worker / 锁 |
| 时区错 | 显式 timezone |
| 401/400/429 | 修环境;同 key 重试 |
上线清单:域名已验证、sending_access 仅 worker、文档化 cron+时区、窗口幂等键、无密钥日志、失败告警。
FAQ
调度器能不写任务方法直接调 Rovela 吗?没有实质意义——调度器只唤醒你的代码。聚合与 sending_access 必须在你的方法里。Rovela 不托管你的调度器或 Worker。
任务里还要用 scheduled_at 吗?通常不必。调度器已在发送时刻触发且正文是实时的,直接 POST /emails。若现在受理、稍后再投且不想再唤醒进程,才用 scheduled_at(定时发送邮件)。
sending_access 能做什么?足够 POST /emails / batch 入队。读信、改期、取消需要 full_access 或 Dashboard session。Worker 保持 sending_access。
200 等于创始人已收到摘要?否——只表示 Rovela 已接受入队。投递状态看 GET /emails/:id、events 或 webhook。
为何既要调度器重试又要 Idempotency-Key?调度器在代码失败时重试任务;Rovela 的 key 在同一窗口内 HTTP 成功两次(或超时但已接受)时合并重复发送。两层都要。
上线 node-cron;让 Rovela 投递
用显式时区注册 cron.schedule,在回调里实时聚合并 POST /emails,把幂等键限定在调度窗口,密钥仅服务端。node-cron 按时唤醒;Rovela 投递。