diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index cae5291b7cb..b8c2795763f 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -410,7 +410,14 @@ class MailMail(models.Model): # specific behavior to customize the send email for notified partners for partner in self.recipient_ids: # check partner email content - email_to = [tools.formataddr((partner.name or '', partner.email or 'False'))] + emails_normalized = tools.email_normalize_all(partner.email) + if emails_normalized: + email_to = [ + tools.formataddr((partner.name or "", email or "False")) + for email in emails_normalized + ] + else: + email_to = [tools.formataddr((partner.name or "", partner.email or "False"))] email_list.append({ 'email_cc': [], 'email_to': email_to, diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 2be12eb3a25..62d0134fc7f 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -20,7 +20,7 @@ from odoo.addons.mail.models.mail_message import Message from odoo.addons.mail.models.mail_notification import MailNotification from odoo.addons.mail.models.res_users import Users from odoo.tests import common, new_test_user -from odoo.tools import formataddr, mute_logger, pycompat +from odoo.tools import email_normalize, formataddr, mute_logger, pycompat from odoo.tools.translate import code_translations mail_new_test_user = partial(new_test_user, context={'mail_create_nolog': True, @@ -585,7 +585,7 @@ class MockEmail(common.BaseCase, MockSmtplibCase): email_to_list = [] for email_to in recipients: if isinstance(email_to, self.env['res.partner'].__class__): - email_to_list.append(formataddr((email_to.name, email_to.email or 'False'))) + email_to_list.append(formataddr((email_to.name, email_normalize(email_to.email, strict=False) or email_to.email))) else: email_to_list.append(email_to) expected['email_to'] = email_to_list diff --git a/addons/test_mail/tests/test_mail_composer.py b/addons/test_mail/tests/test_mail_composer.py index 99b88dfb49f..5b229398b24 100644 --- a/addons/test_mail/tests/test_mail_composer.py +++ b/addons/test_mail/tests/test_mail_composer.py @@ -18,7 +18,7 @@ from odoo.fields import Datetime as FieldDatetime from odoo.exceptions import AccessError from odoo.tests import tagged from odoo.tests.common import users, Form -from odoo.tools import mute_logger, formataddr +from odoo.tools import email_normalize, mute_logger, formataddr @tagged('mail_composer') @@ -1814,6 +1814,10 @@ class TestComposerResultsComment(TestMailComposer, CronMixinCase): self.assertMailMail( self.partner_1 + self.partner_2 + mailed_new_partners, 'sent', author=self.partner_employee, + email_to_recipients=[ + [self.partner_1.email_formatted], + [f'"{self.partner_2.name}" ', f'"{self.partner_2.name}" '], + ] + [[email] for email in mailed_new_partners.mapped('email_formatted')], email_values={ 'body_content': f'TemplateBody {self.test_record.name}', 'email_from': formataddr((self.user_employee.name, 'email.from.1@test.example.com,email.from.2@test.example.com')), @@ -2185,7 +2189,7 @@ class TestComposerResultsMass(TestMailComposer): # to ease translations checks. sent_mail = self._find_sent_email( self.partner_employee_2.email_formatted, - [formataddr((record.customer_id.name, record.customer_id.email))] + [formataddr((record.customer_id.name, email_normalize(record.customer_id.email, strict=False)))] ) debug_info = '' if not sent_mail: @@ -2473,6 +2477,10 @@ class TestComposerResultsMass(TestMailComposer): self.partner_1 + self.partner_2 + mailed_new_partners, 'sent', author=self.partner_employee, + email_to_recipients=[ + [self.partner_1.email_formatted], + [f'"{self.partner_2.name}" ', f'"{self.partner_2.name}" '], + ] + [[email] for email in mailed_new_partners.mapped('email_formatted')], email_values={ 'body_content': f'TemplateBody {record.name}', # currently holding multi-email 'email_from' diff --git a/addons/test_mail/tests/test_mail_mail.py b/addons/test_mail/tests/test_mail_mail.py index 433a9e179b8..9636dd68a2e 100644 --- a/addons/test_mail/tests/test_mail_mail.py +++ b/addons/test_mail/tests/test_mail_mail.py @@ -753,9 +753,9 @@ class TestMailMailServer(MailCommon): sorted(sorted(_mail['email_to']) for _mail in self._mails), sorted([sorted(['"Raoul, le Grand" ', '"Micheline, l\'immense" ']), [formataddr((self.user_employee.name, self.user_employee.email_normalized))], - [formataddr(("Tony Customer", '"Formatted Emails" '))] + [formataddr(("Tony Customer", 'tony.customer@test.example.com'))] ]), - 'Mail (FIXME): double encapsulation of emails ("Tony" <"Formatted" >)' + 'Mail: formatting issues should have been removed as much as possible' ) # CC are added to first email self.assertEqual( @@ -784,10 +784,12 @@ class TestMailMailServer(MailCommon): self.assertEqual( sorted(sorted(_mail['email_to']) for _mail in self._mails), sorted([sorted(['test.email.1@test.example.com', 'test.email.2@test.example.com']), - [formataddr((self.user_employee.name, self.user_employee.email_normalized))], - [formataddr(("Tony Customer", 'tony.customer@test.example.com, norbert.customer@test.example.com'))] + [formataddr((self.user_employee.name, self.user_employee.email_normalized))], + sorted([formataddr(("Tony Customer", 'tony.customer@test.example.com')), + formataddr(("Tony Customer", 'norbert.customer@test.example.com'))]), ]), - 'Mail: currenty broken (multi email in a single address) but supported by some providers ("Tony" )' + 'Mail: formatting issues should have been removed as much as possible (multi emails in a single address are managed ' + 'like separate emails when sending with recipient_ids' ) # CC are added to first email self.assertEqual( @@ -812,10 +814,12 @@ class TestMailMailServer(MailCommon): self.assertEqual( sorted(sorted(_mail['email_to']) for _mail in self._mails), sorted([sorted(['test.email.1@test.example.com', 'test.email.2@test.example.com']), - [formataddr((self.user_employee.name, self.user_employee.email_normalized))], - [formataddr(("Tony Customer", 'tony.customer@test.example.com, "Norbert Customer" '))] + [formataddr((self.user_employee.name, self.user_employee.email_normalized))], + sorted([formataddr(("Tony Customer", 'tony.customer@test.example.com')), + formataddr(("Tony Customer", 'norbert.customer@test.example.com'))]), ]), - 'Mail: currently broken, double encapsulation with formatting ("Tony" >)' + 'Mail: formatting issues should have been removed as much as possible (multi emails in a single address are managed ' + 'like separate emails when sending with recipient_ids (and partner name is always used as name part)' ) # CC are added to first email self.assertEqual( diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py index 94116d2372c..13d77c20ddf 100644 --- a/addons/test_mail/tests/test_message_post.py +++ b/addons/test_mail/tests/test_message_post.py @@ -861,10 +861,11 @@ class TestMessagePost(TestMessagePostCommon, CronMixinCase): False, '', ' ', # falsy ] expected_tos = [ - # Currently semi-broken: sending to an incorrect email "Name" - [f'"{self.partner_1.name}" ',], - # Currently broken: sending to an incorrect email "Name" <"Name"
> - [f'"{self.partner_1.name}" <"Valid Lelitre" >',], + # Sends multi-emails + [f'"{self.partner_1.name}" ', + f'"{self.partner_1.name}" ',], + # Avoid double encapsulation + [f'"{self.partner_1.name}" ',], # sent "normally": formats email based on wrong / falsy email [f'"{self.partner_1.name}" <@wrong>',], [f'"{self.partner_1.name}" <@False>',], diff --git a/addons/test_mass_mailing/tests/test_mailing.py b/addons/test_mass_mailing/tests/test_mailing.py index 9ffb7491d7d..318a13bd56a 100644 --- a/addons/test_mass_mailing/tests/test_mailing.py +++ b/addons/test_mass_mailing/tests/test_mailing.py @@ -237,8 +237,8 @@ class TestMassMailing(TestMassMailCommon): 'partner': customer_mult, 'trace_status': 'cancel'}, {'email': '"Formatted Customer" ', - # double encapsulation, not good - 'email_to_recipients': [[f'"{customer_fmt.name}" <"Formatted Customer" >']], + # mail to avoids double encapsulation + 'email_to_recipients': [[f'"{customer_fmt.name}" ']], 'failure_type': False, 'partner': customer_fmt, 'trace_status': 'sent'}, @@ -249,17 +249,17 @@ class TestMassMailing(TestMassMailCommon): 'partner': customer_unic, 'trace_status': 'cancel'}, # email_re usage forbids mailing to unicode {'email': 'TEST.CUSTOMER.CASE@EXAMPLE.COM', - 'email_to_recipients': [[f'"{customer_case.name}" ']], + 'email_to_recipients': [[f'"{customer_case.name}" ']], 'failure_type': False, 'partner': customer_case, 'trace_status': 'sent'}, # lower cased {'email': 'test.customer.weird@example.com Weird Format', - 'email_to_recipients': [[f'"{customer_weird.name}" ']], + 'email_to_recipients': [[f'"{customer_weird.name}" ']], 'failure_type': False, 'partner': customer_weird, 'trace_status': 'sent'}, # concatenates everything after domain {'email': 'Weird Format2 test.customer.weird.2@example.com', - 'email_to_recipients': [[f'"{customer_weird_2.name}" ']], + 'email_to_recipients': [[f'"{customer_weird_2.name}" ']], 'failure_type': False, 'partner': customer_weird_2, 'trace_status': 'sent'},