Cet article est actuellement disponible en anglais uniquement. Traduction à venir.
A user sends an Odoo email — quotation, invoice, follow-up. The success toast appears. The recipient does not receive it. You check Settings → Technical → Email → Emails and see the message stuck in Outgoing state forever:
State: Outgoing
Created: 2 days ago
Failure reason: (empty)
No error, no retry, no progress. This is one of the most common email problems on Odoo 17.0/18.0/19.0 — usually a misconfigured outgoing mail server, a stuck cron, or a bad SMTP credential combination.
Quick Fix
Manually trigger the send-queue cron and see what happens:
# In Odoo shell — odoo-bin shell -c odoo.conf
self.env['mail.mail'].process_email_queue()
If that succeeds and the mail moves to Sent, the problem was just that the cron was not firing. Restart the cron schedule:
cron = env.ref('mail.ir_cron_mail_scheduler_action')
cron.write({'active': True, 'nextcall': fields.Datetime.now()})
If process_email_queue raises an exception, that is the actual SMTP issue — read the traceback.
Why This Happens
Odoo's email pipeline:
- User triggers send. Odoo creates a
mail.mailrow withstate='outgoing'. - The
mail.mailcron (every 60 seconds) picks upoutgoingrows. - For each, Odoo opens an SMTP connection to the configured outgoing mail server.
- Sends. On success →
state='sent'. On failure →state='exception'withfailure_reasonpopulated.
The five common breaks:
- Cron disabled or stuck. No worker is processing the queue. Mails accumulate.
- Outgoing mail server misconfigured. SMTP host/port/auth wrong. Connection fails silently in some Odoo versions.
- TLS / certificate issue. SMTP server requires STARTTLS but Odoo connects plain, or vice versa.
- Sender address rejected. SMTP server requires the From address to match the authenticated identity.
[email protected]from a server authenticated as[email protected]gets rejected. - Rate limit hit. Gmail/SES throttle and Odoo does not back off properly. Mails sit in queue between throttle windows.
Step-by-Step Diagnosis
1. Check mail.mail queue depth.
SELECT state, count(*) FROM mail_mail GROUP BY state ORDER BY count DESC;
Many rows in outgoing = backlog. Many in exception = SMTP errors.
2. Inspect a stuck row.
SELECT id, subject, state, failure_type, failure_reason, message_id
FROM mail_mail
WHERE state = 'outgoing'
ORDER BY id DESC LIMIT 5;
failure_reason may be populated even on outgoing rows if a previous attempt failed.
3. Check the cron.
SELECT name, active, nextcall, lastcall, interval_number, interval_type
FROM ir_cron WHERE name LIKE '%Mail Queue Manager%' OR name LIKE '%mail.scheduler%';
active = false or nextcall far in the future means the cron is not running.
4. Test the outgoing server.
ir_mail_server = env['ir.mail_server'].search([], limit=1)
ir_mail_server.test_smtp_connection() # raises on failure
The error from test_smtp_connection is your SMTP problem.
5. Run process_email_queue manually:
self.env['mail.mail'].process_email_queue()
Watch the log. Each mail that fails will log a traceback identifying the SMTP error.
Permanent Fix
For disabled or stuck crons, re-activate:
cron = self.env.ref('mail.ir_cron_mail_scheduler_action')
cron.write({
'active': True,
'nextcall': fields.Datetime.now(),
'numbercall': -1, # unlimited runs
})
If the cron was getting stuck, see Cron job stuck running for the watchdog pattern.
For misconfigured outgoing servers, set them up correctly. For Gmail OAuth2 (Odoo 17.0+):
# Settings → Technical → Outgoing Mail Servers
# - Name: Gmail OAuth2
# - SMTP Server: smtp.gmail.com
# - SMTP Port: 465
# - Connection Encryption: SSL/TLS
# - Authentication: OAuth2
# - Username: [email protected]
# - Then click "Connect Your Gmail Account"
For SMTP with password (legacy):
# - SMTP Server: smtp.yourprovider.com
# - SMTP Port: 587
# - Connection Encryption: STARTTLS
# - Authentication: Password
# - Username: [email protected]
# - Password: <app-specific password, not your account password>
App-specific passwords are mandatory for Gmail, Outlook, and most modern providers — your real account password will not work.
For sender address mismatches, use the SMTP server's from_filter:
mail_server = env['ir.mail_server'].create({
'name': 'Sales Outgoing',
'smtp_host': 'smtp.example.com',
'smtp_user': '[email protected]',
'smtp_pass': 'secret',
'from_filter': '[email protected]', # only used for matching from addresses
})
Multiple outgoing servers can be configured; Odoo picks based on from_filter matching.
For rate-limited sends, throttle the cron:
@api.model
def process_email_queue(self, ids=None):
# Process at most 100 emails per cron run
if ids is None:
ids = self.search([('state', '=', 'outgoing')], limit=100).ids
return super().process_email_queue(ids=ids)
For very high volume, install OCA's mail_queue_throttle or use a dedicated transactional email service (SES, SendGrid) with its own throttling.
How to Prevent It
- Monitor
mail.mailqueue depth. A query that alerts whenoutgoingcount exceeds a threshold catches stalls before users notice. ECOSIRE production runs this on every customer. - Test outgoing servers in staging. Send a test email after every config change. Catches credential drift immediately.
- Watchdog cron. A 5-minute cron that reschedules
mail.schedulerif it has not run in 10 minutes self-heals stalls. - Use OAuth2 over passwords. App passwords get rotated, expired, leaked. OAuth2 is more durable and observable. Odoo 18.0+ has solid OAuth2 support for Gmail and Outlook.
- Send via transactional service for bulk. Marketing and bulk sends should not go through the regular SMTP server — use SES, SendGrid, or Postmark with API integration. Reduces queue contention and SMTP-specific failure modes.
- Clean failed mails periodically. A cron that auto-deletes
state=exceptionmails older than 30 days keeps the queue manageable and the UI usable.
Related Errors
- Failed email no error log — adjacent silent-failure bug.
- Mail aliases not receiving — inverse problem on the inbound side.
- Mass mailing batch timeout — sibling bulk-send failure.
- Cron job stuck running — common root cause.
Frequently Asked Questions
Why does Odoo not show the SMTP error to the sender?
Because mails are queued and sent asynchronously. The user's "send" UI completes before the actual SMTP attempt. Errors land on the mail.mail row's failure_reason field, which the user does not see by default. Add Settings → Email → Emails to the user's bookmarks for visibility.
How do I clear stuck mails without re-sending?
Mark them as cancelled:
UPDATE mail_mail SET state = 'cancel'
WHERE state = 'outgoing' AND create_date < now() - interval '7 days';
Cancelled mails are never re-attempted but stay in the table for audit.
Can I prioritize some emails over others?
Yes. The mail.mail priority field (1 = highest) is honored by process_email_queue. Set high priority on transactional emails (password resets, order confirmations) so they jump ahead of marketing.
My cron runs but mails still stuck. What do I check?
Check failure_reason on stuck rows. If it is empty but state is still outgoing, the cron is running but the mail is failing partway through. Run process_email_queue() manually and watch the log for the actual exception. Could be a TLS handshake hang, a slow SMTP server, or an Odoo timeout.
What is the right SMTP timeout for Odoo?
Default is 60 seconds in SMTPConnection. For most providers this is fine. If you talk to a slow corporate relay, set smtp_timeout higher (in ir.mail_server). Going below 30 seconds risks false negatives on legitimate slow servers.
Can I send the same email to many BCC recipients without a campaign?
Yes, but mail.mail does not auto-bcc — you set email_bcc explicitly. For more than a few hundred BCCs, switch to mass mailing or create individual mail.mail rows per recipient. Single-message bulk-BCC trips most providers' anti-spam.
Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transformez votre entreprise avec Odoo ERP
Implémentation, personnalisation et assistance expertes d'Odoo pour rationaliser vos opérations.
Articles connexes
Comment ajouter un bouton personnalisé à une vue de formulaire Odoo (2026)
Ajoutez des boutons d'action personnalisés aux vues de formulaire Odoo 19 : méthode d'action Python, héritage des vues, visibilité conditionnelle, boîtes de dialogue de confirmation. Testé en production.
Comment ajouter un champ personnalisé dans Odoo sans Studio (2026)
Ajoutez des champs personnalisés via le module personnalisé dans Odoo 19 : héritage de modèle, extension de vue, champs calculés, décisions magasin/non-magasin. Code d'abord, contrôle de version.
Comment ajouter un rapport personnalisé dans Odoo à l'aide d'une mise en page externe
Créez un rapport PDF de marque dans Odoo 19 à l'aide de web.external_layout : modèle QWeb, format papier, liaison d'action. Avec logo imprimé + remplacements de pied de page.