Se rendre au contenu

Notifications et Discuss en Odoo 19 — mail.notification, inbox et email

Série Tech-Email · Article 10/14 · Parcours Infrastructure
6 juin 2026 par
Notifications et Discuss en Odoo 19 — mail.notification, inbox et email
B.Mustapha
| Aucun commentaire pour l'instant

Jusqu'ici, la série a montré qui suit un document (les followers), comment se rend un message (le moteur de gabarit), et par quel relais il part (le SMTP). Reste à relier ces fils : quand un message est posté, comment Odoo décide-t-il qu'un collègue le verra apparaître instantanément dans son inbox Discuss tandis qu'un client le recevra par email ? Cette bascule, invisible mais centrale, est le travail du dispatch de notifications — et de son modèle de traçabilité, mail.notification.

Cet article décrit le mécanisme de notification en Odoo 19 : la méthode _notify_thread et ses trois canaux (inbox, email, push web), le calcul des destinataires, la règle qui choisit entre inbox et email, et le modèle mail.notification qui garde la trace de chaque envoi. Il fait converger l'article sur les followers (qui décide du public) et l'article sur le rendu (qui produit le contenu).

Prérequis lecteur. Une instance Odoo 19 en mode debug, et la lecture des articles sur mail.thread/followers et le rendu. On suppose acquis qu'un message porte un sous-type, et que les abonnés du document en sont le public cible.

1. Du message à la notification

Poster un message ne notifie personne en soi. message_post crée un mail.message, puis délègue la diffusion à une méthode dédiée : _notify_thread. C'est elle qui transforme un message en notifications concrètes pour chaque destinataire — une notification Discuss ici, un email là. La distinction est fondamentale : le message est unique, ses notifications sont multiples et personnalisées.

Ce découplage répond à une exigence simple : un même contenu doit atteindre des publics aux attentes différentes. Le responsable interne veut un signalement dans Odoo, sans encombrer sa boîte mail ; le client, qui n'ouvre pas Odoo, veut un email. Le dispatch est l'aiguillage qui sert chacun selon sa préférence, tout en gardant une trace de ce qui a été envoyé et de ce qui a échoué.

2. mail.notification : la trace par destinataire

Là où mail.message représente le message, mail.notification représente son acheminement vers un destinataire précis : une ligne par couple (message, destinataire). C'est le registre qui sait qui a été notifié, par quel canal, et avec quel résultat.

ChampRôle
mail_message_idLe message notifié (requis, supprimé en cascade)
res_partner_idLe destinataire
notification_typeinbox ou email — le canal
notification_statusreadyprocesssent (Délivré) → bounce / exception / canceled. (pending n'est utilisé que par le canal SMS ; l'email ne le traverse pas.)
is_read / read_dateLu ou non (pour le badge « à traiter » de l'inbox)
failure_type / failure_reasonType et détail d'un échec (bounce, spam, email invalide, SMTP…)
mail_mail_idLien optionnel vers le mail.mail (canal email)

Le modèle protège sa cohérence par des contraintes exprimées en syntaxe Odoo 19 — des attributs de classe, non l'ancien _sql_constraints :

# mail/models/mail_notification.py (Odoo 19)
# Une notification inbox exige un destinataire identifié :
_notification_partner_required = models.Constraint(
    "CHECK(notification_type != 'inbox' OR res_partner_id IS NOT NULL)",
    'Customer is required for inbox notification',
)
# Une notification email exige un partner, une adresse, ou un type d'échec :
_notification_partner_or_email_required = models.Constraint(
    "CHECK(notification_type != 'email' OR failure_type IS NOT NULL "
    "OR res_partner_id IS NOT NULL OR COALESCE(mail_email_address, '') != '')",
    'Customer or email is required for inbox / email notification',
)
# Un même message ne notifie un partenaire qu'UNE fois :
_unique_mail_message_id_res_partner_id_ = models.UniqueIndex(
    "(mail_message_id, res_partner_id) WHERE res_partner_id IS NOT NULL"
)

L'index d'unicité explique un détail du dispatch : avant de recréer des notifications, Odoo peut sauter les partenaires déjà notifiés (skip_existing), précisément pour ne pas violer cette contrainte. Le statut, lui, est le point d'entrée du débogage : une notification email restée en bounce ou exception raconte exactement pourquoi un destinataire n'a rien reçu.

3. Le dispatch : _notify_thread

La méthode _notify_thread(message, msg_vals) orchestre tout. Elle calcule d'abord les destinataires, puis — si l'envoi n'est pas programmé pour plus tard — déclenche trois canaux de notification en parallèle.

# mail/models/mail_thread.py — _notify_thread (cœur, extrait)
recipients_data = self._notify_get_recipients(message, msg_vals=msg_vals, **kwargs)
if not recipients_data:
    return recipients_data

scheduled_date = self._is_notification_scheduled(kwargs.pop('scheduled_date', None))
if scheduled_date:
    # envoi différé : on met en file (mail.message.schedule) au lieu de notifier
    self.env['mail.message.schedule'].sudo().create({...})
else:
    # sinon, trois canaux en parallèle :
    self._notify_thread_by_inbox(message, recipients_data, msg_vals=msg_vals, **kwargs)
    self._notify_thread_by_email(message, recipients_data, msg_vals=msg_vals, **kwargs)
    self._notify_thread_by_web_push(message, recipients_data, msg_vals=msg_vals, **kwargs)

Les trois canaux ne s'excluent pas : un message peut produire simultanément des notifications inbox pour les uns et des emails pour les autres, selon ce que chaque destinataire attend. Le canal web push ajoute, pour les navigateurs et l'application mobile abonnés, une notification système. La programmation différée, elle, passe par un modèle intermédiaire (mail.message.schedule) qui retient la notification jusqu'à la date voulue.

4. Qui est notifié

Le calcul des destinataires, dans _notify_get_recipients, part des followers du document et des partenaires explicitement mentionnés dans le message, puis applique plusieurs filtres. Le premier est hérité du système d'abonnés : le sous-type du message croisé aux sous-types suivis par chaque abonné — et, surtout, le cloisonnement internal qui écarte les clients des sous-types internes.

Deux autres filtres méritent d'être connus, car ils expliquent des absences de notification souvent jugées surprenantes :

  • L'auteur est exclu par défaut. On ne se notifie pas de son propre message (notify_author=False). L'option notify_author_mention réautorise l'auteur s'il figure parmi les destinataires directs.
  • Anti-double-email. Un destinataire déjà adressé dans l'email entrant qui a déclenché le message (en-têtes To/Cc) n'est pas renotifié par email — il a déjà reçu le contenu.

Le résultat est une liste structurée : pour chaque destinataire, son identité, sa langue, son statut (client, portail, interne) et — décisif — son canal de notification (notif). C'est ce dernier qui aiguille la suite.

5. La règle inbox ou email

Comment Odoo décide-t-il qu'un destinataire sera notifié dans Discuss plutôt que par email ? La règle tient en une expression, au cœur de la requête qui calcule les destinataires :

# mail/models/mail_followers.py — _get_recipient_data (extrait SQL)
# notif = type de notification du destinataire
COALESCE(sub_user.notification_type, 'email') as notif

Autrement dit :

  • Le destinataire a un compte utilisateur → on lit sa préférence res.users.notification_type : inbox (« Dans Odoo ») ou email (« Par email »).
  • Le destinataire n'a pas de compte (un client, un contact sans accès) → COALESCE retombe sur email : il n'a pas d'inbox Odoo, donc il sera toujours notifié par email.

La préférence d'un utilisateur n'est pas un champ libre : elle est pilotée par l'appartenance au groupe mail.group_mail_notification_type_inbox. Membre du groupe → inbox ; non-membre → email. Et un point de sécurité important : un utilisateur portail ou externe est forcé en email — un partenaire partagé ne reçoit jamais de notification dans l'inbox interne. C'est le réglage que l'utilisateur retrouve dans ses préférences sous « Notifications : par email / dans Odoo ».

Conséquence pratique. Un collègue qui « ne reçoit pas les emails » de notification n'a probablement pas un problème de SMTP : il est simplement en mode inbox, et ses notifications l'attendent dans Discuss. À l'inverse, un client est toujours en email — aucun réglage ne lui donnera une inbox Odoo.

6. Le canal Discuss : _notify_thread_by_inbox

Pour les destinataires en mode inbox, la notification se joue en deux gestes. D'abord, Odoo crée les enregistrements mail.notification correspondants, marqués notification_type='inbox' et notification_status='sent'. Ensuite — et c'est ce qui donne l'effet « temps réel » — il pousse le message sur le bus de chaque utilisateur concerné.

# _notify_thread_by_inbox (extrait) — création + push temps réel
inbox_pids_uids = sorted(
    [(r["id"], r["uid"]) for r in recipients_data if r["id"] and r["notif"] == "inbox"]
)
# 1) trace : une mail.notification 'inbox' par destinataire
self.env["mail.notification"].sudo().create([
    {"author_id": message.author_id.id, "mail_message_id": message.id,
     "notification_status": "sent", "notification_type": "inbox",
     "res_partner_id": pid_uid[0]} for pid_uid in inbox_pids_uids
])
# 2) temps réel : push sur le bus de chaque utilisateur → inbox Discuss
for user in users:
    # store_data est construit par un objet Store propre à CHAQUE user
    # (isolation des données : chacun ne reçoit que ce qu'il a le droit de voir)
    store = Store(bus_channel=user).add(message.with_user(user), msg_vals=msg_vals, ...)
    user._bus_send("mail.message/inbox", {"message_id": message.id,
                                          "store_data": store.get_result()})

Le canal mail.message/inbox du bus est ce qui fait apparaître le message dans l'inbox Discuss sans rechargement de page : l'utilisateur connecté voit le compteur s'incrémenter en direct. Les enregistrements mail.notification, eux, persistent : ils alimentent la liste « À traiter » et leur champ is_read bascule lorsque l'utilisateur marque la notification comme lue.

7. Le canal email : _notify_thread_by_email

Pour les destinataires en mode email, le dispatch rejoint les couches déjà décrites dans la série. Le corps est rendu par le moteur de gabarit, encapsulé dans un layout de notification, puis transformé en un ou plusieurs mail.mail. Chacun reçoit sa propre mail.notification de type email, dont le notification_status suivra le sort de l'envoi : sent en cas de succès, bounce ou exception en cas d'échec.

C'est ici que la boucle se referme avec le début de la série : le mail.mail produit est confié au serveur SMTP — le relais transactionnel configuré —, qui l'achemine vers la boîte du destinataire. Le statut de la mail.notification devient alors le journal de livraison consultable : un parc de notifications bounce sur un même domaine signale un problème d'authentification ou de réputation, bien avant que les destinataires ne se plaignent.

8. message_notify et pièges

À côté du flux standard, Odoo offre message_notify : notifier des partenaires avec un message qui ne s'affiche pas dans le fil du Chatter. Le message est bien créé et persisté en base — de type user_notification —, mais il est filtré de l'affichage du document ; il emprunte le même pipeline inbox/email que les autres notifications. C'est le mécanisme des alertes ponctuelles — une assignation, un rappel — quand on veut prévenir quelqu'un sans « polluer » l'historique visible du document.

Les pièges récurrents autour des notifications :

  • Confondre message et notification. mail.message est le contenu ; mail.notification est sa livraison par destinataire. Déboguer un email manquant, c'est lire la mail.notification, pas le message.
  • Attendre un email pour un utilisateur en mode inbox. Un collègue en « Dans Odoo » ne reçoit aucun email : ses notifications sont dans Discuss. Ce n'est pas un bug SMTP.
  • Espérer une inbox pour un client. Un partenaire portail ou sans compte est toujours en email. Aucun réglage ne lui ouvre l'inbox interne.
  • Ignorer le statut de livraison. Une notification bounce/exception porte le failure_type et le failure_reason — le premier réflexe de diagnostic, pas les logs serveur.
  • Oublier le cloisonnement internal. Une note interne ne notifie jamais un client, même follower : le sous-type interne l'exclut du calcul des destinataires.

À retenir : la notification est l'aiguillage final de tout le système mail d'Odoo. _notify_thread calcule le public à partir des followers et des sous-types, choisit le canal selon la préférence de chaque destinataire — inbox Discuss en temps réel pour les utilisateurs, email pour les clients —, et consigne chaque envoi dans mail.notification. Comprendre cet aiguillage, c'est savoir pourquoi tel message a été reçu, par qui, par quel canal, et — quand quelque chose cloche — où regarder.

L'article suivant changera d'échelle : du message unitaire à la diffusion de masse. Les campagnes mailing.mailing réutilisent le rendu et le transport vus ici, mais à grande échelle, avec leurs propres mécanismes de file, de segmentation et de suivi.

Voir aussi dans ce parcours Infrastructure

mail.thread — followers, sous-types et notifications

Qui suit le document et quels sous-types déterminent le public notifié.

Lire l'article →
mail.render.mixin — placeholders et moteurs de rendu

Le moteur qui produit le corps de l'email envoyé sur le canal email.

Lire l'article →

Série Tech-Email — Article 10/14 — Parcours Infrastructure emailing Odoo 19.

Articles complémentaires

Architecture mail Odoo — les 4 couches

Le HUB de la série : la place des notifications dans le flux complet.

Lire l'article →
Configurer Brevo SMTP — ir.mail_server & from_filter

Le transport qui achemine les notifications du canal email.

Lire l'article →

Source officielle : Odoo 19 — Discuss.

Se connecter pour laisser un commentaire.
mail.render.mixin en Odoo 19 — placeholders, moteurs de rendu et sécurité
Série Tech-Email · Article 9/14 · Parcours Infrastructure