Không gửi inline trong request. SMTP/nhà cung cấp có thể chậm hoặc lỗi tạm thời; nếu chờ, response bị treo và nếu gửi lỗi thì cả request signup fail dù user đã tạo xong.
Thiết kế: ghi user vào DB → đẩy một message vào queue (SQS/RabbitMQ/Redis) → trả response ngay. Một worker riêng consume queue và gọi provider gửi email.
Vì sao queue tốt hơn:
- Tách rời (decouple): web request nhanh, việc chậm chạy nền.
- Retry: gửi lỗi thì message quay lại queue thử lại (có backoff).
- Dead-letter queue (DLQ): message thử N lần vẫn lỗi thì chuyển sang DLQ để điều tra, không kẹt queue chính.
- Chịu được tải dồn (burst): đăng ký ồ ạt thì queue làm bộ đệm.
Lỗi hay gặp — at-least-once: queue thường giao ít nhất một lần, message có thể lặp → worker phải idempotent (vd lưu notification_id đã gửi) để không gửi email hai lần. Đừng giả định exactly-once.
// worker tiêu thụ queue — idempotent theo notification_id
const r = await db.query(
"INSERT INTO sent_notifications(notification_id) VALUES($1) ON CONFLICT DO NOTHING",
[msg.notificationId],
)
if (r.rowCount === 0) { ack(msg); return } // đã gửi rồi, bỏ qua
try {
await mailer.send(msg.email)
} catch (e) {
await db.query("DELETE FROM sent_notifications WHERE notification_id = $1", [msg.notificationId])
nack(msg) // gửi lỗi: xoá dấu rồi nack để queue gửi lại
return
}
ack(msg)