-
-
Notifications
You must be signed in to change notification settings - Fork 712
[16.0] [MIG] mail_alias_with_domain #1624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| ====================== | ||
| Mail Alias With Domain | ||
| ====================== | ||
|
|
||
| .. | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
| !! This file is generated by oca-gen-addon-readme !! | ||
| !! changes will be overwritten. !! | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
| !! source digest: sha256:3c7166c28331e6e457d77bd71f2af2420004c8b92ad4b8d745f80ad1e6884603 | ||
| !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
|
|
||
| .. |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_alias_with_domain | ||
| :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_alias_with_domain | ||
| :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 adds possibility to process aliases together with domain. | ||
|
|
||
| For example, suppose we have 3 companies in odoo. | ||
| Each company wants to have an alias where customers can send the bills. | ||
| invoice@company1.com | ||
| invoice@company2.com | ||
| invoice@company3.com | ||
|
|
||
| In odoo, aliases are unique, and this module extends this functionality in | ||
| such a way that you can have many of the same aliases but with different domains. | ||
|
|
||
| Note that when an incoming mail can be linked to an alias with a domain, | ||
| this will be the only alias used. However when an incoming mail can be | ||
| linked to multiple aliasses that have a domain, it is possible to have | ||
| multiple used. | ||
|
|
||
| FOR DEVELOPERS | ||
|
|
||
| In the default alias system, only the local part of an email address (the part | ||
| before the @) is used to link an incoming email to an alias. This happens in the | ||
| message_route method of the mail.thread model. | ||
|
|
||
| Aliasses in standard Odoo store the alias_name field without domain. | ||
|
|
||
| To still be able to use a domain name, we need a trick. What we will do is: | ||
|
|
||
| * Replace the alias_name in the user interface with an alias_entry field, where a | ||
| complete email address can be entered. | ||
|
|
||
| * If an alias is entered as a complete email address, this will be stored in the | ||
| alias_name as <localpart>__at__<domain>. For instance alex__at__example.com. | ||
| alias_name is therefore changed from a writable field to a stored computed field. | ||
|
|
||
| * The computation of alias_domain will be enhanced to take full email addresses into | ||
| account. | ||
|
|
||
| * If an incoming mail can be linked to a full email address alias, we will write a | ||
| context key pointing to this alias. The search method of mail.alias will be overriden | ||
| to check for this key, and then not search at all, but just return the alias | ||
| requested. | ||
|
|
||
|
|
||
| **Table of contents** | ||
|
|
||
| .. contents:: | ||
| :local: | ||
|
|
||
| Usage | ||
| ===== | ||
|
|
||
| To use this module, you need to: | ||
|
|
||
| Got to the mail aliasses and check which aliasses you want to link to a specific | ||
| domain. | ||
|
|
||
| Bug Tracker | ||
| =========== | ||
|
|
||
| Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/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 <https://github.com/OCA/social/issues/new?body=module:%20mail_alias_with_domain%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. | ||
|
|
||
| Do not contact contributors directly about support or help with technical issues. | ||
|
|
||
| Credits | ||
| ======= | ||
|
|
||
| Authors | ||
| ~~~~~~~ | ||
|
|
||
| * Solvti | ||
| * Therp BV | ||
|
|
||
| Contributors | ||
| ~~~~~~~~~~~~ | ||
|
|
||
| * `Solvti sp. z o.o. <https://solvti.pl>`_: | ||
|
|
||
| * Jakub Wiselka | ||
|
|
||
| * `Therp <https://therp.nl>`_: | ||
|
|
||
| * Ronald Portier (ronald@therp.nl) | ||
|
|
||
| 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 <https://github.com/OCA/social/tree/16.0/mail_alias_with_domain>`_ project on GitHub. | ||
|
|
||
| You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| from . import models | ||
| from .post_init_hook import init_alias_entry |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Copyright 2023 Solvti sp. z o.o. (https://solvti.pl) | ||
| # Copyright 2025 Therp BV (https://therp.nl) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| { | ||
| "name": "Mail Alias With Domain", | ||
| "summary": "Allow simple mail alias to be combined with a mail domain", | ||
| "author": "Solvti, Therp BV, Odoo Community Association (OCA)", | ||
| "website": "https://github.com/OCA/social", | ||
| "version": "16.0.1.0.0", | ||
| "license": "AGPL-3", | ||
| "application": False, | ||
| "installable": True, | ||
| "post_init_hook": "init_alias_entry", | ||
| "depends": ["mail"], | ||
| "data": ["views/mail_alias_views.xml"], | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| from . import mail_alias | ||
| from . import mail_thread |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Copyright 2023 Solvti sp. z o.o. (https://solvti.pl). | ||
| # Copyright 2025 Therp BV (https://therp.nl). | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
| from odoo import api, fields, models | ||
|
|
||
|
|
||
| class Alias(models.Model): | ||
| _inherit = "mail.alias" | ||
|
|
||
| @api.depends("alias_name") | ||
| def _compute_alias_domain(self): | ||
| alias_with_domain = self.filtered( | ||
| lambda r: r.alias_name and "__at__" in r.alias_name | ||
| ) | ||
| for alias in alias_with_domain: | ||
| alias.alias_domain = alias.alias_name.split("__at__")[1] | ||
| alias_without_domain = self - alias_with_domain | ||
| if alias_without_domain: | ||
| super(Alias, alias_without_domain)._compute_alias_domain() | ||
| return None | ||
|
|
||
| alias_entry = fields.Char( | ||
| help="This will be used to enter an email, complete with domain", | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be unique |
||
|
|
||
| @api.model | ||
| def search(self, domain, **kwargs): | ||
| """If mail alias in context, return this as result.""" | ||
| matching_alias = self.env.context.get("matching_alias", False) | ||
| if matching_alias: | ||
| return matching_alias | ||
| return super().search(domain, **kwargs) | ||
|
|
||
| @api.model_create_multi | ||
| def create(self, vals_list): | ||
| for vals in vals_list: | ||
| self._patch_alias_vals(vals) | ||
| records = super().create(vals_list) | ||
| records._synchronize_alias_entry_with_name() | ||
| return records | ||
|
|
||
| def write(self, vals): | ||
| self._patch_alias_vals(vals) | ||
| result = super().write(vals) | ||
| self._synchronize_alias_entry_with_name() | ||
| return result | ||
|
|
||
| def _synchronize_alias_entry_with_name(self): | ||
| """In case alias created/written without alias_entry, complete entry field.""" | ||
| for this in self: | ||
| if not this.alias_name: | ||
| alias_entry = False | ||
| elif "__at__" in this.alias_name: | ||
| alias_entry = this.alias_name.replace("__at__", "@") | ||
| else: | ||
| alias_entry = this.alias_name | ||
| if this.alias_entry != alias_entry: | ||
| super(Alias, this).write({"alias_entry": alias_entry}) | ||
| return None | ||
|
|
||
| @api.model | ||
| def _patch_alias_vals(self, vals): | ||
| """If vals contains alias_entry, add corresponding alias_name.""" | ||
| alias_entry = vals.get("alias_entry", False) | ||
| if alias_entry: | ||
| default_domain = self._get_default_domain() | ||
| if "@" not in alias_entry: | ||
| alias_name = alias_entry | ||
| elif default_domain and default_domain in alias_entry: | ||
| alias_name = alias_entry.split("@")[0] | ||
| else: | ||
| alias_name = alias_entry.replace("@", "__at__") | ||
| vals["alias_name"] = alias_name | ||
|
|
||
| @api.model | ||
| def _get_default_domain(self): | ||
| """get default domain.""" | ||
| ICP = self.env["ir.config_parameter"].sudo() | ||
| return ICP.get_param("mail.catchall.domain") | ||
|
|
||
| @api.model | ||
| def get_clean_email(self, email): | ||
| """Users tend to pollute emails with extra info. get just the email.""" | ||
| # In Odoo 17.0 there is a new method parse_contact_from_email in | ||
| # odoo/tools/mail.py that we could use for this purpose. | ||
| if email: | ||
| # 1. Replace special characters with spaces. | ||
| cleaned = ( | ||
| email.replace('"', " ") | ||
| .replace("<", " ") | ||
| .replace(">", " ") | ||
| .replace(",", " ") | ||
| ) | ||
| # 2. Split on whitespace | ||
| parts = cleaned.split() | ||
| # 3. Find the part with an '@' if any and assume it is the real email. | ||
| for part in parts: | ||
| if "@" in part: | ||
| return part.lower() | ||
| return False # Else module partner_email_check would raise ValidationError. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Copyright 2023 Solvti sp. z o.o. (https://solvti.pl) | ||
| # Copyright 2025 Therp BV (https://therp.nl) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
| from odoo import api, models, tools | ||
|
|
||
|
|
||
| class MailThread(models.AbstractModel): | ||
| _inherit = "mail.thread" | ||
|
|
||
| @api.model | ||
| def message_route( | ||
| self, message, message_dict, model=None, thread_id=None, custom_values=None | ||
| ): | ||
| """Check for a recipient that can be linked to a full domain alias.""" | ||
| if not self.env.context.get("matching_alias", False): | ||
| matching_alias = self._find_alias_with_domain(message_dict) | ||
| if matching_alias: | ||
| # Call super with extra context. | ||
| return ( | ||
| super() | ||
| .with_context(matching_alias=matching_alias) | ||
| .message_route( | ||
| message, | ||
| message_dict, | ||
| model=model, | ||
| thread_id=thread_id, | ||
| custom_values=custom_values, | ||
| ) | ||
| ) | ||
| return super().message_route( | ||
| message, | ||
| message_dict, | ||
| model=model, | ||
| thread_id=thread_id, | ||
| custom_values=custom_values, | ||
| ) | ||
|
|
||
| def _find_alias_with_domain(self, message_dict): | ||
| """Find all aliasses that match.""" | ||
| Alias = self.env["mail.alias"] | ||
| emails = {email for email in (tools.email_split(message_dict["recipients"]))} | ||
| alias_names = [] | ||
| for email in emails: | ||
| clean_email = Alias.get_clean_email(email) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in which cases does email_split return something else than an email address? |
||
| if not clean_email: | ||
| continue | ||
| alias_name = clean_email.replace("@", "__at__") | ||
| alias_names.append(alias_name) | ||
| return Alias.search([("alias_name", "in", alias_names)]) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # Copyright 2025 Therp BV (https://therp.nl) | ||
| # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). | ||
|
|
||
|
|
||
| def init_alias_entry(cr, registry): | ||
| cr.execute( | ||
| "UPDATE mail_alias" | ||
| " SET alias_entry = alias_name" | ||
| " WHERE alias_entry IS NULL AND NOT alias_name IS NULL" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| * `Solvti sp. z o.o. <https://solvti.pl>`_: | ||
|
|
||
| * Jakub Wiselka | ||
|
|
||
| * `Therp <https://therp.nl>`_: | ||
|
|
||
| * Ronald Portier (ronald@therp.nl) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| This module adds possibility to process aliases together with domain. | ||
|
|
||
| For example, suppose we have 3 companies in odoo. | ||
| Each company wants to have an alias where customers can send the bills. | ||
| invoice@company1.com | ||
| invoice@company2.com | ||
| invoice@company3.com | ||
|
|
||
| In odoo, aliases are unique, and this module extends this functionality in | ||
| such a way that you can have many of the same aliases but with different domains. | ||
|
|
||
| Note that when an incoming mail can be linked to an alias with a domain, | ||
| this will be the only alias used. However when an incoming mail can be | ||
| linked to multiple aliasses that have a domain, it is possible to have | ||
| multiple used. | ||
|
|
||
| FOR DEVELOPERS | ||
|
|
||
| In the default alias system, only the local part of an email address (the part | ||
| before the @) is used to link an incoming email to an alias. This happens in the | ||
| message_route method of the mail.thread model. | ||
|
|
||
| Aliasses in standard Odoo store the alias_name field without domain. | ||
|
|
||
| To still be able to use a domain name, we need a trick. What we will do is: | ||
|
|
||
| * Replace the alias_name in the user interface with an alias_entry field, where a | ||
| complete email address can be entered. | ||
|
|
||
| * If an alias is entered as a complete email address, this will be stored in the | ||
| alias_name as <localpart>__at__<domain>. For instance alex__at__example.com. | ||
| alias_name is therefore changed from a writable field to a stored computed field. | ||
|
|
||
| * The computation of alias_domain will be enhanced to take full email addresses into | ||
| account. | ||
|
|
||
| * If an incoming mail can be linked to a full email address alias, we will write a | ||
| context key pointing to this alias. The search method of mail.alias will be overriden | ||
| to check for this key, and then not search at all, but just return the alias | ||
| requested. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| To use this module, you need to: | ||
|
|
||
| Got to the mail aliasses and check which aliasses you want to link to a specific | ||
| domain. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't find it super far fetched to have this in a mail address. did you consider using a character illegal in both localparts and domain names, like '&'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hbrunn Taken up most of your remarks in new PR here: #1797
Only not this one. Using
&led to other problems, and apparently is actually allowed also, just like_and most printable characters. At least according to this site: https://www.ditig.com/characters-allowed-in-email-addresses. But even if__at__is used in some email address - never encountered anything like it -, it will be extremely unlikely that this also will coincide with en email address used as an alias.