EH HR Notify Engine
One send() across in-app, email, SMS, push and webhook, so feature modules never wire channels 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. The engine resolves the user's channels, suppresses duplicates, renders the template and dispatches. No module wires email, SMS or push by hand.
Delivery preferences that belong to the user
Each user sets which channels they want, per template or with a wildcard fallback. A specific preference wins over the wildcard, which wins over the template default. Preferences are private: users see only their own, HR admins see all for support.
One channel failing does not stop the rest
Each channel dispatches in its own guarded step. If email fails, in-app still goes out. Duplicate sends inside a short window are suppressed by a dedupe key so a retried call does not notify twice.
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 preferences, finds they want in-app and email for this template, renders the subject and body against the payload, and posts to their inbox and sends the mail. A duplicate call from a retried job within the minute is dropped on its dedupe key. If the employee had no preference, the template's own default channel is used.
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.
A per-template preference beats the wildcard '*', which beats the template default. The lookup is explicit rather than relying on string sort order, so a template code sorting below '*' still resolves correctly.
A send carrying a dedupe key is suppressed if the same (user, template, channel, key) was dispatched within the 60 second window. The in-memory cache self-trims past 5000 entries so it cannot grow without bound.
Each channel is dispatched in its own try block. A failure on one channel is logged and the remaining channels still fire, so a dead SMS adapter never blocks the in-app and email path.
The model ACL grants every internal user CRUD, so a record rule restricts reads and writes to the user's own rows. Cross-user reads raise AccessError; HR admins are granted a see-all rule for support. Both paths are covered by tests.
SMS dispatches only when the core sms module is present and the user has a mobile or phone number. Push and webhook are soft adapters located at runtime and skipped cleanly when no backend is registered, so the engine never hard-depends on them.
A single send() accepts one recipient or a list, resolving channels and applying duplicate suppression independently for each user in the batch.
What is inside
Built to do the job, end to end.
- Models this module adds. eh.hr.notification.template (code, name, subject, body, sms and push bodies, default channels) and eh.hr.notification.preference (per-user channels per template code, with a wildcard option). Both carry unique constraints written to hold across Odoo 16 to 19.
- The notification service. A registered eh.hr.notification.engine service exposing send(template_code, recipient, payload, channels, dedupe_key). It resolves channels, suppresses duplicates, renders placeholders against the payload and dispatches. This is the API feature modules call.
- Channels in the box. in_app posts a notification to the user's inbox via mail.message; email sends through mail.mail. SMS delegates to the core sms module when installed. Push and webhook are soft adapter hooks: present when a backend module registers one, skipped otherwise.
- Security. An access CSV plus two record rules: HR admins manage templates and see all preferences, HR officers read templates, and every internal user manages only their own preferences. No views or wizards are shipped; the engine is driven by other modules and the preference rows.
Honest about the edges
What this does not do, so nothing surprises you.
- This is an engine, not an end-user app. It ships no menus, views or wizards. Notification templates and preferences are created by the feature modules that call it, or through the ORM, not through a bundled UI on this module.
- SMS dispatch requires the core sms module and a usable number on the user; without them the SMS channel is skipped silently.
- Push and webhook are adapter hooks only. This module locates a registered backend and delegates to it; it does not ship an FCM, APNs or signed-webhook backend itself, so those channels are inert until a backend module is installed.
- Duplicate suppression is a 60 second per-process in-memory window keyed on a caller-supplied dedupe key. It is not a persistent rate limiter, not a scheduled digest, and not shared across worker processes.
- The preference model carries quiet-hours fields, but the engine does not yet enforce quiet hours or defer delivery; they are reserved for a future release.
- Template rendering is lightweight {placeholder} substitution against the payload dictionary, not full QWeb. Unknown placeholders leave the source text unchanged rather than raising.
odoo 17 hr notifications, odoo notification engine, channel abstract notifications odoo, per user notification preferences, in app email sms push odoo, odoo webhook notification, notification templates odoo, hr platform notification service, duplicate suppression notifications, odoo community hr notify, self hosted odoo notifications, erp heritage hr platform
Please log in to comment on this module