From 6b4862c746bed482ac3326dc873e690751e17b74 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Thu, 3 Jul 2025 16:25:01 +0200 Subject: [PATCH 01/11] [ADD] New module mail_company_aware_sender --- mail_company_aware_sender/README.rst | 101 ++++ mail_company_aware_sender/__init__.py | 3 + mail_company_aware_sender/__manifest__.py | 21 + mail_company_aware_sender/models/__init__.py | 5 + .../models/ir_mail_server.py | 18 + .../models/mail_thread.py | 30 ++ .../models/res_company.py | 26 + .../readme/CONFIGURE.rst | 9 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 11 + .../static/description/icon.png | Bin 0 -> 5896 bytes .../static/description/index.html | 450 ++++++++++++++++++ mail_company_aware_sender/tests/__init__.py | 3 + .../tests/test_company_aware_sender.py | 94 ++++ .../views/res_company_view.xml | 22 + 15 files changed, 796 insertions(+) create mode 100644 mail_company_aware_sender/README.rst create mode 100644 mail_company_aware_sender/__init__.py create mode 100644 mail_company_aware_sender/__manifest__.py create mode 100644 mail_company_aware_sender/models/__init__.py create mode 100644 mail_company_aware_sender/models/ir_mail_server.py create mode 100644 mail_company_aware_sender/models/mail_thread.py create mode 100644 mail_company_aware_sender/models/res_company.py create mode 100644 mail_company_aware_sender/readme/CONFIGURE.rst create mode 100644 mail_company_aware_sender/readme/CONTRIBUTORS.rst create mode 100644 mail_company_aware_sender/readme/DESCRIPTION.rst create mode 100644 mail_company_aware_sender/static/description/icon.png create mode 100644 mail_company_aware_sender/static/description/index.html create mode 100644 mail_company_aware_sender/tests/__init__.py create mode 100644 mail_company_aware_sender/tests/test_company_aware_sender.py create mode 100644 mail_company_aware_sender/views/res_company_view.xml diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst new file mode 100644 index 0000000000..47000fbfa4 --- /dev/null +++ b/mail_company_aware_sender/README.rst @@ -0,0 +1,101 @@ +========================= +Mail Sender Company Aware +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:748c15d6a6d963cd97ffc441a6006a9f9141143e8b8637a25f6fa2aaf7addd9d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github + :target: https://github.com/OCA/social/tree/16.0/mail_company_aware_sender + :alt: OCA/social +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_company_aware_sender + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to send out mails using a company aware email address. +Suppose we have a user Charles the Magnificent with email address +charlemagne@kingdom.fr. However this user works for two companies, one +for the kingdom of France, with email address info@kingdom.fr, and +one for the Roman Empire with email address chancellery@imperiumromanum.org. + +Now when sending out mail, we want to make clear from what active company the +mail is sent, but also want to keep the name of the active user in the +email from address, so when sending from the kingdom, the email from will +be charlemagne@kingdom.fr, but when sending from the empire, the email will +be charlemagne@imperiumromanum.org. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To send out mails with company aware from addresses, three things are needed: + * The company partner needs to have an email address; + * The company must be enabled for it's domain being used in from addresses; + * The company email domain must be defined on a domain whitelist in one + or more of the outgoing mail servers. + +On the company you can also set whether email from should include the name +of the sender or not. This will only affect emails that are modified to use +the company address. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Therp BV + +Contributors +~~~~~~~~~~~~ + +* `Therp BV `_: + + * Ronald Portier (NL66278) + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/social `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mail_company_aware_sender/__init__.py b/mail_company_aware_sender/__init__.py new file mode 100644 index 0000000000..83e553ac46 --- /dev/null +++ b/mail_company_aware_sender/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/mail_company_aware_sender/__manifest__.py b/mail_company_aware_sender/__manifest__.py new file mode 100644 index 0000000000..c239e6ccb9 --- /dev/null +++ b/mail_company_aware_sender/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Mail Sender Company Aware", + "summary": "Send emails with company specific mail domain", + "version": "16.0.1.0.0", + "category": "Social Network", + "website": "https://github.com/OCA/social", + "author": ("Therp BV, " "Odoo Community Association (OCA)"), + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": [ + "mail", + "mail_outbound_static", # Need the domain whitelist on outgoing server. + ], + "data": [ + "views/res_company_view.xml", + ], +} diff --git a/mail_company_aware_sender/models/__init__.py b/mail_company_aware_sender/models/__init__.py new file mode 100644 index 0000000000..bc94f9ac78 --- /dev/null +++ b/mail_company_aware_sender/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import ir_mail_server +from . import mail_thread +from . import res_company diff --git a/mail_company_aware_sender/models/ir_mail_server.py b/mail_company_aware_sender/models/ir_mail_server.py new file mode 100644 index 0000000000..0e35bc6f3e --- /dev/null +++ b/mail_company_aware_sender/models/ir_mail_server.py @@ -0,0 +1,18 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class IrMailServer(models.Model): + + _inherit = "ir.mail_server" + + @api.model + def _is_domain_whitelisted(self, domain): + """Check whether domain has been whitelisted for sending.""" + whitelist_servers = self.search([]).filtered("domain_whitelist") + for server in whitelist_servers: + if domain in self._get_domain_whitelist(server.domain_whitelist): + return True + return False diff --git a/mail_company_aware_sender/models/mail_thread.py b/mail_company_aware_sender/models/mail_thread.py new file mode 100644 index 0000000000..9d211492d0 --- /dev/null +++ b/mail_company_aware_sender/models/mail_thread.py @@ -0,0 +1,30 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import models +from odoo.tools import formataddr + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + def _message_compute_author(self, author_id, email_from, raise_on_email=True): + """Set email from using company email domain. + + We will NOT override an explicitly passed email_from. + + Check for current company to see whether we should try to override + the email_from domain. + """ + override_from = (not email_from) and self.env.company._override_email_domain() + author_id, email_from = super()._message_compute_author( + author_id, email_from, raise_on_email=raise_on_email + ) + if override_from and author_id: + author = self.env["res.partner"].browse(author_id) + before_at = author.email.split("@")[0] + after_at = self.env.company.email.split("@")[1] + email_from = f"{before_at}@{after_at}" + if self.env.company.format_email: + # formataddr wants a tuple with name (of False) and email. + email_from = formataddr((author.name, email_from)) + return author_id, email_from diff --git a/mail_company_aware_sender/models/res_company.py b/mail_company_aware_sender/models/res_company.py new file mode 100644 index 0000000000..84beaa2608 --- /dev/null +++ b/mail_company_aware_sender/models/res_company.py @@ -0,0 +1,26 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models +from odoo.tools import email_domain_extract + + +class ResCompany(models.Model): + _inherit = "res.company" + + use_email_domain = fields.Boolean( + help="Use domain part of company for sender email address", + ) + format_email = fields.Boolean( + default=True, # As this is what Odoo standard does. + help='Format email_from with name "John Smith" ' + " or use plain email address", + ) + + def _override_email_domain(self): + """Check whether company email domain can and should be used.""" + self.ensure_one() + if not (self.use_email_domain and self.email): + return False + email_domain = email_domain_extract(self.email) + return self.env["ir.mail_server"].sudo()._is_domain_whitelisted(email_domain) diff --git a/mail_company_aware_sender/readme/CONFIGURE.rst b/mail_company_aware_sender/readme/CONFIGURE.rst new file mode 100644 index 0000000000..b3216efbb3 --- /dev/null +++ b/mail_company_aware_sender/readme/CONFIGURE.rst @@ -0,0 +1,9 @@ +To send out mails with company aware from addresses, three things are needed: + * The company partner needs to have an email address; + * The company must be enabled for it's domain being used in from addresses; + * The company email domain must be defined on a domain whitelist in one + or more of the outgoing mail servers. + +On the company you can also set whether email from should include the name +of the sender or not. This will only affect emails that are modified to use +the company address. diff --git a/mail_company_aware_sender/readme/CONTRIBUTORS.rst b/mail_company_aware_sender/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..a5e152b71a --- /dev/null +++ b/mail_company_aware_sender/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Therp BV `_: + + * Ronald Portier (NL66278) diff --git a/mail_company_aware_sender/readme/DESCRIPTION.rst b/mail_company_aware_sender/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..ff6fcd36a2 --- /dev/null +++ b/mail_company_aware_sender/readme/DESCRIPTION.rst @@ -0,0 +1,11 @@ +This module allows to send out mails using a company aware email address. +Suppose we have a user Charles the Magnificent with email address +charlemagne@kingdom.fr. However this user works for two companies, one +for the kingdom of France, with email address info@kingdom.fr, and +one for the Roman Empire with email address chancellery@imperiumromanum.org. + +Now when sending out mail, we want to make clear from what active company the +mail is sent, but also want to keep the name of the active user in the +email from address, so when sending from the kingdom, the email from will +be charlemagne@kingdom.fr, but when sending from the empire, the email will +be charlemagne@imperiumromanum.org. diff --git a/mail_company_aware_sender/static/description/icon.png b/mail_company_aware_sender/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1af495578ff7ae76542a5b3b88f7087de9ed771 GIT binary patch literal 5896 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 + + + + + +Mail Sender Company Aware + + + +
+

Mail Sender Company Aware

+ + +

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

+

This module allows to send out mails using a company aware email address. +Suppose we have a user Charles the Magnificent with email address +charlemagne@kingdom.fr. However this user works for two companies, one +for the kingdom of France, with email address info@kingdom.fr, and +one for the Roman Empire with email address chancellery@imperiumromanum.org.

+

Now when sending out mail, we want to make clear from what active company the +mail is sent, but also want to keep the name of the active user in the +email from address, so when sending from the kingdom, the email from will +be charlemagne@kingdom.fr, but when sending from the empire, the email will +be charlemagne@imperiumromanum.org.

+

Table of contents

+ +
+

Configuration

+
+
To send out mails with company aware from addresses, three things are needed:
+
    +
  • The company partner needs to have an email address;
  • +
  • The company must be enabled for it’s domain being used in from addresses;
  • +
  • The company email domain must be defined on a domain whitelist in one +or more of the outgoing mail servers.
  • +
+
+
+

On the company you can also set whether email from should include the name +of the sender or not. This will only affect emails that are modified to use +the company address.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Therp BV
  • +
+
+
+

Contributors

+
    +
  • Therp BV:
      +
    • Ronald Portier (NL66278)
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/social project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/mail_company_aware_sender/tests/__init__.py b/mail_company_aware_sender/tests/__init__.py new file mode 100644 index 0000000000..2b47816235 --- /dev/null +++ b/mail_company_aware_sender/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_company_aware_sender diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py new file mode 100644 index 0000000000..e07995814c --- /dev/null +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -0,0 +1,94 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import Command +from odoo.tests import TransactionCase + + +class TestCompanyAwareSender(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Create two companies and a partner/user. + cls.Company = cls.env["res.company"] + cls.Partner = cls.env["res.partner"] + cls.User = cls.env["res.users"] + cls.IrMailServer = cls.env["ir.mail_server"] + cls.mail_server = cls.IrMailServer.create( + { + "name": "localhost", + "smtp_host": "localhost", + "domain_whitelist": "therp.nl", + } + ) + cls.company_kingdom = cls.Company.create( + { + "name": "The kingdom of France", + "email": "info@kingdom.fr", + } + ) + cls.company_imperium = cls.Company.create( + { + "name": "Imperium Romanum", + "email": "chancellery@imperiumromanum.org", + "use_email_domain": True, + "format_email": False, + } + ) + cls.partner_charles = cls.Partner.create( + { + "name": "Charles Le Magne", + "email": "charlemagne@therp.nl", + } + ) + cls.user_charles = cls.User.with_context(no_reset_password=True).create( + { + "partner_id": cls.partner_charles.id, + "login": "charlemagne", + "email": "charlemagne@therp.nl", + "company_id": cls.company_kingdom.id, + "company_ids": [ + Command.set([cls.company_kingdom.id, cls.company_imperium.id]), + ], + } + ) + + def test_nothing_changed(self): + # Check with default user and author (current user). + mail_thread = ( + self.env["mail.thread"] + .with_user(self.user_charles) + .with_company(self.company_kingdom) + ) + author_id, email_from = mail_thread._message_compute_author(None, None) + self.assertEqual(author_id, self.partner_charles.id) + self.assertEqual(email_from, '"Charles Le Magne" ') + author_id, email_from = mail_thread._message_compute_author( + None, '"Unknown Person" ' + ) + self.assertEqual(author_id, False) + self.assertEqual(email_from, '"Unknown Person" ') + + def test_company_overwrite(self): + # Check with default user and author (current user). + mail_thread = ( + self.env["mail.thread"] + .with_user(self.user_charles) + .with_company(self.company_imperium) + ) + # Should not work if domain not whitelisted. + author_id, email_from = mail_thread._message_compute_author(None, None) + self.assertEqual(author_id, self.partner_charles.id) + self.assertEqual(email_from, '"Charles Le Magne" ') + # Whitelist domain. + self.mail_server.write( + {"domain_whitelist": "therp.nl,kingdom.fr,imperiumromanum.org"} + ) + author_id, email_from = mail_thread._message_compute_author(None, None) + self.assertEqual(author_id, self.partner_charles.id) + self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + self.company_imperium.write({"format_email": True}) + author_id, email_from = mail_thread._message_compute_author(None, None) + self.assertEqual(author_id, self.partner_charles.id) + self.assertEqual( + email_from, '"Charles Le Magne" ' + ) diff --git a/mail_company_aware_sender/views/res_company_view.xml b/mail_company_aware_sender/views/res_company_view.xml new file mode 100644 index 0000000000..5f3e28f782 --- /dev/null +++ b/mail_company_aware_sender/views/res_company_view.xml @@ -0,0 +1,22 @@ + + + + + res.company.form - mail_sender_company_aware + res.company + + + + + + + + + + From f404e1fd1ab5cba53ff34a22195bb33bd315d36e Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Fri, 4 Jul 2025 16:02:42 +0200 Subject: [PATCH 02/11] [IMP] mail_company_aware_sender: provide opt-out on partner --- mail_company_aware_sender/README.rst | 11 +++++++- mail_company_aware_sender/__manifest__.py | 1 + mail_company_aware_sender/models/__init__.py | 2 ++ .../models/mail_thread.py | 12 ++------ .../models/res_partner.py | 28 +++++++++++++++++++ mail_company_aware_sender/models/res_users.py | 15 ++++++++++ .../readme/CONFIGURE.rst | 3 ++ .../readme/DESCRIPTION.rst | 6 ++++ .../static/description/index.html | 9 +++++- .../tests/test_company_aware_sender.py | 5 ++++ .../views/res_partner_view.xml | 18 ++++++++++++ 11 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 mail_company_aware_sender/models/res_partner.py create mode 100644 mail_company_aware_sender/models/res_users.py create mode 100644 mail_company_aware_sender/views/res_partner_view.xml diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst index 47000fbfa4..52aa6fc1be 100644 --- a/mail_company_aware_sender/README.rst +++ b/mail_company_aware_sender/README.rst @@ -7,7 +7,7 @@ Mail Sender Company Aware !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:748c15d6a6d963cd97ffc441a6006a9f9141143e8b8637a25f6fa2aaf7addd9d + !! source digest: sha256:7ee2897c3172b5b4d4927dbf7ec1e94e1c71bc5602f7bdf85b3ac0cae33481d0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -40,6 +40,12 @@ email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org. +This module can also be used to send company aware emails from templates. +For instance with this formula for the email_from field: +{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +In this example the company defined in the object will be used instead of the current +company. + **Table of contents** .. contents:: @@ -58,6 +64,9 @@ On the company you can also set whether email from should include the name of the sender or not. This will only affect emails that are modified to use the company address. +For particular users/partners there can be an opt-out for using company aware +emails. This can be configured right under the email field in the partner form. + Bug Tracker =========== diff --git a/mail_company_aware_sender/__manifest__.py b/mail_company_aware_sender/__manifest__.py index c239e6ccb9..4c48e843c4 100644 --- a/mail_company_aware_sender/__manifest__.py +++ b/mail_company_aware_sender/__manifest__.py @@ -17,5 +17,6 @@ ], "data": [ "views/res_company_view.xml", + "views/res_partner_view.xml", ], } diff --git a/mail_company_aware_sender/models/__init__.py b/mail_company_aware_sender/models/__init__.py index bc94f9ac78..732f504cd2 100644 --- a/mail_company_aware_sender/models/__init__.py +++ b/mail_company_aware_sender/models/__init__.py @@ -3,3 +3,5 @@ from . import ir_mail_server from . import mail_thread from . import res_company +from . import res_partner +from . import res_users diff --git a/mail_company_aware_sender/models/mail_thread.py b/mail_company_aware_sender/models/mail_thread.py index 9d211492d0..c3e12fa545 100644 --- a/mail_company_aware_sender/models/mail_thread.py +++ b/mail_company_aware_sender/models/mail_thread.py @@ -1,7 +1,6 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import models -from odoo.tools import formataddr class MailThread(models.AbstractModel): @@ -15,16 +14,11 @@ def _message_compute_author(self, author_id, email_from, raise_on_email=True): Check for current company to see whether we should try to override the email_from domain. """ - override_from = (not email_from) and self.env.company._override_email_domain() + email_passed = bool(email_from) author_id, email_from = super()._message_compute_author( author_id, email_from, raise_on_email=raise_on_email ) - if override_from and author_id: + if (not email_passed) and author_id: author = self.env["res.partner"].browse(author_id) - before_at = author.email.split("@")[0] - after_at = self.env.company.email.split("@")[1] - email_from = f"{before_at}@{after_at}" - if self.env.company.format_email: - # formataddr wants a tuple with name (of False) and email. - email_from = formataddr((author.name, email_from)) + email_from = author.company_aware_email(default_email=email_from) return author_id, email_from diff --git a/mail_company_aware_sender/models/res_partner.py b/mail_company_aware_sender/models/res_partner.py new file mode 100644 index 0000000000..ab32bbb85a --- /dev/null +++ b/mail_company_aware_sender/models/res_partner.py @@ -0,0 +1,28 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models +from odoo.tools import formataddr + + +class ResPartner(models.Model): + _inherit = "res.partner" + + fixed_email = fields.Boolean( + help="Email for partner will not be influenced by company", + ) + + def company_aware_email(self, company=None, default_email=None): + """Set email using company email domain if configured.""" + self.ensure_one() + result_email = default_email or self.email + if not self.fixed_email: + company = company or self.env.company + if company._override_email_domain(): + before_at = self.email.split("@")[0] + after_at = company.email.split("@")[1] + result_email = f"{before_at}@{after_at}" + if company.format_email: + # formataddr wants a tuple with name (or False) and email. + result_email = formataddr((self.name, result_email)) + return result_email diff --git a/mail_company_aware_sender/models/res_users.py b/mail_company_aware_sender/models/res_users.py new file mode 100644 index 0000000000..f4177dfbec --- /dev/null +++ b/mail_company_aware_sender/models/res_users.py @@ -0,0 +1,15 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class ResUsers(models.Model): + _inherit = "res.users" + + def company_aware_email(self, company=None, default_email=None): + """Set email using company email domain if configured.""" + self.ensure_one() + return self.partner_id.company_aware_email( + company=company, default_email=default_email + ) diff --git a/mail_company_aware_sender/readme/CONFIGURE.rst b/mail_company_aware_sender/readme/CONFIGURE.rst index b3216efbb3..dbd3243d0c 100644 --- a/mail_company_aware_sender/readme/CONFIGURE.rst +++ b/mail_company_aware_sender/readme/CONFIGURE.rst @@ -7,3 +7,6 @@ To send out mails with company aware from addresses, three things are needed: On the company you can also set whether email from should include the name of the sender or not. This will only affect emails that are modified to use the company address. + +For particular users/partners there can be an opt-out for using company aware +emails. This can be configured right under the email field in the partner form. diff --git a/mail_company_aware_sender/readme/DESCRIPTION.rst b/mail_company_aware_sender/readme/DESCRIPTION.rst index ff6fcd36a2..2ee1ecea21 100644 --- a/mail_company_aware_sender/readme/DESCRIPTION.rst +++ b/mail_company_aware_sender/readme/DESCRIPTION.rst @@ -9,3 +9,9 @@ mail is sent, but also want to keep the name of the active user in the email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org. + +This module can also be used to send company aware emails from templates. +For instance with this formula for the email_from field: +{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +In this example the company defined in the object will be used instead of the current +company. diff --git a/mail_company_aware_sender/static/description/index.html b/mail_company_aware_sender/static/description/index.html index 47059c51c5..578a11e9ce 100644 --- a/mail_company_aware_sender/static/description/index.html +++ b/mail_company_aware_sender/static/description/index.html @@ -367,7 +367,7 @@

Mail Sender Company Aware

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:748c15d6a6d963cd97ffc441a6006a9f9141143e8b8637a25f6fa2aaf7addd9d +!! source digest: sha256:7ee2897c3172b5b4d4927dbf7ec1e94e1c71bc5602f7bdf85b3ac0cae33481d0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module allows to send out mails using a company aware email address. @@ -380,6 +380,11 @@

Mail Sender Company Aware

email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org.

+

This module can also be used to send company aware emails from templates. +For instance with this formula for the email_from field: +{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +In this example the company defined in the object will be used instead of the current +company.

Table of contents

    @@ -408,6 +413,8 @@

    Configuration

    On the company you can also set whether email from should include the name of the sender or not. This will only affect emails that are modified to use the company address.

    +

    For particular users/partners there can be an opt-out for using company aware +emails. This can be configured right under the email field in the partner form.

Bug Tracker

diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py index e07995814c..fc93d4469e 100644 --- a/mail_company_aware_sender/tests/test_company_aware_sender.py +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -92,3 +92,8 @@ def test_company_overwrite(self): self.assertEqual( email_from, '"Charles Le Magne" ' ) + # Now opt out for the override. + self.partner_charles.write({"fixed_email": True}) + author_id, email_from = mail_thread._message_compute_author(None, None) + self.assertEqual(author_id, self.partner_charles.id) + self.assertEqual(email_from, '"Charles Le Magne" ') diff --git a/mail_company_aware_sender/views/res_partner_view.xml b/mail_company_aware_sender/views/res_partner_view.xml new file mode 100644 index 0000000000..5e51a4c776 --- /dev/null +++ b/mail_company_aware_sender/views/res_partner_view.xml @@ -0,0 +1,18 @@ + + + + + res.partner.form - mail_sender_company_aware + res.partner + + + + + + + + + From 6fe061c25475d52d93f18c7d222dd6a3d55672c7 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Mon, 7 Jul 2025 22:57:59 +0200 Subject: [PATCH 03/11] [IMP] ..company_aware..r: test connection company aware --- .../models/ir_mail_server.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/mail_company_aware_sender/models/ir_mail_server.py b/mail_company_aware_sender/models/ir_mail_server.py index 0e35bc6f3e..823f7460a8 100644 --- a/mail_company_aware_sender/models/ir_mail_server.py +++ b/mail_company_aware_sender/models/ir_mail_server.py @@ -1,7 +1,9 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import api, models +from odoo import _, api, models +from odoo.exceptions import ValidationError +from odoo.tools import email_domain_extract class IrMailServer(models.Model): @@ -16,3 +18,20 @@ def _is_domain_whitelisted(self, domain): if domain in self._get_domain_whitelist(server.domain_whitelist): return True return False + + def _get_test_email_addresses(self): + self.ensure_one() + if self.from_filter or not self.env.user.email: + return super()._get_test_email_addresses() + email_to = "noreply@odoo.com" + email_from = self.env.user.company_aware_email() + email_domain = email_domain_extract(email_from) + valid_domains = self._get_domain_whitelist(self.domain_whitelist) + if email_domain not in valid_domains: + raise ValidationError( + _( + "Domain %s not whitelisted on this server", + email_domain, + ) + ) + return email_from, email_to From 4e69aa53232d2143549877363d6c7f40e6ff9e41 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Tue, 8 Jul 2025 14:16:58 +0200 Subject: [PATCH 04/11] [IMP] ..company_aware..r: prevent mail encapsulation --- mail_company_aware_sender/README.rst | 3 +++ mail_company_aware_sender/models/ir_mail_server.py | 5 +++++ mail_company_aware_sender/readme/DESCRIPTION.rst | 3 +++ mail_company_aware_sender/static/description/index.html | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst index 52aa6fc1be..2f842251c6 100644 --- a/mail_company_aware_sender/README.rst +++ b/mail_company_aware_sender/README.rst @@ -40,6 +40,9 @@ email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org. +Note that after installing this module the system parameter mail.default.from +will no longer be used to set the from address. + This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: {{ object.invoice_user_id.company_aware_email(company=object.company_id) }} diff --git a/mail_company_aware_sender/models/ir_mail_server.py b/mail_company_aware_sender/models/ir_mail_server.py index 823f7460a8..4bf7362206 100644 --- a/mail_company_aware_sender/models/ir_mail_server.py +++ b/mail_company_aware_sender/models/ir_mail_server.py @@ -35,3 +35,8 @@ def _get_test_email_addresses(self): ) ) return email_from, email_to + + @api.model + def _get_default_from_address(self): + """Prevent overwrite of email_from.""" + return None diff --git a/mail_company_aware_sender/readme/DESCRIPTION.rst b/mail_company_aware_sender/readme/DESCRIPTION.rst index 2ee1ecea21..b7406e1267 100644 --- a/mail_company_aware_sender/readme/DESCRIPTION.rst +++ b/mail_company_aware_sender/readme/DESCRIPTION.rst @@ -10,6 +10,9 @@ email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org. +Note that after installing this module the system parameter mail.default.from +will no longer be used to set the from address. + This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: {{ object.invoice_user_id.company_aware_email(company=object.company_id) }} diff --git a/mail_company_aware_sender/static/description/index.html b/mail_company_aware_sender/static/description/index.html index 578a11e9ce..30bb9247a0 100644 --- a/mail_company_aware_sender/static/description/index.html +++ b/mail_company_aware_sender/static/description/index.html @@ -1,4 +1,3 @@ - @@ -380,6 +379,8 @@

Mail Sender Company Aware

email from address, so when sending from the kingdom, the email from will be charlemagne@kingdom.fr, but when sending from the empire, the email will be charlemagne@imperiumromanum.org.

+

Note that after installing this module the system parameter mail.default.from +will no longer be used to set the from address.

This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: {{ object.invoice_user_id.company_aware_email(company=object.company_id) }} From f4c72d894dded60afdb0a2cb47241570dd774bd0 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Tue, 8 Jul 2025 15:10:27 +0200 Subject: [PATCH 05/11] [IMP] ..company_aware..r: utility function for template email_from --- mail_company_aware_sender/README.rst | 2 +- mail_company_aware_sender/models/res_users.py | 13 +++++++++++++ mail_company_aware_sender/readme/DESCRIPTION.rst | 2 +- .../static/description/index.html | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst index 2f842251c6..b3bd011196 100644 --- a/mail_company_aware_sender/README.rst +++ b/mail_company_aware_sender/README.rst @@ -45,7 +45,7 @@ will no longer be used to set the from address. This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: -{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +{{ user.get_company_aware_email(object) }} In this example the company defined in the object will be used instead of the current company. diff --git a/mail_company_aware_sender/models/res_users.py b/mail_company_aware_sender/models/res_users.py index f4177dfbec..79ca559225 100644 --- a/mail_company_aware_sender/models/res_users.py +++ b/mail_company_aware_sender/models/res_users.py @@ -13,3 +13,16 @@ def company_aware_email(self, company=None, default_email=None): return self.partner_id.company_aware_email( company=company, default_email=default_email ) + + def get_company_aware_email(self, record): + """Get company aware email_from related to Odoo record. + + Can be used on email_from field of template like so: + {{ user.get_company_aware_email(object) }} + """ + record.ensure_one() # Must be recordlist with exactly one member. + user = record.user_id if "user_id" in record._fields else self.env.user + company = ( + record.company_id if "company_id" in record._fields else self.env.company + ) + return user.company_aware_email(company=company) diff --git a/mail_company_aware_sender/readme/DESCRIPTION.rst b/mail_company_aware_sender/readme/DESCRIPTION.rst index b7406e1267..d7067a1348 100644 --- a/mail_company_aware_sender/readme/DESCRIPTION.rst +++ b/mail_company_aware_sender/readme/DESCRIPTION.rst @@ -15,6 +15,6 @@ will no longer be used to set the from address. This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: -{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +{{ user.get_company_aware_email(object) }} In this example the company defined in the object will be used instead of the current company. diff --git a/mail_company_aware_sender/static/description/index.html b/mail_company_aware_sender/static/description/index.html index 30bb9247a0..468aba70c0 100644 --- a/mail_company_aware_sender/static/description/index.html +++ b/mail_company_aware_sender/static/description/index.html @@ -383,7 +383,7 @@

Mail Sender Company Aware

will no longer be used to set the from address.

This module can also be used to send company aware emails from templates. For instance with this formula for the email_from field: -{{ object.invoice_user_id.company_aware_email(company=object.company_id) }} +{{ user.get_company_aware_email(object) }} In this example the company defined in the object will be used instead of the current company.

Table of contents

From e6fe1dce2f81ec3998b53cad240aa73836ef4541 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Tue, 8 Jul 2025 16:03:19 +0200 Subject: [PATCH 06/11] [IMP] ..company_aware..r: test utility function --- .../tests/test_company_aware_sender.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py index fc93d4469e..1fb2853767 100644 --- a/mail_company_aware_sender/tests/test_company_aware_sender.py +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -51,6 +51,14 @@ def setUpClass(cls): ], } ) + cls.partner_himiltrude = cls.Partner.create( + { + "name": "Himiltrude", + "email": "himiltrude@therp.nl", + "user_id": cls.user_charles.id, + "company_id": cls.company_imperium.id, + } + ) def test_nothing_changed(self): # Check with default user and author (current user). @@ -97,3 +105,19 @@ def test_company_overwrite(self): author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) self.assertEqual(email_from, '"Charles Le Magne" ') + + def test_get_sender_from_object(self): + # Whitelist domain. + self.mail_server.write( + {"domain_whitelist": "therp.nl,kingdom.fr,imperiumromanum.org"} + ) + # Check with default user and author (current user). + main_company = self.env.ref("base.main_company") + himiltrude = self.partner_himiltrude.sudo().with_company(main_company) + # Make sure user and company from object used. + email_from = self.env.user.sudo().get_company_aware_email(himiltrude) + self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + + def test_disable_encapsulation(self): + email_from = self.mail_server._get_default_from_address() + self.assertEqual(email_from, None) From 017c9223e8c16738487c9f60988edde9c035ebe3 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Tue, 8 Jul 2025 17:07:29 +0200 Subject: [PATCH 07/11] [IMP] ..company_aware..r: test server functions --- mail_company_aware_sender/tests/__init__.py | 1 + mail_company_aware_sender/tests/common.py | 61 ++++++++++++++++++ .../tests/test_company_aware_sender.py | 64 +------------------ .../tests/test_mail_server.py | 45 +++++++++++++ 4 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 mail_company_aware_sender/tests/common.py create mode 100644 mail_company_aware_sender/tests/test_mail_server.py diff --git a/mail_company_aware_sender/tests/__init__.py b/mail_company_aware_sender/tests/__init__.py index 2b47816235..e64f5c2944 100644 --- a/mail_company_aware_sender/tests/__init__.py +++ b/mail_company_aware_sender/tests/__init__.py @@ -1,3 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_company_aware_sender +from . import test_mail_server diff --git a/mail_company_aware_sender/tests/common.py b/mail_company_aware_sender/tests/common.py new file mode 100644 index 0000000000..cfd075fcf6 --- /dev/null +++ b/mail_company_aware_sender/tests/common.py @@ -0,0 +1,61 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import Command +from odoo.tests import TransactionCase + + +class CompanyAwareSenderCase(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Create two companies and a partner/user. + cls.Company = cls.env["res.company"] + cls.Partner = cls.env["res.partner"] + cls.User = cls.env["res.users"] + cls.IrMailServer = cls.env["ir.mail_server"] + cls.mail_server = cls.IrMailServer.create( + { + "name": "localhost", + "smtp_host": "localhost", + "domain_whitelist": "therp.nl", + } + ) + cls.company_kingdom = cls.Company.create( + { + "name": "The kingdom of France", + "email": "info@kingdom.fr", + } + ) + cls.company_imperium = cls.Company.create( + { + "name": "Imperium Romanum", + "email": "chancellery@imperiumromanum.org", + "use_email_domain": True, + "format_email": False, + } + ) + cls.partner_charles = cls.Partner.create( + { + "name": "Charles Le Magne", + "email": "charlemagne@therp.nl", + } + ) + cls.user_charles = cls.User.with_context(no_reset_password=True).create( + { + "partner_id": cls.partner_charles.id, + "login": "charlemagne", + "email": "charlemagne@therp.nl", + "company_id": cls.company_kingdom.id, + "company_ids": [ + Command.set([cls.company_kingdom.id, cls.company_imperium.id]), + ], + } + ) + cls.partner_himiltrude = cls.Partner.create( + { + "name": "Himiltrude", + "email": "himiltrude@therp.nl", + "user_id": cls.user_charles.id, + "company_id": cls.company_imperium.id, + } + ) diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py index 1fb2853767..c618461d37 100644 --- a/mail_company_aware_sender/tests/test_company_aware_sender.py +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -1,65 +1,9 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import Command -from odoo.tests import TransactionCase +from .common import CompanyAwareSenderCase -class TestCompanyAwareSender(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - # Create two companies and a partner/user. - cls.Company = cls.env["res.company"] - cls.Partner = cls.env["res.partner"] - cls.User = cls.env["res.users"] - cls.IrMailServer = cls.env["ir.mail_server"] - cls.mail_server = cls.IrMailServer.create( - { - "name": "localhost", - "smtp_host": "localhost", - "domain_whitelist": "therp.nl", - } - ) - cls.company_kingdom = cls.Company.create( - { - "name": "The kingdom of France", - "email": "info@kingdom.fr", - } - ) - cls.company_imperium = cls.Company.create( - { - "name": "Imperium Romanum", - "email": "chancellery@imperiumromanum.org", - "use_email_domain": True, - "format_email": False, - } - ) - cls.partner_charles = cls.Partner.create( - { - "name": "Charles Le Magne", - "email": "charlemagne@therp.nl", - } - ) - cls.user_charles = cls.User.with_context(no_reset_password=True).create( - { - "partner_id": cls.partner_charles.id, - "login": "charlemagne", - "email": "charlemagne@therp.nl", - "company_id": cls.company_kingdom.id, - "company_ids": [ - Command.set([cls.company_kingdom.id, cls.company_imperium.id]), - ], - } - ) - cls.partner_himiltrude = cls.Partner.create( - { - "name": "Himiltrude", - "email": "himiltrude@therp.nl", - "user_id": cls.user_charles.id, - "company_id": cls.company_imperium.id, - } - ) - +class TestCompanyAwareSender(CompanyAwareSenderCase): def test_nothing_changed(self): # Check with default user and author (current user). mail_thread = ( @@ -117,7 +61,3 @@ def test_get_sender_from_object(self): # Make sure user and company from object used. email_from = self.env.user.sudo().get_company_aware_email(himiltrude) self.assertEqual(email_from, "charlemagne@imperiumromanum.org") - - def test_disable_encapsulation(self): - email_from = self.mail_server._get_default_from_address() - self.assertEqual(email_from, None) diff --git a/mail_company_aware_sender/tests/test_mail_server.py b/mail_company_aware_sender/tests/test_mail_server.py new file mode 100644 index 0000000000..00366b878f --- /dev/null +++ b/mail_company_aware_sender/tests/test_mail_server.py @@ -0,0 +1,45 @@ +# Copyright 2025 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.exceptions import ValidationError + +from .common import CompanyAwareSenderCase + + +class TestMailServer(CompanyAwareSenderCase): + def test_test_email_adresses(self): + # Whitelist domain, and enable from address. + self.mail_server.write( + { + "from_filter": "info@therp.nl", + "domain_whitelist": "therp.nl,kingdom.fr,imperiumromanum.org", + } + ) + email_from, email_to = self.mail_server._get_test_email_addresses() + self.assertEqual(email_from, "info@therp.nl") + self.assertEqual(email_to, "noreply@odoo.com") + # Disable from_filter and remove therp.nl from whitelist. + self.mail_server.write( + { + "from_filter": "info@therp.nl", + "domain_whitelist": "kingdom.fr,imperiumromanum.org", + } + ) + self.mail_server.write({"from_filter": False}) + # Partner charles in imperium should use company aware from. + email_from, email_to = ( + self.mail_server.with_user(self.user_charles) + .with_company(self.company_imperium) + ._get_test_email_addresses() + ) + self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + self.assertEqual(email_to, "noreply@odoo.com") + # There should be an exception when using an invalid email domain. + self.company_imperium.write({"email": "court@aachen.de"}) + with self.assertRaises(ValidationError): + self.mail_server.with_user(self.user_charles).with_company( + self.company_imperium + )._get_test_email_addresses() + + def test_disable_encapsulation(self): + email_from = self.mail_server._get_default_from_address() + self.assertEqual(email_from, None) From 7f2e54c2eab422543414d60cbc94913218e5715a Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Thu, 10 Jul 2025 11:33:38 +0200 Subject: [PATCH 08/11] [FIX] ..company_aware..: only override reply_to for configured companies --- mail_company_aware_sender/models/ir_mail_server.py | 12 ++++++++++-- mail_company_aware_sender/models/res_company.py | 3 +++ mail_company_aware_sender/tests/test_mail_server.py | 4 +++- mail_company_aware_sender/views/res_company_view.xml | 4 ++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/mail_company_aware_sender/models/ir_mail_server.py b/mail_company_aware_sender/models/ir_mail_server.py index 4bf7362206..bdd9296a01 100644 --- a/mail_company_aware_sender/models/ir_mail_server.py +++ b/mail_company_aware_sender/models/ir_mail_server.py @@ -36,7 +36,15 @@ def _get_test_email_addresses(self): ) return email_from, email_to + def build_email(self, *args, **kwargs): + """Provide a valid return address when using company aware From.""" + if self.env.company.use_email_domain and self.env.company.reply_to: + kwargs["reply_to"] = self.env.company.reply_to + return super().build_email(*args, **kwargs) + @api.model def _get_default_from_address(self): - """Prevent overwrite of email_from.""" - return None + """Prevent overwrite of email_from if not desired for company.""" + if self.env.company.use_email_domain: + return None + return super()._get_default_from_address() diff --git a/mail_company_aware_sender/models/res_company.py b/mail_company_aware_sender/models/res_company.py index 84beaa2608..c218fefd8b 100644 --- a/mail_company_aware_sender/models/res_company.py +++ b/mail_company_aware_sender/models/res_company.py @@ -16,6 +16,9 @@ class ResCompany(models.Model): help='Format email_from with name "John Smith" ' " or use plain email address", ) + reply_to = fields.Char( + help="reply_to address to use, if not filled, fallback to mail.default.from", + ) def _override_email_domain(self): """Check whether company email domain can and should be used.""" diff --git a/mail_company_aware_sender/tests/test_mail_server.py b/mail_company_aware_sender/tests/test_mail_server.py index 00366b878f..807fab5721 100644 --- a/mail_company_aware_sender/tests/test_mail_server.py +++ b/mail_company_aware_sender/tests/test_mail_server.py @@ -41,5 +41,7 @@ def test_test_email_adresses(self): )._get_test_email_addresses() def test_disable_encapsulation(self): - email_from = self.mail_server._get_default_from_address() + email_from = self.mail_server.with_company( + self.company_imperium + )._get_default_from_address() self.assertEqual(email_from, None) diff --git a/mail_company_aware_sender/views/res_company_view.xml b/mail_company_aware_sender/views/res_company_view.xml index 5f3e28f782..2901f8edfd 100644 --- a/mail_company_aware_sender/views/res_company_view.xml +++ b/mail_company_aware_sender/views/res_company_view.xml @@ -15,6 +15,10 @@ name="format_email" attrs="{'invisible': [('use_email_domain', '=', False)]}" /> + From e907b43b380d7dec1008862561efb3ceb32ff80f Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Fri, 23 Jan 2026 08:51:50 +0100 Subject: [PATCH 09/11] [IMP] mail_company_aware_sender: pre-commit stuff --- mail_company_aware_sender/README.rst | 10 +++++----- .../static/description/index.html | 17 ++++++++++------- .../odoo/addons/mail_company_aware_sender | 1 + setup/mail_company_aware_sender/setup.py | 6 ++++++ 4 files changed, 22 insertions(+), 12 deletions(-) create mode 120000 setup/mail_company_aware_sender/odoo/addons/mail_company_aware_sender create mode 100644 setup/mail_company_aware_sender/setup.py diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst index b3bd011196..910b0bf1fa 100644 --- a/mail_company_aware_sender/README.rst +++ b/mail_company_aware_sender/README.rst @@ -17,13 +17,13 @@ Mail Sender Company Aware :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github - :target: https://github.com/OCA/social/tree/16.0/mail_company_aware_sender + :target: https://github.com/OCA/social/tree/14.0/mail_company_aware_sender :alt: OCA/social .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/social-16-0/social-16-0-mail_company_aware_sender + :target: https://translation.odoo-community.org/projects/social-14-0/social-14-0-mail_company_aware_sender :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=14.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -76,7 +76,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -108,6 +108,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -This module is part of the `OCA/social `_ project on GitHub. +This module is part of the `OCA/social `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mail_company_aware_sender/static/description/index.html b/mail_company_aware_sender/static/description/index.html index 468aba70c0..07139d0bad 100644 --- a/mail_company_aware_sender/static/description/index.html +++ b/mail_company_aware_sender/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -368,7 +369,7 @@

Mail Sender Company Aware

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:7ee2897c3172b5b4d4927dbf7ec1e94e1c71bc5602f7bdf85b3ac0cae33481d0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module allows to send out mails using a company aware email address. Suppose we have a user Charles the Magnificent with email address charlemagne@kingdom.fr. However this user works for two companies, one @@ -422,7 +423,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -445,11 +446,13 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/social project on GitHub.

+

This module is part of the OCA/social project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/setup/mail_company_aware_sender/odoo/addons/mail_company_aware_sender b/setup/mail_company_aware_sender/odoo/addons/mail_company_aware_sender new file mode 120000 index 0000000000..8d9bd4635a --- /dev/null +++ b/setup/mail_company_aware_sender/odoo/addons/mail_company_aware_sender @@ -0,0 +1 @@ +../../../../mail_company_aware_sender \ No newline at end of file diff --git a/setup/mail_company_aware_sender/setup.py b/setup/mail_company_aware_sender/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/mail_company_aware_sender/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From b54d05c4a8cf19f31ffd0fd1561af527b5bae745 Mon Sep 17 00:00:00 2001 From: Nikos Tsirintanis Date: Fri, 23 Jan 2026 08:56:30 +0100 Subject: [PATCH 10/11] [14.0][MIG] mail_company_aware_sender: Backport to 14.0 --- mail_company_aware_sender/__manifest__.py | 2 +- .../models/ir_mail_server.py | 12 ++++----- .../models/mail_thread.py | 4 +-- mail_company_aware_sender/tests/common.py | 7 +++--- .../tests/test_company_aware_sender.py | 25 +++++++++++++------ .../tests/test_mail_server.py | 19 ++++++++++---- 6 files changed, 43 insertions(+), 26 deletions(-) diff --git a/mail_company_aware_sender/__manifest__.py b/mail_company_aware_sender/__manifest__.py index 4c48e843c4..62880cd88d 100644 --- a/mail_company_aware_sender/__manifest__.py +++ b/mail_company_aware_sender/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Mail Sender Company Aware", "summary": "Send emails with company specific mail domain", - "version": "16.0.1.0.0", + "version": "14.0.1.0.0", "category": "Social Network", "website": "https://github.com/OCA/social", "author": ("Therp BV, " "Odoo Community Association (OCA)"), diff --git a/mail_company_aware_sender/models/ir_mail_server.py b/mail_company_aware_sender/models/ir_mail_server.py index bdd9296a01..3116f91199 100644 --- a/mail_company_aware_sender/models/ir_mail_server.py +++ b/mail_company_aware_sender/models/ir_mail_server.py @@ -21,18 +21,18 @@ def _is_domain_whitelisted(self, domain): def _get_test_email_addresses(self): self.ensure_one() - if self.from_filter or not self.env.user.email: - return super()._get_test_email_addresses() email_to = "noreply@odoo.com" + # if server forces a sender, use it. + if self.smtp_from: + return self.smtp_from, email_to + if not self.env.user.email: + return super()._get_test_email_addresses() email_from = self.env.user.company_aware_email() email_domain = email_domain_extract(email_from) valid_domains = self._get_domain_whitelist(self.domain_whitelist) if email_domain not in valid_domains: raise ValidationError( - _( - "Domain %s not whitelisted on this server", - email_domain, - ) + _("Domain %s not whitelisted on this server") % email_domain ) return email_from, email_to diff --git a/mail_company_aware_sender/models/mail_thread.py b/mail_company_aware_sender/models/mail_thread.py index c3e12fa545..412a6642a9 100644 --- a/mail_company_aware_sender/models/mail_thread.py +++ b/mail_company_aware_sender/models/mail_thread.py @@ -6,7 +6,7 @@ class MailThread(models.AbstractModel): _inherit = "mail.thread" - def _message_compute_author(self, author_id, email_from, raise_on_email=True): + def _message_compute_author(self, author_id, email_from, raise_exception=True): """Set email from using company email domain. We will NOT override an explicitly passed email_from. @@ -16,7 +16,7 @@ def _message_compute_author(self, author_id, email_from, raise_on_email=True): """ email_passed = bool(email_from) author_id, email_from = super()._message_compute_author( - author_id, email_from, raise_on_email=raise_on_email + author_id, email_from, raise_exception=raise_exception ) if (not email_passed) and author_id: author = self.env["res.partner"].browse(author_id) diff --git a/mail_company_aware_sender/tests/common.py b/mail_company_aware_sender/tests/common.py index cfd075fcf6..1754f4200a 100644 --- a/mail_company_aware_sender/tests/common.py +++ b/mail_company_aware_sender/tests/common.py @@ -1,10 +1,9 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import Command -from odoo.tests import TransactionCase +from odoo.tests.common import SavepointCase -class CompanyAwareSenderCase(TransactionCase): +class CompanyAwareSenderCase(SavepointCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -47,7 +46,7 @@ def setUpClass(cls): "email": "charlemagne@therp.nl", "company_id": cls.company_kingdom.id, "company_ids": [ - Command.set([cls.company_kingdom.id, cls.company_imperium.id]), + (6, 0, [cls.company_kingdom.id, cls.company_imperium.id]), ], } ) diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py index c618461d37..060982c9b6 100644 --- a/mail_company_aware_sender/tests/test_company_aware_sender.py +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -1,9 +1,18 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from email.utils import parseaddr + from .common import CompanyAwareSenderCase class TestCompanyAwareSender(CompanyAwareSenderCase): + def _assert_email(self, email_from, expected_email, expected_name=None): + """Assert email_from matches expected parts, tolerant to quoting differences.""" + name, email = parseaddr(email_from or "") + self.assertEqual(email, expected_email) + if expected_name is not None: + self.assertEqual(name, expected_name) + def test_nothing_changed(self): # Check with default user and author (current user). mail_thread = ( @@ -13,12 +22,12 @@ def test_nothing_changed(self): ) author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) - self.assertEqual(email_from, '"Charles Le Magne" ') + self._assert_email(email_from, "charlemagne@therp.nl", "Charles Le Magne") author_id, email_from = mail_thread._message_compute_author( None, '"Unknown Person" ' ) self.assertEqual(author_id, False) - self.assertEqual(email_from, '"Unknown Person" ') + self._assert_email(email_from, "unknown.person@example.com", "Unknown Person") def test_company_overwrite(self): # Check with default user and author (current user). @@ -30,25 +39,25 @@ def test_company_overwrite(self): # Should not work if domain not whitelisted. author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) - self.assertEqual(email_from, '"Charles Le Magne" ') + self._assert_email(email_from, "charlemagne@therp.nl", "Charles Le Magne") # Whitelist domain. self.mail_server.write( {"domain_whitelist": "therp.nl,kingdom.fr,imperiumromanum.org"} ) author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) - self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + self._assert_email(email_from, "charlemagne@imperiumromanum.org") self.company_imperium.write({"format_email": True}) author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) - self.assertEqual( - email_from, '"Charles Le Magne" ' + self._assert_email( + email_from, "charlemagne@imperiumromanum.org", "Charles Le Magne" ) # Now opt out for the override. self.partner_charles.write({"fixed_email": True}) author_id, email_from = mail_thread._message_compute_author(None, None) self.assertEqual(author_id, self.partner_charles.id) - self.assertEqual(email_from, '"Charles Le Magne" ') + self._assert_email(email_from, "charlemagne@therp.nl", "Charles Le Magne") def test_get_sender_from_object(self): # Whitelist domain. @@ -60,4 +69,4 @@ def test_get_sender_from_object(self): himiltrude = self.partner_himiltrude.sudo().with_company(main_company) # Make sure user and company from object used. email_from = self.env.user.sudo().get_company_aware_email(himiltrude) - self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + self._assert_email(email_from, "charlemagne@imperiumromanum.org") diff --git a/mail_company_aware_sender/tests/test_mail_server.py b/mail_company_aware_sender/tests/test_mail_server.py index 807fab5721..bfe21d2abd 100644 --- a/mail_company_aware_sender/tests/test_mail_server.py +++ b/mail_company_aware_sender/tests/test_mail_server.py @@ -1,37 +1,46 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from email.utils import parseaddr + from odoo.exceptions import ValidationError from .common import CompanyAwareSenderCase class TestMailServer(CompanyAwareSenderCase): + def _assert_email(self, email_from, expected_email, expected_name=None): + """Assert email_from matches expected parts, tolerant to quoting differences.""" + name, email = parseaddr(email_from or "") + self.assertEqual(email, expected_email) + if expected_name is not None: + self.assertEqual(name, expected_name) + def test_test_email_adresses(self): # Whitelist domain, and enable from address. self.mail_server.write( { - "from_filter": "info@therp.nl", + "smtp_from": "info@therp.nl", "domain_whitelist": "therp.nl,kingdom.fr,imperiumromanum.org", } ) email_from, email_to = self.mail_server._get_test_email_addresses() self.assertEqual(email_from, "info@therp.nl") self.assertEqual(email_to, "noreply@odoo.com") - # Disable from_filter and remove therp.nl from whitelist. + # Disable smtp_from and remove therp.nl from whitelist. self.mail_server.write( { - "from_filter": "info@therp.nl", + "smtp_from": "info@therp.nl", "domain_whitelist": "kingdom.fr,imperiumromanum.org", } ) - self.mail_server.write({"from_filter": False}) + self.mail_server.write({"smtp_from": False}) # Partner charles in imperium should use company aware from. email_from, email_to = ( self.mail_server.with_user(self.user_charles) .with_company(self.company_imperium) ._get_test_email_addresses() ) - self.assertEqual(email_from, "charlemagne@imperiumromanum.org") + self._assert_email(email_from, "charlemagne@imperiumromanum.org") self.assertEqual(email_to, "noreply@odoo.com") # There should be an exception when using an invalid email domain. self.company_imperium.write({"email": "court@aachen.de"}) From 1e0115d801222549698781c24a2bbced6ffd3f2e Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Mon, 23 Feb 2026 12:25:39 +0100 Subject: [PATCH 11/11] [IMP] ..aware_sender: add test for server selection --- mail_company_aware_sender/README.rst | 6 ++- .../static/description/index.html | 26 ++++++---- mail_company_aware_sender/tests/common.py | 10 ++++ .../tests/test_company_aware_sender.py | 8 --- .../tests/test_mail_server.py | 50 +++++++++++++++---- 5 files changed, 72 insertions(+), 28 deletions(-) diff --git a/mail_company_aware_sender/README.rst b/mail_company_aware_sender/README.rst index 910b0bf1fa..611574c5ea 100644 --- a/mail_company_aware_sender/README.rst +++ b/mail_company_aware_sender/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ========================= Mail Sender Company Aware ========================= @@ -13,7 +17,7 @@ Mail Sender Company Aware .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github diff --git a/mail_company_aware_sender/static/description/index.html b/mail_company_aware_sender/static/description/index.html index 07139d0bad..ce1aea7b6e 100644 --- a/mail_company_aware_sender/static/description/index.html +++ b/mail_company_aware_sender/static/description/index.html @@ -3,7 +3,7 @@ -Mail Sender Company Aware +README.rst -
-

Mail Sender Company Aware

+
+ + +Odoo Community Association + +
+

Mail Sender Company Aware

-

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

This module allows to send out mails using a company aware email address. Suppose we have a user Charles the Magnificent with email address charlemagne@kingdom.fr. However this user works for two companies, one @@ -401,7 +406,7 @@

Mail Sender Company Aware

-

Configuration

+

Configuration

To send out mails with company aware from addresses, three things are needed:
    @@ -419,7 +424,7 @@

    Configuration

    emails. This can be configured right under the email field in the partner form.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -427,15 +432,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Therp BV
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -457,5 +462,6 @@

Maintainers

+
diff --git a/mail_company_aware_sender/tests/common.py b/mail_company_aware_sender/tests/common.py index 1754f4200a..86c8093412 100644 --- a/mail_company_aware_sender/tests/common.py +++ b/mail_company_aware_sender/tests/common.py @@ -1,5 +1,7 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from email.utils import parseaddr + from odoo.tests.common import SavepointCase @@ -17,6 +19,7 @@ def setUpClass(cls): "name": "localhost", "smtp_host": "localhost", "domain_whitelist": "therp.nl", + "sequence": 99, } ) cls.company_kingdom = cls.Company.create( @@ -58,3 +61,10 @@ def setUpClass(cls): "company_id": cls.company_imperium.id, } ) + + def _assert_email(self, email_from, expected_email, expected_name=None): + """Assert email_from matches expected parts, tolerant to quoting differences.""" + name, email = parseaddr(email_from or "") + self.assertEqual(email, expected_email) + if expected_name is not None: + self.assertEqual(name, expected_name) diff --git a/mail_company_aware_sender/tests/test_company_aware_sender.py b/mail_company_aware_sender/tests/test_company_aware_sender.py index 060982c9b6..3a72233eb7 100644 --- a/mail_company_aware_sender/tests/test_company_aware_sender.py +++ b/mail_company_aware_sender/tests/test_company_aware_sender.py @@ -1,18 +1,10 @@ # Copyright 2025 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from email.utils import parseaddr from .common import CompanyAwareSenderCase class TestCompanyAwareSender(CompanyAwareSenderCase): - def _assert_email(self, email_from, expected_email, expected_name=None): - """Assert email_from matches expected parts, tolerant to quoting differences.""" - name, email = parseaddr(email_from or "") - self.assertEqual(email, expected_email) - if expected_name is not None: - self.assertEqual(name, expected_name) - def test_nothing_changed(self): # Check with default user and author (current user). mail_thread = ( diff --git a/mail_company_aware_sender/tests/test_mail_server.py b/mail_company_aware_sender/tests/test_mail_server.py index bfe21d2abd..0228a49c3c 100644 --- a/mail_company_aware_sender/tests/test_mail_server.py +++ b/mail_company_aware_sender/tests/test_mail_server.py @@ -1,6 +1,5 @@ -# Copyright 2025 Therp BV . +# Copyright 2025-2026 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from email.utils import parseaddr from odoo.exceptions import ValidationError @@ -8,13 +7,6 @@ class TestMailServer(CompanyAwareSenderCase): - def _assert_email(self, email_from, expected_email, expected_name=None): - """Assert email_from matches expected parts, tolerant to quoting differences.""" - name, email = parseaddr(email_from or "") - self.assertEqual(email, expected_email) - if expected_name is not None: - self.assertEqual(name, expected_name) - def test_test_email_adresses(self): # Whitelist domain, and enable from address. self.mail_server.write( @@ -54,3 +46,43 @@ def test_disable_encapsulation(self): self.company_imperium )._get_default_from_address() self.assertEqual(email_from, None) + + def test_email_server_selection(self): + # When one server has domain whitelisted, that server should send message. + self.IrMailServer.create( + { + "name": "default mail server", + "smtp_host": "localhost", + "domain_whitelist": "therp.nl", + "sequence": 5, + } + ) + # First test with smtp_from. + self.mail_server.write( + { + "smtp_from": "info@imperiumromanum.org", + "domain_whitelist": "imperiumromanum.org", + } + ) + # Partner charles in imperium should use company aware from. + email_from, email_to = ( + self.mail_server.with_user(self.user_charles) + .with_company(self.company_imperium) + ._get_test_email_addresses() + ) + self._assert_email(email_from, "info@imperiumromanum.org") + # Call the (misspelled) _get_mail_sever method, defined in the + # mail_outbound_static module, to select the right mail server. + mail_server_id = self.IrMailServer._get_mail_sever("imperiumromanum.org") + # The server with the domain should be used, despite higher sequence. + self.assertEqual(mail_server_id, self.mail_server.id) + # Now test with no smtp_from. + self.mail_server.write({"smtp_from": False}) + email_from, email_to = ( + self.mail_server.with_user(self.user_charles) + .with_company(self.company_imperium) + ._get_test_email_addresses() + ) + self._assert_email(email_from, "charlemagne@imperiumromanum.org") + mail_server_id = self.IrMailServer._get_mail_sever("imperiumromanum.org") + self.assertEqual(mail_server_id, self.mail_server.id)