[18.0] mail_composer_cc_bcc: IndexError in _prepare_outgoing_list when a recipient partner has no email — the whole email fails, not just that recipient

Aperta Adatta ai principianti
#252 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
76/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
python
Ambito
backend

Direzione di ricerca

Inizia da mail_composer_cc_bcc/models/mail_mail.py alla riga 76 e confronta la sua gestione dei destinatari con _prepare_outgoing_list in odoo/addons/mail/models/mail_mail.py del core. Riproduci il problema con un partner con un'email e uno senza. Poi aggiungi un test di regressione per il caso di destinatari misti. È completato quando il destinatario non valido viene saltato senza impedire ai destinatari validi di ricevere la mail.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

When a message is sent through the mail composer and any recipient partner has no email address, MailMail._prepare_outgoing_list() raises

IndexError: list index out of range

at mail_composer_cc_bcc/models/mail_mail.py:76 (branch 18.0, 18.0.1.0.3). The exception escapes core's per-recipient handling, so the whole mail.mail fails: none of the valid recipients receives anything and the mail is left in exception state. Without this module installed, core handles the same situation gracefully — it skips the invalid recipient and delivers to everybody else.

Root cause

Core builds one outgoing entry per recipient partner, and for a partner without an email address it deliberately produces a placeholder (odoo/addons/mail/models/mail_mail.py, _prepare_outgoing_list):

email_to_normalized = tools.mail.email_normalize_all(partner.email)
email_to = [
    tools.formataddr((partner.name or "", email or "False"))
    for email in email_to_normalized or [partner.email]
]

With partner.email falsy, email_normalize_all() returns [], the comprehension iterates over [False] and formats the literal string "False". formataddr then splits on the last @ and reassembles, which for "False" gives an empty local part:

>>> tools.formataddr(("Recipient Without Email", "False"))
'"Recipient Without Email" <@False>'
>>> extract_rfc2822_addresses('"Recipient Without Email" <@False>')
[]

So the entry has a non-empty email_to list — it survives the if m["email_to"] check — but no extractable address, and

rcpt_to = extract_rfc2822_addresses(m["email_to"][0])[0]   # <-- IndexError

raises. The @ is inserted by core's formataddr, not by this module.

Steps to reproduce

Clean database with only mail_composer_cc_bcc installed (it only depends on mail):

Partner = env["res.partner"]
p_ok = Partner.create({"name": "Recipient With Email", "email": "[email protected]"})
p_ko = Partner.create({"name": "Recipient Without Email"})  # no email

mail = env["mail.mail"].create({
    "subject": "test",
    "body_html": "<p>test</p>",
    "recipient_ids": [(6, 0, (p_ok + p_ko).ids)],
})
mail.with_context(is_from_composer=True)._prepare_outgoing_list()
# -> IndexError: list index out of range

Through the UI: add both partners as followers of any record with a chatter, open the full composer (this module only acts in composition mode comment) and send. Nobody receives the message, including Recipient With Email.

Current vs expected behaviour

Current (module installed) Expected (= core behaviour)
Recipient without email IndexError skipped, notification classified mail_email_invalid
Other recipients of the same mail.mail not delivered delivered
mail.mail state exception sent

Core's contract for this case is explicit: IrMailServer._prepare_smtp_to_list() returns an empty list, assert smtp_to_list, self.NO_VALID_RECIPIENT fires, and mail/models/mail_mail.py::_send() catches that AssertionError specifically in order to "ignore error to avoid blocking delivery to next recipients, if any" (its own comment). This module short-circuits that path before core ever gets there.

Proposed fix

Leave the entry untouched instead of indexing blindly. That is not merely defensive — it is what restores the core behaviour, because core then finds no SMTP recipient for that entry, raises NO_VALID_RECIPIENT and skips only that recipient:

-            rcpt_to = extract_rfc2822_addresses(m["email_to"][0])[0]
+            addresses = [
+                address
+                for address in extract_rfc2822_addresses(m["email_to"][0])
+                if tools.mail.email_normalize(address)
+            ]
+            if not addresses:
+                continue
+            rcpt_to = addresses[0]

The email_normalize() filter is a second belt: extract_rfc2822_addresses() alone already returns [] for the <@False> shape, but normalizing makes the criterion explicit and covers any other malformed value matching the extraction pattern.

Worth noting for whoever fixes it: do not give rcpt_to a fallback value instead. The entry would stay alive and get the whole audience written into its To header while its normalized recipient list stays empty — i.e. no SMTP envelope restriction.

Affected versions

  • 18.0 (18.0.1.0.3): line 76.
  • 19.0: the module is not on the 19.0 branch yet, but the open migrations carry the same construct — #163 has it twice (email_to and email_cc), and #104 / #189 once. I reproduced it on 19.0 running the code of the open migration, where core builds the identical <@False> placeholder, and pushed the fix above to the branch of #104 (camptocamp/mail#2), with a test.

Happy to open the equivalent PR for 18.0 if the approach is agreed.

Lingua principale
Python
Stelle
36
Fork
180
Merge medio
4g 2h
PR unite (30g)
14

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di OCA/mail

Tutte le issue di OCA/mail

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.