The email that sat in queue for 12 hours: an Odoo cron debugging story

Two days ago, I wrote about integrating Odoo with Unosend's HTTP API after SMTP auth kept failing. The module worked, the tests passed, and I deployed it. Done, right?

Not quite. The next mailing sat in in_queue for over 12 hours without sending a single email.

What happened

We set up a mass mailing campaign in Odoo for PLOC — the Cuban political party whose Odoo instance we manage. The campaign targeted journalists and media contacts. We clicked Send, Odoo moved the mailing to in_queue, and then nothing.

Twelve hours later, the mailing was still sitting there. The recipients hadn't received anything.

Finding the problem

I logged into the Odoo instance and checked the mailing state:

ID=1 | State=in_queue | Sent=0 | Name=#Madrid acoge una Propuesta...

Still in_queue. Not sending, not done — just stuck.

First thing I checked: the Unosend integration. Was the API key still valid? Were there errors in the logs?

grep unosend /var/log/odoo/... → nothing

No Unosend calls at all. The module never got triggered because Odoo never reached the "send email" step. The mailing was waiting to be processed first.

In Odoo, mass mailings go through a two-step pipeline:

  1. Process queue cron — takes a mailing from in_queue, generates individual mail.mail records for each recipient, and transitions the mailing to sending
  2. Email queue manager cron — picks up mail.mail records with state outgoing and calls ir.mail_server.send_email(), which is where Unosend intercepts

The mailing was stuck at step 1. The cron that moves mailings out of in_queue hadn't run.

The cron configuration

I checked the scheduled actions:

Mail Marketing: Process queue | 1 days | Next=2026-05-13 00:42:58
Mail: Email Queue Manager        | 1 hours | Next=2026-05-12 15:42:18

The "Process queue" cron was set to run once per day. And its nextcall timestamp was already in the past — meaning it should have run but didn't, or ran and rescheduled itself 24 hours later.

Either way, a daily interval for a mailing queue is too slow. When someone clicks Send on a campaign, they expect it to go out within minutes, not sometime in the next 24 hours.

The fix

Two changes:

  1. Triggered the cron manually via ir.cron.method_direct_trigger — the mailing started processing immediately. 27 emails went out via Unosend, all HTTP 200.
  2. Changed the cron interval from 1 day to 5 minutes — now Odoo checks for pending mailings every 5 minutes, which means a campaign sent at any time will start processing within 5 minutes at most.

There was also a second mailing in the queue (a test send to 10 contacts). That one processed and delivered too.

What about the email queue manager?

The email queue manager (step 2) was already set to run every hour, which is reasonable for individual outgoing emails. After step 1 processes a mailing and creates the mail.mail records, step 2 picks them up within an hour.

But when you trigger step 1 manually, the individual emails enter the outgoing state immediately. And since the email queue manager runs every hour, they'll go out within 60 minutes. In our case, because we forced both crons, everything went out in about 90 seconds.

The logs told the story

Here's what the Odoo logs looked like once we triggered the cron:

INFO odoo.addons.mass_mailing: Mass-mailing mailing.mailing(1,) targets mailing.contact, blacklist: 0 emails
INFO odoo.addons.unosend_mail: Unosend: overriding FROM mireleslachy@gmail.com with verified domain noreply@partidoliberalortodoxocubano.com
INFO odoo.addons.unosend_mail: Unosend: sending email from noreply@partidoliberalortodoxocubano.com to info@opendemocracy.net subject=#Madrid acoge una Propuesta...
INFO odoo.addons.unosend_mail: Unosend: email delivered successfully (200)

Two things worth noting:

  1. FROM override works correctly — the mailing was created with a Gmail address as sender, but Unosend requires a verified domain, so the module replaces it with noreply@partidoliberalortodoxocubano.com. This is by design.
  2. Every email delivered — all 27 + 10 went through with 200 status codes.

Lessons

  1. Don't trust Odoo defaults for mailing crons. The "Process queue" default is once per day, which makes sense for Odoo's own hosted instances (where they throttle email volume) but not for self-hosted setups using external APIs. Change it to 5-15 minutes.
  2. Two pipelines, two crons. Mass mailings need the "Process queue" cron to move from in_queue to individual emails. Then the "Email Queue Manager" cron sends those individual emails. If either cron is slow or stuck, the whole thing grinds to a halt.
  3. The nextcall being in the past is a red flag. If a cron's next scheduled run was hours ago and it hasn't executed, something is wrong — either the cron runner is down, or the cron errored silently.
  4. Check the cron, not just the integration. I spent time verifying the Unosend API key, the config, the module code — all fine. The problem was upstream: Odoo never reached the send step because the queue processor hadn't run.
  5. Force-triggering crons is safe for diagnostics. ir.cron.method_direct_trigger runs the cron once immediately without changing its schedule. It's the fastest way to confirm whether the problem is the cron itself or something else.

What's next

The Unosend module is working, the cron is properly configured, and the next mailing will process within 5 minutes. I still need to:

  • Add attachment support to the module (currently skipped)
  • Set up open/click tracking from Unosend back into Odoo
  • Clean up the 5 old failed emails from before Unosend was configured

But the core problem — emails sitting in queue for hours — is solved. It wasn't a bug in the integration code. It was a cron interval that no one thought to change from the default.

This is a follow-up to Integrating Odoo with Unosend: when SMTP fails and the API saves the day. The module code is on GitLab.

← Back to Blog