Skip to content

[19.0][FIX] mail_tracking: do not swallow exceptions in send_email - #266

Open
skanndar wants to merge 1 commit into
OCA:19.0from
skanndar:19.0-fix-mail_tracking-reraise-send-error
Open

[19.0][FIX] mail_tracking: do not swallow exceptions in send_email#266
skanndar wants to merge 1 commit into
OCA:19.0from
skanndar:19.0-fix-mail_tracking-reraise-send-error

Conversation

@skanndar

Copy link
Copy Markdown

Fixes #253

The problem

MailMail._send() tells a successful delivery from a failed one by the exception raised by
send_email(), not by its return value. mail_tracking catches every exception and, when a
mail.tracking.email record exists, records the error on the tracking record and returns
normally, so core believes the mail was delivered:

  • the recipient is appended to success_pids and _postprocess_sent_message() writes
    notification_status = 'sent' on a notification for a mail that never left — an email that
    did not arrive is displayed to users as delivered, with no trace anywhere
    ;
  • core's except AssertionError never runs, so a NO_VALID_RECIPIENT is never classified as
    mail_email_invalid;
  • res stays falsy, so the mail keeps the placeholder failure reason core sets before the
    sending loop ("Error without exception. Probably due to sending an email without computed
    recipients."
    ) with failure_type = 'unknown', even when the other recipients were delivered.

A tracking record is created for every outgoing email
(mail_tracking/models/mail_mail.py::_prepare_outgoing_list), so this is not an edge case: with
mail_tracking installed, core's whole failure-handling path is disabled for the database.

Not a duplicate of #215 / #212

#215 added else: raise, i.e. it re-raises only when there is no tracking record — the
branch that is effectively dead in practice. The case reported in #253 is the other one and is
still live on 19.0 (checked at ee6a0e79) and on 18.0.

The change

Re-raise unconditionally. The error is still recorded on the tracking record by the
smtp_error() call just above, so nothing is lost; only core's contract is restored.

Tests

Two regression tests build the mail.message + mail.mail + mail.notification trio core uses
to decide whether a recipient was reached:

  • test_smtp_error_is_not_reported_as_sent — fails without the patch with
    AssertionError: 'sent' == 'sent' (the notification of a mail that never left is marked as
    delivered) and, further down, mail.failure_reason is the placeholder instead of the real one;
  • test_smtp_error_still_tracks_the_error — checks the tracking record is still annotated and
    that core can now classify the failure (failure_type == 'mail_email_invalid'), which fails
    without the patch with AssertionError: 'mail_email_invalid' != 'unknown'.

Module suite on odoo:19 (19.0-20260817), fresh database: 46 tests / 0 failures before,
48 tests / 0 failures after.

⚠️ Note for adopters (worth a line in the changelog)

This is a behaviour change with a visible consequence: delivery failures that used to be silent
now surface as exception states
on mail.mail and mail.notification. On a production
database with this patch, a backlog of previously hidden failures became visible at once. That is
the correct outcome — the tracking records had been in error state all along, the state was
simply never propagated — but it will look like a regression to anyone upgrading without warning.

`MailMail._send()` tells a successful delivery from a failed one by the
exception raised by `send_email()`, not by its return value. When a
`mail.tracking.email` record exists -- which is the case for every outgoing
HTML mail, since `mail_tracking` creates one in `_prepare_outgoing_list()` --
the override recorded the error on the tracking record and returned normally,
so core believed the mail had been delivered:

* the recipient was appended to `success_pids` and
  `_postprocess_sent_message()` wrote `notification_status = 'sent'` on a
  notification for a mail that never left;
* core's `except AssertionError` never ran, so a `NO_VALID_RECIPIENT` was
  never classified as `mail_email_invalid`;
* `res` stayed falsy, so the mail kept the placeholder failure reason set
  before the sending loop ("Error without exception. Probably due to sending
  an email without computed recipients.") with `failure_type = 'unknown'`,
  even when the other recipients had been delivered.

Re-raise unconditionally. The error is still recorded on the tracking record
by the `smtp_error()` call just above, so nothing is lost; only core's
contract is restored. The previous `else: raise` (added in OCA#212 / OCA#215) is
kept as behaviour for the case with no tracking record.

Note for adopters: delivery failures that used to be silent now surface as
`exception` states on `mail.mail` and `mail.notification`.

Fixes OCA#253
@pedrobaeza pedrobaeza added this to the 19.0 milestone Sep 10, 2026

@pedrobaeza pedrobaeza left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests generated by AI are synthetic and are not bringing real flows, so better to remove them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants