EH HR Notify Engine
One send() call across in-app, email and SMS, with per-user channel preferences and built-in dedupe, so feature modules never wire delivery by hand.
Why this module
EH HR Notify Engine
Channels behind a single send()
Feature modules pass a template code, a recipient and a payload to one service. The engine decides which channels to use and dispatches them. No module wires email, SMS or in-app delivery by hand, so notification behavior stays consistent across the whole platform.
Each person picks their channels
A per-user preference says how someone wants a given notification delivered. A preference for a specific template wins over a wildcard that covers all templates, which falls back to the template default and finally to in-app, so there is always a sane delivery path.
Duplicates suppressed in a 60s window
When a caller passes a dedupe key, the engine drops a repeat send to the same user, template and channel inside a 60 second window. A burst of identical events does not turn into a burst of identical messages.
Day in the life
A leave request is approved
The leave module calls send('leave.approved', employee, payload). The engine looks up the employee's preference for that template, finds they chose email, renders the subject and body against the payload and queues one mail.mail. A colleague who left their preference at the default gets the same notice in their Odoo inbox instead. If the approval fires twice from a double click, the second send carries the same dedupe key and is dropped. Neither user touches a settings screen, and the leave module never names a channel.
Edge cases
The cases most modules quietly ignore.
In the shipped code today, each one a place where a cheaper module silently does the wrong thing.
With a dedupe key supplied, a repeat send to the same user, template and channel inside 60 seconds is skipped. The cache is capped and trimmed of stale keys past 5000 entries, so it cannot grow without bound.
Each channel dispatch is wrapped in its own try and except. If email fails for one recipient, the exception is logged and the loop continues to the next channel and the next recipient rather than aborting the whole send.
Resolution is explicit, not alphabetical luck: a preference for the exact template code is searched first, then the wildcard, then the template default, then a hard in-app fallback. A template code sorting below the wildcard character can no longer let the wildcard win.
The model ACL grants every internal user create and write on preferences, so a record rule scopes each user to their own rows. HR admins see all rows for support. Tests assert a user cannot even read another user's preference, raising AccessError.
SMS dispatches only when the sms module is installed and the user has a mobile or phone number, otherwise it returns quietly. Push and webhook look up a registered backend service and skip silently when none is present, so an absent channel never raises.
An unknown template code raises a clear UserError naming the code, rather than failing deep inside a dispatch. Recipients can be a single user or a list, and an empty payload is handled safely.
What is inside
Built to do the job, end to end.
- eh.hr.notification.template. Unique template code, translatable name, subject and HTML body, optional SMS body and push title and body, and a default channel list. Bodies are filled with payload values using lightweight placeholder substitution.
- eh.hr.notification.preference. Per-user, per-template channel choice as an ordered comma list, indexed on user and template code, unique per pair. Quiet-hours fields are stored on the model. A record rule keeps each user's rows private.
- Notification engine service. A registered service, not an ORM model, exposing one send() method. It resolves channels, applies dedupe, renders templates and dispatches to in-app, email, SMS, push and webhook handlers with per-channel error isolation.
- Dependencies and security. Builds on eh_hr_core and Odoo mail. Ships ir.model.access entries for HR admin and officer on templates plus self-service on preferences, and the record rules that isolate them.
Honest about the edges
What this does not do, so nothing surprises you.
- This module ships the engine and its templates and preferences, not a notification configuration UI. Templates and preferences are managed as data records.
- In-app, email and SMS dispatch out of the box; SMS needs Odoo's sms module installed and a number on the user. Push and webhook are pluggable slots that stay inactive until a backend service is registered by another module.
- Duplicate suppression only applies when the caller passes a dedupe key, and it relies on an in-process cache scoped to the running worker rather than a shared store.
- Quiet-hours fields exist on the preference model but are stored only; the engine does not currently defer or hold sends based on them.
- Template bodies use simple placeholder substitution against the payload, not full QWeb expression rendering.
- There is no digest or batching feature; each send dispatches immediately per resolved channel.
Odoo 16 HR notifications, notification engine Odoo, multi-channel notifications Odoo, per-user notification preferences, in-app email SMS notifications Odoo, notification templates Odoo HR, duplicate notification suppression, Odoo Community HR platform, self-hosted Odoo notifications, HR notification service Odoo 16
Please log in to comment on this module