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 tải 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 consuming the queue — idempotent per 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 } // already sent, skip
await mailer.send(msg.email)
ack(msg) // on failure: nack → the queue retries