Bạn gọi template.send_mail(...), không có lỗi gì, nhưng khách bảo chưa nhận được thư. Không phải bug — mặc định send_mail không gửi ngay. Nó điền mẫu, tạo một bản ghi mail.mail ở trạng thái chờ, rồi để một cron gửi theo lô sau đó. Hiểu đúng đường đi này giúp bạn không ngồi debug SMTP khi vấn đề chỉ là "thư đang trong hàng đợi", và biết khi nào cần ép gửi ngay.

Ba cách gửi từ code

Odoo có vài lối gửi email tuỳ ngữ cảnh:

Ảnh chụp đoạn mã Python nền tối ba phần. Phần một template send_mail lấy template bằng env ref, gọi send_mail với the id không force_send với chú thích mặc định đưa vào hàng đợi cron mail.mail gửi sau ra state outgoing, và gọi send_mail với force_send True với chú thích gửi ngay cần SMTP lỗi ra state exception. Phần hai message_post trên record với chú thích model phải kế thừa mail.thread, gọi the message_post body Nhắc gia hạn thẻ subject Nhắc gia hạn partner_ids the partner_id ids với chú thích gửi email cho các partner này. Phần ba tự dựng mail.mail bằng env mail.mail create với subject body_html email_to rồi gọi mail send với chú thích gửi ngay bản này

Hình 1: Ba lối gửi. template.send_mail(res_id) — điền mẫu rồi tạo mail.mail; đây là cách phổ biến nhất khi đã có mail.template. record.message_post(...) — ghi vào khung trao đổi (chatter) của bản ghi và gửi email cho partner_ids; cần model kế thừa mail.thread (thẻ có). mail.mail.create(...).send() — tự dựng một thư thô rồi gửi, khi không muốn qua template.

Đo: send_mail đưa vào hàng đợi, chưa gửi

Gọi send_mail cho thẻ VIP-0001 và soi ngay bản ghi thư nó tạo ra:

Ảnh chụp phiên odoo shell nền tối, gán mid bằng t send_mail của bản ghi 1 với chú thích force_send False, browse mail.mail theo mid gán m, in m state và m subject trả về tuple outgoing và Thẻ VIP-0001 của bạn còn 150 điểm với chú thích nằm hàng đợi, in m recipient_ids mapped email trả về danh sách chứa an.nguyen@example.vn với chú thích người nhận qua recipient_ids, rồi gọi t send_mail với force_send True và browse state trả về exception với chú thích SMTP lỗi 535 chưa cấu hình, cuối cùng chú thích đo xong đã unlink các mail.mail test không phình bảng

Hình 2: send_mail(1) tạo một mail.mail ở trạng thái outgoing — tức là đã dựng xong nhưng chưa gửi, đang chờ cron "Gửi email" quét hàng đợi. Chủ đề đã điền đúng, và người nhận nằm ở recipient_ids (partner an.nguyen@example.vn) chứ không phải trường email_to thô — vì Odoo khớp email với một res.partner. Khi tôi ép force_send=True, nó thử gửi ngay và ra exception với lỗi SMTP 535 (máy chủ này chưa cấu hình gửi thư). Đo xong tôi xoá các thư test để không phình bảng.

Hàng đợi vs gửi ngay

Đường đi của một email quyết định bởi force_send:

Ảnh sơ đồ luồng gửi email trên nền kem tiêu đề Đường đi của email khi gửi từ code, gồm bốn hộp nối bằng mũi tên: hộp send_mail điền template, hộp mail.mail bản ghi thư nhãn outgoing, hộp cron Gửi email mail.mail queue, hộp SMTP nhãn sent hoặc exception. Bên dưới ghi force_send False mặc định dừng ở outgoing để cron gửi theo lô an toàn không chặn request, và force_send True nhảy thẳng tới SMTP ngay trong request nhanh nhưng SMTP treo hoặc lỗi sẽ ảnh hưởng người dùng chưa cấu hình SMTP thì ra exception

Hình 3: force_send=False (mặc định) dừng thư ở trạng thái outgoing; cron hệ thống "Gửi email" (một ir.cron có sẵn) quét hàng đợi và gửi theo lô. Ưu điểm: request của người dùng không bị chặn chờ SMTP, gửi hỏng thì cron thử lại. force_send=True gửi thẳng trong request — nhanh, nhưng nếu SMTP chậm/treo thì người dùng chờ theo, và lỗi nổ ngay tại đó (exception).

Quy tắc thực dụng: gần như luôn để force_send=False. Chỉ ép gửi ngay khi thực sự cần thư đến tức thì (ví dụ mã OTP, xác nhận đặt chỗ) và bạn chấp nhận rủi ro SMTP làm chậm thao tác. Việc gửi hàng loạt (nhắc hạn cả nghìn thẻ) thì phải qua hàng đợi, nếu không một lần gọi sẽ treo cả tiến trình.

Vài lưu ý

  • send_mail trả về id của mail.mail, không phải kết quả gửi. Muốn biết đã gửi chưa, đọc state của bản ghi đó (outgoing / sent / exception).
  • message_post khác gửi email thuần: nó ghi vào chatter của bản ghi (lưu vết trao đổi) và mới gửi email cho follower/partner_ids. Dùng khi muốn cả lưu vết và thông báo.
  • Chưa cấu hình SMTP thì mọi thứ dừng ở outgoing/exception — thư vẫn được dựng đúng (đọc body_html để kiểm), chỉ là không ra khỏi hệ thống. Đừng nhầm "code chạy xong" với "khách đã nhận".
  • Dọn mail.mail test. Khi thử nghiệm, mỗi lần gọi tạo một bản ghi; nhớ unlink để bảng không phình. auto_delete=True trên template giúp tự xoá sau khi gửi thành công.

Ba ý mang về

  1. send_mail(res_id) KHÔNG gửi ngay — nó điền mẫu, tạo mail.mail state outgoing chờ cron gửi theo lô. Trả về id thư; đọc state mới biết đã gửi.
  2. force_send=True gửi thẳng trong request (cần SMTP; lỗi → exception) — chỉ dùng khi cần tức thì, còn gửi hàng loạt phải qua hàng đợi.
  3. Người nhận đi qua recipient_ids (partner) chứ không phải email_to thô; message_post vừa ghi chatter vừa báo follower; nhớ dọn mail.mail test.

Email là cách nhắc ra ngoài. Nhưng để nhắc nội bộ — giao việc "gọi khách gia hạn" cho một nhân viên với hạn chót — Odoo có công cụ khác. Phần sau dựng lên hoạt động (activity) từ code: giao việc, đặt hạn, và nó hiện ở đâu trong giao diện.