如何在Node.js中实现固定日期前24小时自动发送邮件?
嘿,这个需求在Node.js里实现起来其实挺清晰的,尤其是你已经有持续运行的VPS环境,我来给你一步步拆解靠谱的实现方案,兼顾稳定性和灵活性~
核心思路
首先得明确我们要做的几件事:
- 从数据库读取目标日期,计算出提前24小时的触发时间
- 用可靠的定时任务工具调度邮件发送逻辑
- 保证VPS重启后任务不丢失,进程崩溃能自动恢复
工具选择
- 邮件发送:用
nodemailer,这是Node.js生态里最成熟的邮件库,支持各种SMTP服务(Gmail、SendGrid、阿里云邮箱都可以) - 定时任务:如果你的数据库是MongoDB,首推
agenda——它会把任务持久化到MongoDB里,服务器重启后能自动恢复任务;如果用其他数据库,可以用node-schedule,但需要自己做任务持久化 - 进程管理:用
pm2,保证你的Node.js进程在VPS上持续运行,崩溃自动重启
具体实现步骤
1. 配置邮件发送服务
先搞定邮件发送的基础功能,这里以SMTP为例:
const nodemailer = require('nodemailer'); // 初始化邮件传输器(替换成你的邮箱服务商配置) const transporter = nodemailer.createTransport({ host: 'smtp.your-provider.com', port: 587, secure: false, // true对应465端口,false对应其他 auth: { user: 'your-email@example.com', pass: 'your-app-password-or-email-password' // 注意:Gmail需要用应用专用密码 } }); // 批量发送提醒邮件的函数 async function sendReminderEmails(recipients) { const baseMailOptions = { from: '"Your App" <your-email@example.com>', subject: 'Event Reminder', text: 'Hi! Just a heads up—your event is tomorrow.', html: '<p>Hi! Just a heads up—your event is <strong>tomorrow</strong>.</p>' }; // 逐个发送(避免批量收件人互相看到邮箱) for (const recipient of recipients) { try { await transporter.sendMail({ ...baseMailOptions, to: recipient.email, // 可以根据收件人定制内容,比如加上姓名 html: `<p>Hi ${recipient.name}! Just a heads up—your event is <strong>tomorrow</strong>.</p>` }); console.log(`✅ 邮件已发送到 ${recipient.email}`); } catch (err) { console.error(`❌ 发送邮件到 ${recipient.email} 失败:`, err); // 可选:添加重试逻辑,比如失败后5分钟再试一次 } } } module.exports = sendReminderEmails;
2. 用Agenda实现持久化定时任务(推荐)
Agenda适合需要持久化任务的场景,重启服务器不会丢失任务:
安装依赖
npm install agenda mongoose
代码实现
const Agenda = require('agenda'); const mongoose = require('mongoose'); const sendReminderEmails = require('./email-service'); // 连接MongoDB(Agenda依赖MongoDB存储任务) mongoose.connect('mongodb://localhost:27017/your-db-name') .then(() => console.log('✅ 连接MongoDB成功')) .catch(err => console.error('❌ MongoDB连接失败:', err)); // 初始化Agenda const agenda = new Agenda({ db: { address: 'mongodb://localhost:27017/your-db-name' } }); // 定义"发送提醒邮件"的任务类型 agenda.define('send event reminders', async (job) => { const { recipients } = job.attrs.data; await sendReminderEmails(recipients); }); // 启动Agenda并创建定时任务 (async function initScheduler() { await agenda.start(); // 从数据库读取目标事件(假设你有Event模型) const Event = require('./models/Event'); const targetEvent = await Event.findOne({ /* 你的查询条件,比如找未处理的事件 */ }); if (!targetEvent) { console.log('⚠️ 未找到需要设置提醒的事件'); return; } // 计算提前24小时的触发时间 const triggerTime = new Date(targetEvent.targetDate); triggerTime.setHours(triggerTime.getHours() - 24); // 检查触发时间是否在未来,避免创建已过期的任务 if (triggerTime <= new Date()) { console.log('⚠️ 触发时间已过,跳过任务创建'); return; } // 创建定时任务 await agenda.schedule(triggerTime, 'send event reminders', { recipients: targetEvent.recipients // 从DB取收件人列表 }); console.log(`✅ 已安排提醒任务,将在 ${triggerTime} 执行`); })();
3. 用Node-Schedule实现轻量定时任务
如果不用MongoDB,用node-schedule也可以,但要自己处理任务持久化:
安装依赖
npm install node-schedule
代码实现
const schedule = require('node-schedule'); const sendReminderEmails = require('./email-service'); const Event = require('./models/Event'); async function setupReminderJob() { const targetEvent = await Event.findOne({ /* 查询条件 */ }); if (!targetEvent) { console.log('⚠️ 未找到目标事件'); return; } const triggerTime = new Date(targetEvent.targetDate); triggerTime.setHours(triggerTime.getHours() - 24); if (triggerTime <= new Date()) { console.log('⚠️ 触发时间已过期'); return; } // 创建定时任务 const job = schedule.scheduleJob(triggerTime, async () => { await sendReminderEmails(targetEvent.recipients); console.log('✅ 提醒邮件发送完成'); }); console.log(`✅ 已创建定时任务,ID: ${job.name},执行时间: ${triggerTime}`); // 可选:把任务信息存到数据库,服务器重启后重新加载 // await JobLog.create({ jobName: job.name, triggerTime, recipients: targetEvent.recipients }); } // 启动时执行一次,也可以定期执行(比如每天检查新事件) setupReminderJob();
VPS上的运行保障
用PM2管理你的Node.js进程,确保它持续运行:
- 安装PM2:
npm install -g pm2
- 启动应用:
pm2 start app.js --name email-reminder-scheduler
- 保存PM2配置,重启VPS后自动恢复进程:
pm2 save pm2 startup # 按照终端提示执行生成的命令
- 查看日志和进程状态:
pm2 logs email-reminder-scheduler # 查看实时日志 pm2 status # 查看进程状态
关键注意事项
- 时区问题:确保数据库里的日期和Node.js应用的时区一致,建议统一用UTC时间处理,避免计算错误。比如:
// 如果DB里的日期是UTC,直接转换 const triggerTime = new Date(Date.parse(targetEvent.targetDate) - 24 * 60 * 60 * 1000); - 任务重试:如果邮件发送失败,Agenda可以通过
job.retry()设置重试次数,或者在邮件函数里手动添加重试逻辑。 - 定期检查新事件:如果你的数据库会新增事件,建议每天定时执行一次查询,自动为新事件创建提醒任务(比如用Agenda的
every('0 0 * * *', 'check new events'))。
内容的提问来源于stack exchange,提问作者Surya Mahla




