BullMQ¶
适用于 Node.js 18+ 上 BullMQ 后台邮件投递的集成指南(本示例使用 Express)。消息通过 Nodemailer 在 Worker 中经 Mailexam SMTP 发送——适合通知与 CI,且不阻塞 HTTP 请求。
Web 层入队任务;BullMQ 异步发送(类似 Python 的 Celery 或 Ruby 的 Sidekiq)。
前置条件¶
从项目的欢迎邮件(或控制台)中复制:
YOUR_LOGIN— SMTP 登录名(例如xxxxx);YOUR_PASSWORD— SMTP 密码(与登录名成对的唯一凭据);- 主机 —
YOUR_LOGIN.mailexam.cn(与登录名一致)。
1. 依赖¶
package.json 最小依赖:
{
"dependencies": {
"bullmq": "^5.34.0",
"dotenv": "^16.0.0",
"express": "^4.21.0",
"ioredis": "^5.4.0",
"nodemailer": "^6.9.0"
},
"scripts": {
"start": "node server.js",
"worker": "node worker.js"
}
}
本地运行 Redis(Docker):
2. 环境变量¶
项目根目录的 .env:
REDIS_URL=redis://localhost:6379/0
MAILEXAM_LOGIN=YOUR_LOGIN
MAILEXAM_PASSWORD=YOUR_PASSWORD
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test
SMTP 主机:YOUR_LOGIN.mailexam.cn(GitHub 参考实现 中域名为 mailexam.io,请按欢迎邮件填写)。
3. Nodemailer 传输¶
与 Express 相同 — 项目根目录的 mail.js:
// mail.js
const nodemailer = require('nodemailer');
function createTransport() {
const login = process.env.MAILEXAM_LOGIN;
const port = Number(process.env.MAILEXAM_PORT || 587);
return nodemailer.createTransport({
host: `${login}.mailexam.cn`,
port,
secure: port === 465,
auth: {
user: login,
pass: process.env.MAILEXAM_PASSWORD,
},
});
}
async function sendTest({ to, subject, text }) {
const transport = createTransport();
await transport.sendMail({
from: process.env.MAIL_FROM || 'noreply@example.test',
to: to || 'user@example.test',
subject: subject || 'BullMQ + Mailexam',
text: text || 'Mailexam test from BullMQ',
});
}
module.exports = { sendTest };
4. BullMQ 队列与 Worker¶
queue.js:
require('dotenv').config();
const { Queue } = require('bullmq');
const IORedis = require('ioredis');
const connection = new IORedis(process.env.REDIS_URL || 'redis://localhost:6379/0', {
maxRetriesPerRequest: null,
});
const mailQueue = new Queue('mail', { connection });
module.exports = { mailQueue, connection };
worker.js:
require('dotenv').config();
const { Worker } = require('bullmq');
const IORedis = require('ioredis');
const { sendTest } = require('./mail');
const connection = new IORedis(process.env.REDIS_URL || 'redis://localhost:6379/0', {
maxRetriesPerRequest: null,
});
new Worker(
'mail',
async (job) => {
const { to, subject, text } = job.data;
await sendTest({ to, subject, text });
return { status: 'ok', to: to || 'user@example.test' };
},
{ connection },
);
5. HTTP 端点¶
// server.js
require('dotenv').config();
const express = require('express');
const { mailQueue } = require('./queue');
const app = express();
app.use(express.json());
app.post('/mail/test', async (req, res, next) => {
try {
const { to, subject, text, body } = req.body ?? {};
const job = await mailQueue.add('send-test', {
to,
subject,
text: text ?? body,
});
res.json({ status: 'ok', queued: true, jobId: job.id });
} catch (err) {
next(err);
}
});
const host = process.env.HTTP_HOST || '127.0.0.1';
const port = Number(process.env.HTTP_PORT || 3000);
app.listen(port, host);
启动 Redis、Worker 与服务器:
curl -X POST http://127.0.0.1:3000/mail/test \
-H 'Content-Type: application/json' \
-d '{"to":"user@example.test","subject":"Test","text":"Hello"}'
Worker 处理任务后,消息将出现在 Mailexam 控制台 → 您的项目 → 收件箱。
6. 本地开发与 CI¶
| 环境 | 建议 |
|---|---|
local |
Redis + Worker 进程 + HTTP 服务器 |
| CI | Redis 服务、REDIS_URL 与 Mailexam 密钥 |
.gitlab-ci.yml 示例:
variables:
REDIS_URL: redis://redis:6379/0
MAILEXAM_LOGIN: $MAILEXAM_LOGIN
MAILEXAM_PASSWORD: $MAILEXAM_PASSWORD
MAILEXAM_PORT: "587"
MAIL_FROM: "noreply@example.test"
集成测试发送后,通过 Mailexam API 验证投递。
7. 常见问题¶
任务已入队但未发送
- 确认 Worker 正在运行(
npm run worker)。 - 检查
REDIS_URL与 Redis 连通性。 - 查看 Worker 日志中的 SMTP 错误。
连接超时 / 认证失败
- 主机必须为
{login}.mailexam.cn,用户名为欢迎邮件中的同一登录名。
控制台中看不到邮件
- 查看同一 Mailexam 项目的收件箱。
- 等待 Worker 处理完成。
另请参阅¶
- 示例目录
- BullMQ 实现示例(GitHub)
- Express — 同一 SMTP 栈上的同步 Nodemailer
- Celery — Python 后台邮件
- Sidekiq — Ruby 后台邮件
- Mailexam API 文档
- BullMQ 文档