Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions mail_alias_with_domain/README.rst
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/14.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-14-0/social-14-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=14.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:%2014.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/14.0/mail_alias_with_domain>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
4 changes: 4 additions & 0 deletions mail_alias_with_domain/__init__.py
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
17 changes: 17 additions & 0 deletions mail_alias_with_domain/__manifest__.py
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-2026 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": "14.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"],
}
4 changes: 4 additions & 0 deletions mail_alias_with_domain/models/__init__.py
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
105 changes: 105 additions & 0 deletions mail_alias_with_domain/models/mail_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2023 Solvti sp. z o.o. (https://solvti.pl).
# Copyright 2025-2026 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",
)

_sql_constraints = [
("unique_alias_entry", "UNIQUE(alias_entry)", "Alias entry must 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.
46 changes: 46 additions & 0 deletions mail_alias_with_domain/models/mail_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2023 Solvti sp. z o.o. (https://solvti.pl)
# Copyright 2025-2026 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"]
alias_names = [
email.replace("@", "__at__")
# tools.email_split only returns clean email addresses (no names).
for email in tools.email_split(message_dict["recipients"])
]
return Alias.search([("alias_name", "in", alias_names)])
10 changes: 10 additions & 0 deletions mail_alias_with_domain/post_init_hook.py
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"
)
7 changes: 7 additions & 0 deletions mail_alias_with_domain/readme/CONTRIBUTORS.rst
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)
41 changes: 41 additions & 0 deletions mail_alias_with_domain/readme/DESCRIPTION.rst
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.

4 changes: 4 additions & 0 deletions mail_alias_with_domain/readme/USAGE.rst
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.
Loading