You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Nodemailer与node-cron按序发送邮件的定时触发问题

问题:按时间间隔依次发送邮件,循环激活所有定时任务导致发送异常

需求

搭建网页,用户点击后按时间间隔依次收到不同内容的邮件,发送完最后一封后停止。

问题描述

尝试用邮件数组配合for循环实现,但循环会一次性激活所有邮件的定时任务,node-cron未按设置的cron表达式时间发送邮件。

用户代码

const express= require('express');
const app= express();
const nodemailer= require('nodemailer');
require('dotenv').config();
const cron= require('node-cron');

const PORT= process.env.PORT || 2525;

//Middleware
app.use(express.static('public'));
app.use(express.json());

app.get('/', (req, res)=> {
res.sendFile(__dirname + '/public/contactform.html')
})

app.post('/', (req, res)=>{
console.log(req.body);

const transporter= nodemailer.createTransport({
    host: 'smtp.gmail.com',
    secure: true,
    auth: {
        user: "creativoasueldo@gmail.com",
        pass: "hhixfdgyrlcablfy",
    }
})

const mailOptions= [
   {
from: "creativoasueldo@gmail.com",
to: req.body.email,
subject: 'Mensaje 1', 
text: "Hello", 
cronExpression: "*/6 * * * *"
},
{
    from: "creativoasueldo@gmail.com",
    to: req.body.email,
    subject: 'Mensaje 2', 
    text: "Hola",
    cronExpression: "5 */7 * * * *"
    }, 
    {
        from: "creativoasueldo@gmail.com",
        to: req.body.email,
        subject: 'Mensaje 3', 
        text: "Allo",
        cronExpression: "10 */8 * * * *"
        }, 
       {
            from: "creativoasueldo@gmail.com",
            to: req.body.email,
            subject: 'Mensaje 4', 
            text: "Buona Sera",
            cronExpression: "15 */9 * * * *"
            }, 
            {
                from: "creativoasueldo@gmail.com",
                to: req.body.email,
                subject: 'Mensaje 5', 
                text: "Konichiwa",
                cronExpression: "20 */10 * * * *"
                }]

for (let i = 0; i < mailOptions.length;  i++) {
    const {cronExpression, from, to, subject, text} =mailOptions[i];    
    console.log('Hola', to, i, mailOptions.length);                   
    cron.schedule(cronExpression, ()=> { 
        transporter.sendMail(mailOptions[i], (error, info)=>{
            if(error){
                console.log(error);
                res.send('error');
            }else {
                console.log('Email sent'); 
            }
        });
    } );
    console.log('Hola');
} 
})

app.listen(PORT, ()=>{
console.log(`Server running on ${PORT}`);
 })

解决方案

问题根源

  1. cron表达式格式错误:node-cron默认使用6位表达式(秒 分 时 日 月 周),你部分配置混用5位格式,且*/7这类间隔写法会导致任务频繁重复触发,不符合“依次间隔发送”的需求。
  2. 逻辑偏差:你需要的是发送完上一封后再触发下一封,但原代码一次性启动所有定时任务,所有任务同时开始计时,完全违背“依次”的逻辑。
  3. 闭包隐患:虽然用了let,但cron回调中引用mailOptions[i]仍可能出现变量绑定错误,导致发送内容异常。

修改后的实现思路

放弃node-cron,改用setTimeout结合递归调用的方式,确保上一封邮件发送完成(无论成功失败)后,再启动下一封的定时发送,完全贴合“依次间隔”的需求。

修改后的代码

const express = require('express');
const app = express();
const nodemailer = require('nodemailer');
require('dotenv').config();

const PORT = process.env.PORT || 2525;

// 中间件配置
app.use(express.static('public'));
app.use(express.json());

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/public/contactform.html')
})

app.post('/', (req, res) => {
  console.log(req.body);

  // 创建邮件传输器
  const transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    secure: true,
    auth: {
      user: "creativoasueldo@gmail.com",
      pass: "hhixfdgyrlcablfy",
    }
  })

  // 重新定义邮件队列,用延迟时间(毫秒)替代cron表达式
  const mailQueue = [
    {
      from: "creativoasueldo@gmail.com",
      to: req.body.email,
      subject: 'Mensaje 1',
      text: "Hello",
      delay: 0 // 第一封立即发送
    },
    {
      from: "creativoasueldo@gmail.com",
      to: req.body.email,
      subject: 'Mensaje 2',
      text: "Hola",
      delay: 7 * 60 * 1000 // 第一封发送后7分钟发送
    },
    {
      from: "creativoasueldo@gmail.com",
      to: req.body.email,
      subject: 'Mensaje 3',
      text: "Allo",
      delay: 8 * 60 * 1000 // 第二封发送后8分钟发送
    },
    {
      from: "creativoasueldo@gmail.com",
      to: req.body.email,
      subject: 'Mensaje 4',
      text: "Buona Sera",
      delay: 9 * 60 * 1000 // 第三封发送后9分钟发送
    },
    {
      from: "creativoasueldo@gmail.com",
      to: req.body.email,
      subject: 'Mensaje 5',
      text: "Konichiwa",
      delay: 10 * 60 * 1000 // 第四封发送后10分钟发送
    }
  ];

  // 递归发送函数:按顺序处理邮件队列
  const sendNextMail = (index) => {
    // 终止条件:所有邮件发送完成
    if (index >= mailQueue.length) {
      console.log('所有邮件发送完成');
      return;
    }

    const currentMail = mailQueue[index];
    // 延迟指定时间后发送当前邮件
    setTimeout(() => {
      transporter.sendMail(currentMail, (error, info) => {
        if (error) {
          console.log(`第${index+1}封邮件发送失败:`, error);
        } else {
          console.log(`第${index+1}封邮件已发送:`, info.response);
        }
        // 无论当前邮件是否发送成功,继续发送下一封
        sendNextMail(index + 1);
      });
    }, currentMail.delay);
  };

  // 启动邮件发送队列
  sendNextMail(0);

  // 立即响应前端,避免请求超时
  res.send('邮件发送任务已启动,将按间隔依次发送');
})

app.listen(PORT, () => {
  console.log(`Server running on ${PORT}`);
})

关键修改说明

  1. 替换cron为延迟时间:用delay(毫秒)定义每封邮件相对于上一封的发送间隔,逻辑更直观,完全匹配“依次发送”的需求。
  2. 递归发送逻辑:sendNextMail函数负责处理当前索引的邮件,发送完成后自动触发下一封,确保顺序执行。
  3. 及时响应前端:邮件发送是异步任务,无需等待所有邮件发送完成再响应,避免请求超时。
  4. 消除闭包问题:直接使用当前索引的邮件配置,不会出现循环中变量引用错误。

内容的提问来源于stack exchange,提问作者Jorge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 14:25:47