Node.js API中如何在if条件内使用setTimeout?邮件方案咨询
问题解决方案
一、在if条件内使用setTimeout延迟发送邮件
你当前代码里的return msg会导致循环只处理第一个符合条件的用户,需要先移除。然后在if块内用setTimeout包裹邮件发送逻辑,由于sendTransacEmail是异步函数,要在回调里用async/await处理:
修改后的if条件示例:
if([1,93].includes(dashboardJobCount[i].days)){ // 延迟1天(1000*60*60*24毫秒)发送邮件 setTimeout(async () => { // 再次校验用户是否仍未验证,避免重复发送 const [user]: any = await connection.query(`select verified, username from account where account_id=?`, [dashboardJobCount[i].account_id]); if(user[0]?.verified === false){ const msg = await tranEmailApi.sendTransacEmail({ ...firstMail, to: [{ email: user[0].username }] // 替换为用户实际邮箱 }); console.log('延迟邮件发送成功', msg); } }, 1000 * 60 * 60 * 24); // 1天的毫秒数 }
注意点:
- 不要在循环里直接依赖
i变量(虽然你用了let没问题,但用forEach循环会更稳妥) - 延迟发送前必须再次检查用户验证状态,防止用户已验证后仍发送邮件
- 把硬编码的
receivers替换为数据库中用户的真实邮箱
二、实现“未点击确认链接时自动发送多轮邮件”的思路
方案1:数据库存任务+定时任务(推荐)
Node.js的setTimeout在进程重启后会丢失任务,所以更可靠的方式是用数据库记录邮件发送计划,配合定时任务执行:
- 新增邮件任务表
创建email_tasks表,字段包括:
task_id(主键)account_id(关联用户账户)email_type(标记是第1/2/3封邮件)send_time(计划发送时间)status(待发送/已发送/已取消)
- 生成多轮任务
当用户注册(或符合初始发送条件)时,向表中插入多条任务:
- 第1天邮件:
send_time = 当前时间 + 1天 - 第2天邮件:
send_time = 当前时间 + 2天
- 定时任务执行
用node-schedule或node-cron库,定期(比如每分钟)查询待发送任务,执行邮件发送并更新状态:
import schedule from 'node-schedule'; // 每分钟检查一次待发送任务 schedule.scheduleJob('* * * * *', async () => { const connection = await createConnection(); try { const [tasks]: any = await connection.query(` select * from email_tasks where send_time <= now() and status='pending' `); for(const task of tasks){ // 检查用户是否仍未验证 const [user]: any = await connection.query(`select verified, username from account where account_id=?`, [task.account_id]); if(user[0]?.verified === false){ let emailConfig; switch(task.email_type){ case 'first': emailConfig = { ...firstMail, to: [{ email: user[0].username }] }; break; case 'second': emailConfig = { ...secondMail, to: [{ email: user[0].username }] }; break; } await tranEmailApi.sendTransacEmail(emailConfig); // 更新任务状态为已发送 await connection.query(`update email_tasks set status='sent' where task_id=?`, [task.task_id]); } else { // 用户已验证,取消任务 await connection.query(`update email_tasks set status='cancelled' where task_id=?`, [task.task_id]); } } } catch(err){ console.error('定时任务执行失败', err); } finally { await connection.end(); } });
- 用户验证时取消任务
当用户点击确认链接后,更新account表的verified为true,同时将该用户所有待发送的邮件任务状态改为已取消。
方案2:利用Sendinblue内置延迟发送
Sendinblue的Transactional API支持sendAt参数,可直接指定邮件发送时间,同时支持取消未发送的邮件:
// 发送延迟1天的邮件并记录messageId const scheduleFirstMail = async (accountId: string, userEmail: string) => { const sendAt = new Date(); sendAt.setDate(sendAt.getDate() + 1); // 延迟1天 const msg = await tranEmailApi.sendTransacEmail({ ...firstMail, to: [{ email: userEmail }], sendAt: sendAt.toISOString() // 转为ISO格式字符串 }); // 记录邮件ID,用于后续取消 await connection.query(`insert into email_tasks (account_id, message_id, email_type) values (?, ?, ?)`, [accountId, msg.messageId, 'first']); }; // 用户验证时取消未发送邮件 const cancelPendingEmails = async (accountId: string) => { const [tasks]: any = await connection.query(`select message_id from email_tasks where account_id=? and status='pending'`, [accountId]); for(const task of tasks){ await tranEmailApi.cancelScheduledEmail(task.messageId); await connection.query(`update email_tasks set status='cancelled' where message_id=?`, [task.messageId]); } };
此方案依赖Sendinblue服务,无需自己维护定时任务,但要注意平台对延迟时间的限制。
三、现有代码的其他优化建议
- 移除循环内的return:当前代码的
return msg会导致循环只处理第一个用户,改为记录日志或收集结果 - 用forEach替代for循环:避免异步场景下的变量问题,代码更简洁
- 使用数据库连接池:不要每次请求都创建新连接,提升性能
- 动态设置收件人:把硬编码的
receivers替换为数据库中的用户邮箱
内容的提问来源于stack exchange,提问作者jaga b
相关产品推荐
相关产品推荐

