如何使用MongoDB Atlas Triggers向特定期限未登录用户发送自动化邮件
方案1:使用MongoDB Atlas 定时触发器(Scheduled Trigger)实现
你要找的无集合操作触发的触发器就是Atlas的定时触发器,和你之前看到的数据库事件触发器是两类不同的触发器,专门用于定时执行任务:
- 进入Atlas对应集群的「Triggers」面板,点击「Add Trigger」,触发器类型选择
Scheduled - 配置触发规则:可以直接选择按天固定时间执行,也可以用cron表达式自定义频率,例如下方cron表示每天北京时间上午9点执行:
0 1 * * *(注意Atlas默认用UTC时区,北京时间要减8小时换算) - 在触发器的函数编辑区写入核心逻辑,示例代码如下:
// 引入依赖,需要提前在触发器的Dependencies面板添加nodemailer和dayjs const nodemailer = require('nodemailer'); const dayjs = require('dayjs'); exports = async function() { // 1. 格式化当日日期,格式要和你streaks数组中存储的日期格式完全一致 const today = dayjs().format('YYYY-MM-DD'); // 2. 获取users集合实例 const usersCollection = context.services.get("你的集群服务名").db("你的库名").collection("users"); // 3. 查询所有streaks不包含当日日期的用户 const targetUsers = await usersCollection.find({ streaks: { $ne: today } }).toArray(); // 4. 初始化邮件发送客户端,邮箱密钥存在Atlas的Values&Secrets中,不要硬编码 const transporter = nodemailer.createTransport({ service: '你的邮箱服务商,比如QQ邮箱、Gmail', auth: { user: context.values.get("EMAIL_ACCOUNT"), pass: context.values.get("EMAIL_AUTH_CODE") } }); // 5. 批量发送邮件,建议用异步池控制并发,避免触发邮箱频率限制 for (const user of targetUsers) { await transporter.sendMail({ from: '你的发件人名称 <发件人邮箱>', to: user.email, subject: '提醒:今日还未登录哦', text: '登录账号保持你的连续登录天数吧~' }); } return { success: true, count: targetUsers.length }; };
- 额外配置:
- 在触发器的「Dependencies」面板添加
nodemailer和dayjs依赖 - 在Atlas项目的「Values & Secrets」面板添加邮箱账号、授权码等敏感配置,避免硬编码泄露
- 在触发器的「Dependencies」面板添加
方案2:自有Express服务实现(适合不想全依赖Atlas的场景)
如果你不想用Atlas触发器,直接在现有的Node.js服务中集成定时任务即可,和现有Mongoose逻辑完全兼容:
- 先安装依赖:
npm install node-cron nodemailer dayjs - 在Express服务中添加定时任务代码:
const cron = require('node-cron'); const nodemailer = require('nodemailer'); const dayjs = require('dayjs'); const User = require('./models/User'); // 你现有的Mongoose User模型 // 每天北京时间9点执行 cron.schedule('0 9 * * *', async () => { const today = dayjs().format('YYYY-MM-DD'); // 用现有Mongoose模型查询目标用户 const targetUsers = await User.find({ streaks: { $ne: today } }); // 初始化邮件客户端,密钥存在你的.env文件中 const transporter = nodemailer.createTransport({ service: process.env.EMAIL_SERVICE, auth: { user: process.env.EMAIL_ACCOUNT, pass: process.env.EMAIL_AUTH_CODE } }); // 发送邮件逻辑和方案1一致 for (const user of targetUsers) { await transporter.sendMail({ from: process.env.EMAIL_FROM, to: user.email, subject: '提醒:今日还未登录哦', text: '登录账号保持你的连续登录天数吧~' }); } }, { timezone: 'Asia/Shanghai' // 直接指定时区,不需要换算 });
- 生产环境优化建议:如果你的Express服务是多实例部署,建议加分布式锁(比如用Redis存当日任务执行标记),避免重复发送邮件。
注意事项
如果你的streaks数组存储的是ISODate类型而非格式化字符串,查询时需要匹配当日的时间范围,避免时分秒不匹配导致查询错误:
const startOfToday = dayjs().startOf('day').toDate(); const endOfToday = dayjs().endOf('day').toDate(); const targetUsers = await User.find({ streaks: { $not: { $elemMatch: { $gte: startOfToday, $lte: endOfToday } } } });
内容的提问来源于stack exchange,提问作者seshkebab
相关产品推荐
相关产品推荐

