Trois adresses techniques décident du sort de chaque email qui quitte Odoo : celle qui reçoit les rebonds (bounce), celle qui collecte les réponses (catchall), et celle qui sert d'expéditeur de repli (default from). Depuis la version 17, ces trois adresses ne vivent plus dans des paramètres système globaux mais dans un modèle dédié et rattaché à chaque société : mail.alias.domain. Comprendre ce modèle, c'est cesser de subir les réécritures d'expéditeur silencieuses et les bounces qui n'arrivent nulle part.
Cet article dissèque mail.alias.domain tel qu'il existe en Odoo 19 : ses champs, ses adresses calculées, ses contraintes, et son articulation avec le pivot from_filter vu dans les articles précédents de la série. Il prolonge l'article HUB sur les 4 couches mail et complète la configuration des relais (Brevo, Mailjet / SES / Microsoft 365) par le versant interne : ce qu'Odoo met dans les en-têtes avant de remettre le message au serveur sortant.
from_filter et la sélection du serveur sortant. Aucune manipulation DNS n'est nécessaire pour cet article — tout se passe dans le backend Odoo.
1. Pourquoi mail.alias.domain existe
Jusqu'à la version 16, la configuration des adresses techniques d'Odoo reposait sur des paramètres système (ICP, ir.config_parameter) globaux à toute la base : mail.catchall.domain, mail.bounce.alias, mail.catchall.alias et mail.default.from. Un seul jeu de valeurs pour l'ensemble de l'instance — un problème dès qu'une base héberge plusieurs sociétés avec des domaines d'envoi distincts.
Le cas qui exposait la limite : une base hébergeant deux sociétés aux marques distinctes, chacune avec son propre domaine d'envoi. Avec un seul mail.default.from global, toutes les notifications partaient sous une identité unique, quelle que soit la société émettrice ; et un seul mail.catchall.domain empêchait de router les réponses vers le bon domaine. Toute organisation multi-société ou multi-marque se heurtait à ce plafond.
La version 17 a introduit le modèle mail.alias.domain pour porter cette configuration au niveau de chaque société. Chaque res.company pointe désormais un alias_domain_id, et le modèle calcule à partir de lui les adresses de bounce, catchall et default from. Aucun changement de ce mécanisme entre la v18 et la v19 : le modèle décrit ici est stable en Odoo 19.
Pour ne pas perdre la configuration des bases migrées, Odoo conserve une couche de compatibilité, _migrate_icp_to_domain(), qui relit les anciens ICP et crée un mail.alias.domain équivalent (détaillée en section 7).
2. Anatomie du modèle
Le modèle mail.alias.domain (chemin backend : Settings → Technical → Email → Alias Domains) tient en une poignée de champs, dont la moitié sont des adresses calculées à partir d'un local-part et du nom de domaine.
| Champ | Type | Défaut | Rôle |
|---|---|---|---|
name | Char (requis) | — | Domaine email, ex. example.com |
sequence | Integer | 10 | Ordre de priorité entre domaines |
bounce_alias | Char (requis) | bounce | Local-part du Return-Path |
bounce_email | Char (calculé) | — | {bounce_alias}@{name} |
catchall_alias | Char (requis) | catchall | Local-part du Reply-To catchall |
catchall_email | Char (calculé) | — | {catchall_alias}@{name} |
default_from | Char | notifications | Local-part ou email complet |
default_from_email | Char (calculé) | — | From de repli résolu (voir section 4) |
company_ids | One2many | — | Sociétés rattachées à ce domaine |
Le nom de domaine n'est pas libre : une contrainte _check_name impose qu'il corresponde à un dot-atom RFC valide (caractères latins non accentués uniquement). Un domaine contenant un accent ou un caractère interdit lève une ValidationError à l'enregistrement plutôt que d'être silencieusement transformé.
mail.alias.domain rattaché à une res.company dérive trois adresses calculées à partir d'un local-part et du nom de domaine.
3. Les trois adresses techniques
Les trois local-parts ne jouent pas le même rôle dans le cycle de vie d'un email. Les distinguer évite de confondre un problème de réception de bounces avec un problème d'expéditeur.
Le bounce (bounce_email) alimente le Return-Path — l'Envelope From de la couche SMTP (RFC 5321), distinct du From: visible. À l'envoi d'une notification, Odoo positionne l'en-tête Return-Path sur le bounce_email du domaine de l'enregistrement source. C'est l'adresse qui reçoit les rapports de non-distribution (NDR) : quand un email destiné à bounce@example.com revient, la passerelle entrante le reconnaît comme un rebond (elle compare le destinataire aux bounce_email de tous les mail.alias.domain) et marque l'adresse fautive comme invalide après un rejet permanent.
Le catchall (catchall_email) sert de Reply-To générique : quand un destinataire répond à une notification Odoo, sa réponse atterrit sur l'adresse catchall, que la passerelle entrante route ensuite vers le bon enregistrement (le fil de discussion d'une commande, d'un ticket…). Le routage entrant lui-même relève de la passerelle fetchmail, sujet de l'article suivant de la série.
Le default from (default_from_email) est l'expéditeur de repli : l'adresse que Odoo substitue dans le From: quand l'expéditeur d'origine ne peut pas être conservé tel quel — typiquement quand aucun serveur sortant ne déclare un from_filter couvrant son domaine. C'est le champ le plus subtil des trois, parce que sa valeur peut prendre deux formes.
4. La subtilité default_from_email
Contrairement au bounce et au catchall — qui sont toujours des local-parts auxquels Odoo ajoute le domaine —, le champ default_from accepte soit un local-part, soit une adresse email complète. La méthode calculée _compute_default_from_email arbitre entre les deux : si la valeur contient déjà un @, elle est prise telle quelle ; sinon, le nom de domaine est ajouté.
# Logique de _compute_default_from_email (modèle mail.alias.domain, Odoo 19)
for domain in self.filtered('default_from'):
if "@" in domain.default_from:
domain.default_from_email = domain.default_from # email complet : tel quel
else:
domain.default_from_email = f'{domain.default_from}@{domain.name}' # local-part : + domaine
Cette souplesse a un usage concret. Avec un local-part (notifications), chaque société garde un From de repli sur son propre domaine (notifications@example.com). Avec une adresse complète, l'aide-documentation du champ le précise, on peut forcer tous les emails sortants de la société à partir d'une adresse unique, y compris sur un domaine différent — utile pour canaliser des notifications derrière une seule identité d'envoi validée.
default_from_email doit appartenir à un domaine couvert par le from_filter d'un ir.mail_server. Sinon, le From de repli lui-même retombe en fin de l'algorithme de sélection du serveur — et perd l'alignement DKIM. Une contrainte interne (_check_default_from_not_used_by_users) interdit par ailleurs de réutiliser une adresse déjà revendiquée par le from_filter d'un serveur SMTP personnel : la tentative lève une UserError.
5. Le lien avec from_filter et la sélection du serveur
Le default_from n'est pas une décoration : il est un maillon de l'algorithme _find_mail_server() qui choisit le serveur sortant. Quand l'adresse From: d'origine ne matche aucun from_filter, Odoo bascule sur le default_from_email de la société et relance la recherche avec cette adresse — réécrivant au passage l'expéditeur visible. La figure ci-dessous situe les trois adresses dans le flux entrant et sortant.
_find_mail_server() est traité dans les articles relais de la série.
Concrètement, la bascule se déroule ainsi. Odoo cherche d'abord un serveur dont le from_filter couvre l'adresse From: exacte, puis son domaine. Sans correspondance, il substitue le default_from_email de la société et relance la recherche avec cette adresse de repli. Si l'expéditeur d'origine et le default from diffèrent, Odoo encapsule alors le From visible : le destinataire reçoit un en-tête de la forme Expéditeur d'origine via Notifications <notifications@example.com>. C'est le symptôme typique d'un domaine d'alias désaligné du relais — l'identité d'envoi n'est plus celle attendue, et la signature DKIM ne porte plus sur le bon domaine.
En pratique, la règle de cohérence se résume ainsi : le domaine du mail.alias.domain doit correspondre au from_filter du serveur sortant de la même société. Si la société envoie via un relais filtrant sur example.com, son mail.alias.domain doit nommer example.com, et son default_from doit rester sur ce domaine. Le mécanisme d'encapsulation et l'algorithme de sélection sont détaillés dans l'article Brevo et l'article DKIM/SPF/DMARC.
6. Multi-société : un domaine par res.company
L'intérêt central du modèle est là : chaque société rattache son propre mail.alias.domain via le champ res.company.alias_domain_id. À partir de ce lien, la société expose trois adresses : bounce_email et catchall_email sont des champs calculés côté société, tandis que default_from_email est un related direct sur le domaine. Ensemble, elles pilotent les en-têtes de ses emails.
# Côté res.company (Odoo 19) : le lien et les adresses dérivées
alias_domain_id = fields.Many2one('mail.alias.domain', ...) # le domaine de la société
default_from_email = fields.Char(related='alias_domain_id.default_from_email')
# bounce_email / catchall_email : champs calculés à partir du domaine rattaché
Deux contraintes d'unicité protègent la configuration, exprimées en Odoo 19 via la syntaxe models.Constraint (et non l'ancien _sql_constraints) :
# Contraintes SQL du modèle mail.alias.domain (Odoo 19)
_bounce_email_uniques = models.Constraint(
'UNIQUE(bounce_alias, name)', 'Bounce emails should be unique')
_catchall_email_uniques = models.Constraint(
'UNIQUE(catchall_alias, name)', 'Catchall emails should be unique')
Une contrainte applicative complémentaire (_check_bounce_catchall_uniqueness) va plus loin, sur deux fronts. D'abord, elle interdit qu'un bounce ou un catchall entre en collision avec un autre mail.alias.domain portant le même name — le cas fréquent à la migration, où deux domaines homonymes réutiliseraient le même bounce. Ensuite, elle vérifie l'absence de collision avec un mail.alias métier existant (l'alias d'un produit, d'un projet…). Dans les deux cas, la ValidationError nomme l'élément en conflit — et, pour un alias métier, le document propriétaire — ce qui accélère le diagnostic.
7. Migration et auto-attribution : deux pièges
Deux comportements automatiques méritent l'attention, l'un à la migration, l'autre à la première création.
La compatibilité ICP. Pour les bases venues d'une version antérieure à la 17 — ou pour le cas où le module mail est installé après une configuration faite au niveau du module base seul —, la méthode _migrate_icp_to_domain() relit les anciens paramètres système (mail.catchall.domain, mail.bounce.alias, mail.catchall.alias, mail.default.from) et reconstruit un mail.alias.domain équivalent :
# _migrate_icp_to_domain() — couche de compatibilité pré-v17 (Odoo 19)
alias_domain = Icp.get_param('mail.catchall.domain')
if alias_domain:
existing = self.search([('name', '=', alias_domain)])
if existing:
return existing # idempotent : ne recrée pas un domaine homonyme
return self.create({
'name': alias_domain,
'bounce_alias': Icp.get_param('mail.bounce.alias') or 'bounce',
'catchall_alias': Icp.get_param('mail.catchall.alias') or 'catchall',
'default_from': Icp.get_param('mail.default.from') or 'notifications',
})
Le garde if existing: return existing rend l'opération idempotente : relancée (ré-installation, module ajouté après coup), elle réutilise le domaine déjà présent plutôt que d'en créer un doublon.
L'attribution en cascade. Le second comportement se déclenche à la création du tout premier domaine de la base : Odoo le propage non seulement à toutes les sociétés sans alias_domain_id, mais aussi à tous les mail.alias sans domaine — un alias de projet ou de helpdesk se retrouve ainsi soudainement rattaché au nouveau domaine. C'est l'effet le plus impactant en production, détaillé ci-dessous.
mail.alias.domain d'une base, la méthode create() attribue ce domaine à toutes les sociétés qui n'en ont pas encore (y compris archivées) et à tous les mail.alias sans domaine. C'est commode pour un démarrage mono-société, mais en environnement multi-société, créer un domaine « générique » en premier le propage partout : créer en priorité le domaine de chaque société dans le bon ordre, ou réassigner explicitement les alias_domain_id ensuite.
8. Configuration pratique et pièges
La création se fait par l'interface (Alias Domains → New) ou par script pour un déploiement reproductible :
# À exécuter dans odoo-bin shell (mode debug), ou depuis une migration de module.
domain = env['mail.alias.domain'].create({
'name': 'example.com', # validé dot-atom (latin non accentué)
'bounce_alias': 'bounce', # → bounce@example.com (Return-Path)
'catchall_alias': 'catchall', # → catchall@example.com (Reply-To)
'default_from': 'notifications',# local-part → notifications@example.com
})
# Rattacher une société à ce domaine
env.company.alias_domain_id = domain.id
# Vérifier les adresses dérivées
print(domain.bounce_email, domain.catchall_email, domain.default_from_email)
env.cr.commit()
Les valeurs saisies passent par _sanitize_configuration, qui normalise les local-parts (suppression des caractères invalides) avant l'enregistrement — inutile donc de pré-nettoyer manuellement. Les pièges récurrents en production :
- Domaine d'alias désaligné du
from_filter. Le piège n°1 : unmail.alias.domainnommant un domaine que le serveur sortant ne filtre pas. Les emails partent alors via le From de repli, expéditeur réécrit et DKIM cassé. - Nom de domaine avec accent ou caractère interdit. Bloqué par
_check_nameavec uneValidationError— utiliser le domaine en caractères latins simples. default_fromrevendiqué par un serveur personnel. Si unir.mail_serveravecowner_user_idfiltre déjà cette adresse, la sauvegarde lève uneUserError.- Collision bounce/catchall avec un alias métier. Choisir des local-parts dédiés (
bounce,catchall) et ne pas les réutiliser comme alias d'un modèle. - Domaine générique créé en premier en multi-société. Il se propage à toutes les sociétés sans domaine — ordonner les créations.
Pour vérifier la configuration effective, deux contrôles rapides. Côté Odoo, lire le domaine d'une société et ses adresses dérivées confirme l'alignement attendu :
# Quel mail.alias.domain pilote la société courante, et quelles adresses en découlent ?
company = env.company
print(company.alias_domain_id.name) # ex. example.com
print(company.bounce_email) # bounce@example.com
print(company.catchall_email) # catchall@example.com
print(company.default_from_email) # notifications@example.com (related)
Côté message reçu, l'inspection de l'en-tête source (« Afficher l'original » dans la plupart des webmails) doit montrer un Return-Path sur le bounce_email du domaine et un Reply-To sur le catchall — preuve que les trois adresses sont bien posées avant la remise au relais.
À retenir : mail.alias.domain est le pont entre l'identité d'envoi d'une société et la couche transport. Bien réglé — un domaine par société, aligné sur le from_filter du relais —, il garantit que les rebonds reviennent, que les réponses se routent, et que l'expéditeur reste celui qu'on attend. Mal réglé, il est la cause invisible des réécritures de From et des bounces perdus.
L'article suivant de la série quittera l'envoi pour la réception : la passerelle entrante fetchmail, qui relève les emails IMAP/POP3 et les route vers les bons enregistrements via les alias décrits ici.
Voir aussi dans ce parcours Infrastructure
Relais SMTP alternatifs — Mailjet, SES, Microsoft 365
Configurer le serveur sortant et son from_filter, auquel le default_from doit s'aligner.
Architecture mail Odoo — les 4 couches
HUB de la série : transport / message / template / mass et flux message_post → SMTP.
Série Tech-Email — Article 5/14 — Parcours Infrastructure emailing Odoo 19.
Articles complémentaires
DKIM, SPF, DMARC pour Odoo
Pourquoi un From de repli mal réglé casse l'alignement DKIM, et comment l'éviter.
Lire l'article →Actions serveur, cron et automations
ir.cron et base.automation — le moteur qui processe la queue mail.mail.
Source officielle : Odoo 19 — Email communication.