Livefeed and Notifications¶
Part of the user manual. Related: VISIBILITY · PERMISSIONS · ROLES · TENANCY · WORKFLOWS · EDMS · PROJECTS · CONTACTS · ACTIONS. Notifications fire independent of VISIBILITY filtering — this is the source of the "I get emails but no rows" confusion. See VISIBILITY's troubleshooting flow.
REBIM tells users about activity through four surfaces: two sidebar panels, bell icons on table rows, and email. This page covers what each surface shows, when notifications fire on the server side, how @mentions resolve, how subscriptions opt-in, and the admin controls that affect notification volume.
TL;DR¶
- Activity panel (left sidebar) — general notifications: document changes, workflow event activity, transmittal sends, etc.
- Mention panel (right sidebar) — @mention notifications only.
- Bell icons (red
fa-bellbadge on individual rows) — per-row unread markers. Click through to clear. - Email — the same notifications, queued via
email_user(user, ...). Routes toUser.email, NOTcontact.email. See CONTACTS § The email caveat. - Notifications fire independent of VISIBILITY. A user can be notified about a record they can't see in their table — that's the classic "I get emails but no rows" failure mode, almost always a missing config tenant.
- Admin controls:
DISABLE_ADMIN_CC(global pref),DISABLE_EDMS_NOTIFICATIONS(setting), per-transitionnotify_*toggles (see ROLES), per-document subscribe/unsubscribe (see EDMS § Subscriptions).
Where notifications surface¶
Every notification lands in two places at once: a row in
LivefeedNotification (in-app surface) plus a queued email (out-of-app
surface, when the recipient has a valid email + is_active).
In-app: the LivefeedNotification row¶
| Field | Purpose |
|---|---|
owner |
The recipient — the user the notification is FOR |
message_txt |
The displayed text (or a sentinel — BELLICON / MENTION — that drives bell icons + mention panel) |
message_details |
Long-form text used in the mention panel rendering |
module_ref |
Which module the notification points at: workflow, edms, etc. |
module_key |
The PK of the underlying record (workflow event PK, document PK) |
project_key |
The PK of the parent project — used for filtering |
is_read |
Toggles to True when the user clicks through or marks read |
is_discussion_notification |
Distinguishes discussion-feed rows (bellicons + mentions) from general activity-feed rows |
custom_ref |
Optional cross-reference identifier |
is_compliance |
Marks compliance-related notifications for filtered views |
tenants |
Tenancy (TENANCY) |
The frontend fires two fetches of the notifications endpoint:
GET /apps/livefeed/notifications/(no?discussions=) — returns rows whereis_discussion_notification=False. These populate the activity panel.GET /apps/livefeed/notifications/?discussions=true— returns rows whereis_discussion_notification=True. The frontend then splits them client-side:message_txt == "BELLICON"→ drives the per-row bell icons viaapply_bellicons.message_txt == "MENTION"→ populates the mention panel.
Out-of-app: queued email¶
Most notifications also fire an email. The email path is
email_user(recipient_user, subject, message, override_from=...,
override_template=...) in core/utils.py. Behaviour:
- Recipient is the
User(Django auth user) — emails go touser.email. - Skipped if the user has no email, an invalid email, or
is_active=False. - The email is queued (
QueuedEmailmodel) and sent asynchronously byemail_queue_worker— so notifications never block the request that triggered them. - The
from_emaildefaults to a global pref (email__originating_address) but signal handlers usually passoverride_from=project.email_fromso emails come from the project-branded address. Mention emails additionally override toemail__mention_addressif set.
Override: when the calling signal handler passes
contact=person.contact to email_user() AND contact.email is
set + valid, that overrides user.email for delivery (per-project
routing). All in-tree signal call sites do this; ad-hoc callers
without a ProjectPeople.contact in scope (e.g. the admin-CC
fan-out) fall back to user.email as before. See CONTACTS § The
email caveat for the resolution
rules.
When notifications fire — server-side rules¶
Three signal handlers in the codebase fan notifications out. They fire when the relevant model changes.
Document changes (EDMS)¶
Trigger: a document's status changes, OR a new revision lands, OR the document is created.
Who gets notified:
- Project members whose disciplines include the document's
discipline(or all project members for status-history changes — depends on signal path). - Project members with the
role_leader=Trueflag, when the change is workflow-related. - Project admins (
project_admin=Trueon their ProjectPeople row). - Document subscribers (users explicitly subscribed to this doc — see EDMS § Subscriptions).
- Site admins, if
notifications__disable_admin_ccglobal pref is False (default True — admin CC is OFF by default for many installs).
Toggleable via DISABLE_EDMS_NOTIFICATIONS setting (turns all
EDMS document notifications off install-wide).
Workflow event transitions¶
Trigger: a workflow event's workflow_transition advances (the
event moves from one status to another via a transition).
Who gets notified — controlled per-transition by six toggles (see ROLES § Notifications and roles):
notify_role_members(default True) → users matching the transition'stransition_roles.notify_role_leaders(default True) → users matchingtransition_rolesANDrole_leader=True.notify_project_admins(default True) → allproject_admin=Trueusers in the project.notify_readonly_members(default True) → users matchingreadonly_roles.notify_commentonly_members(default True) → users matchingcommentonly_roles.notify_all_project_members(default False) → everyone in the project's People list, whatever their roles.
The transition's any_role flag does not affect who is notified —
it only controls who may perform the transition.
Plus site admins on the same notifications__disable_admin_cc gate.
Transmittals¶
Trigger: a transmittal is sent, re-transmitted, or recalled.
Who gets notified:
- All
individualslisted on the transmittal. - All members of
groupslisted on the transmittal (groups are expanded into individuals at signal time). - The transmittal owner (gets a record copy).
Recalling sends a separate notification ("this transmittal has been recalled") to the same audience.
@-mentions¶
Trigger: a workflow event's details field contains
@username tokens AND the event is a discussion / Comment-type
event.
Resolution:
- Extract
@usernametokens from the event's details text. - Filter
ProjectPeople.objects.filter(project=event.project, contact__site_login__username__in=usernames)— only project members with matching site_logins resolve. - Skip any tokens that don't resolve to a project member (silently dropped).
Effect for each resolved mention:
- Create a
LivefeedNotificationwithmessage_txt="MENTION",is_discussion_notification=True,message_details=<event summary>. This populates the mention panel. - Send an email via
email_user()withoverride_fromset toemail__mention_addressglobal pref (orproject.email_fromif not set).
The mention notification appears in the recipient's mention panel specifically, not the activity panel.
Bellicons (per-row visual marker)¶
Trigger: various — typically when a workflow event's discussion gets a new comment, OR another user takes a notable action on a doc / event the user is connected to.
Effect: create a LivefeedNotification with
message_txt="BELLICON", is_discussion_notification=True,
module_ref="workflow" / "edms", module_key=<event or doc pk>.
The frontend reads these on the discussion-feed fetch and renders a
red fa-bell badge on the matching row in the relevant DataTable.
Clicking through the row clears the bellicon (via
/delete-discussion-notifications/<event_pk>/?module_ref=...).
The two sidebar panels¶
The dashboard layout has two notification sidebars.
Activity panel (#activityPanel)¶
Shows general notifications — non-discussion rows from LivefeedNotification. Examples:
- "Document XYZ has changed status to Approved"
- "You have been added to project Q"
- "Workflow event #42 has advanced to Review"
Each entry shows:
- Module icon (
fa-cubefor EDMS,fa-check-square-ofor workflow,fa-pencil-square-ofor other). - Timestamp.
- A red
fa-bellbadge if unread. - Free-text message.
- A close button (
fa-window-close) to delete the notification.
Clicking an unread entry marks it read (the bell badge clears). The panel itself fades in/out briefly when new unread items arrive.
Awareness label (BYOP-era addition): when more notifications exist on the server than fit in the response (default cap 100), a small italic line appears at the top of the panel:
N shown · M total
Auto-hides when nothing is truncated.
Mention panel (#mentionPanel)¶
Shows @-mention notifications only — the MENTION-typed subset of discussion notifications.
Each entry shows:
- Module icon.
- Timestamp.
- The event's project + ID.
- The first ~80 chars of the event details (the comment that mentioned you).
- A "Click for Details" link that jumps to the discussion thread.
Awareness label (BYOP-era deferred-follow-up): when the discussion feed is truncated, an italic line appears:
older mentions may exist past this view
Less precise than the activity panel's exact count — the discussion
feed mixes BELLICON + MENTION rows so we can detect truncation but
not the exact mention-only count. The lightweight honest label
avoids a second HTTP fetch + custom response shaping. See
BYOP-PLAN.md Phase 3a in devdocs/ for the rationale.
If you want an exact mention-only count, the deferred backend
enhancement is a ?mentions_only=true filter on the livefeed endpoint.
Not implemented yet.
Bell icons on individual table rows¶
When a row in a DataTable has unread discussion activity (someone
commented, the document changed status, etc.), a red fa-bell badge
appears next to the row's identifier.
The badge is keyed by (module_ref, module_key). For workflow events
the key is the event PK; for documents it's the document PK. A user
can have multiple bell icons on multiple rows.
How they clear¶
- Click through to the discussion thread. Opening the discussion
for an event/document with a bellicon hits
/delete-discussion-notifications/<event_pk>/?module_ref=...which removes the user's BELLICON rows for that target. Bell disappears on next data load. - Delete via close button in the activity panel (rare — bell icons are usually targeted, not bulk-deleted).
- Mark read flows do NOT automatically clear bell icons — the bell is tied to the BELLICON LivefeedNotification row, not the is_read flag on the general notification.
Header badges¶
The dashboard header shows two count badges:
- Notification count — total unread general-activity-panel rows
(
unread_notification_countinapp_dashboard.js). - User mention count — total mentions in
all_user_mentions(rendered in two places:#user-mention-unread-count-topfor the top header,#user-mention-unread-countfor sidebar).
Both update when new fetches land. Click the header icons to open the respective panels.
Document subscriptions¶
A user can subscribe to a specific document. Subscribers are notified when the document changes regardless of whether their role would normally route the notification.
How to subscribe: document kebab menu → Subscribe (toggles to Unsubscribe for the same user). See ACTIONS § EDMS Documents and EDMS § Subscriptions.
What subscribers get:
- A
LivefeedNotificationin the activity panel. - An email (to
User.email).
Visibility still applies. A subscriber who loses project access (removed from ProjectPeople, tenancy change) stops getting the notifications anyway, because the document is no longer visible to them. Subscriptions are an opt-in layered on top, not an override.
Discussion threads¶
Both workflow events and transmittals have discussion threads — chat-style conversation threads attached to the record.
On workflow events¶
Open the event → kebab → View Discussion (or click through from
the activity / mention panel). The thread is a sequence of Comment-type
child events linked via content_parent. Anyone with visibility on
the parent event can participate.
Commenting:
- Type the message.
- @mention users with
@usernamesyntax. Each mention fires the resolution chain described above. - Optionally attach a file (becomes the comment event's
secondary_attachment).
The parent event tracks show_discussion_paperclip (boolean) —
when True, a paperclip icon appears on the parent event's row in the
workflow table, indicating "discussion contains an attachment." This
is set by the comment-creation signal handler when an attachment
lands.
On transmittals¶
Same shape — open the transmittal → discussion thread. Anyone who can see the transmittal can participate. Useful for back-and-forth about a transmittal's contents.
Comment-only role users¶
If a user matches a workflow transition's commentonly_roles (see
ROLES), the kebab on the event includes two extra
shortcuts:
- Submit Comment Sheet — opens the discussion + pre-fills "Comment sheet attached".
- Submit No Comment — opens the discussion + pre-fills "No Comment".
These reduce the "I was asked to comment but have nothing to add" case to one click.
Admin controls¶
DISABLE_EDMS_NOTIFICATIONS (settings.py)¶
Setting in server/settings.py. When True, all EDMS document
notifications are skipped install-wide. Use sparingly — turning this
on silences subscribers + role-based notifications + admin CC. Mostly
a debugging / migration safety switch.
notifications__disable_admin_cc (global pref)¶
Stored in dynamic_preferences. When True (the typical install
default), site admins do NOT get CC'd on tenant notifications.
When False, every notification a non-admin user receives ALSO goes
to all is_superuser=True users. Generates substantial volume on
busy installs — use only for very small / debug installs.
Read via core.utils.get_global_pref("notifications__disable_admin_cc", True).
Per-transition notify_* toggles¶
The most granular control. Each WorkflowTransition carries six
booleans (see ROLES § Notifications and roles):
notify_role_membersnotify_role_leadersnotify_project_adminsnotify_readonly_membersnotify_commentonly_membersnotify_all_project_members(default off — emails everyone on the project)
Untick a toggle to silence that role category for THIS transition specifically. Visibility is unaffected — the user still SEES the event, they just don't get pinged about it.
email__mention_address (global pref)¶
Stored in dynamic_preferences. Overrides the From: address for
mention emails specifically (separate from project.email_from
which covers general project emails). Used when mentions should
route through a different mailbox / branded sender than other
notifications.
mqtt__transport (global pref)¶
Stored in dynamic_preferences. Controls how live notifications
reach the browser in real time — not whether they fire (that's the
rules above), just the transport.
Normally the browser holds a WebSocket straight to the message broker (paho/MQTT). On installs where WebSockets are blocked (some corporate proxies / firewalls), that connection silently fails and the bell never updates without a manual refresh. This pref provides a Server-Sent Events (SSE) fallback over plain HTTP.
| Value | Behaviour |
|---|---|
mqtt (default) |
WebSocket only — exactly the original behaviour. No fallback. |
auto |
Try the WebSocket; if it fails, fall back to SSE automatically. |
sse |
Skip the WebSocket entirely and use SSE only — for installs where WebSockets are known to be blocked. |
SSE relays the same broker triggers via the server, so the live behaviour is identical — it's a real-time push, not polling, so there's no added latency and no pointless refreshes. Only the transport differs.
Leave it on mqtt unless an install actually has WebSocket problems.
The SSE path opens one streaming connection per signed-in tab and is
gated both ends (a default mqtt install never starts the server-side
stream). Changing the value takes effect on the user's next full
page load — the active transport is fixed when the page renders.
Read via core.utils.get_global_pref("mqtt__transport", "mqtt").
Pagination cap¶
The notifications endpoint uses LivefeedNotificationPagination
(default 100, max 500). Tune via BYOP_PAGINATION['livefeed_notification']
in settings — but bumping the default doesn't increase the on-page
display, it just changes the truncation threshold + the awareness
label trigger. See devdocs/BYOP-PLAN.md Phase 6.
Practical operations¶
Mark a notification read¶
Click the activity panel entry. The fa-bell badge clears. The
underlying LivefeedNotification.is_read is set to True.
Delete all read notifications¶
/delete-read-notifications/ endpoint, called via the panel's "clear
read" action. Removes is_read=True rows for the current user.
Subscribe to a document¶
Document kebab → Subscribe. Toggle to unsubscribe via the same kebab.
@mention a project member in a discussion¶
Type @username in the event details / discussion comment. The
mention resolves only if:
- The username corresponds to an
auth.Userrow, AND - That User has a linked
DirectoryContact(viasite_login), AND - That Contact has a
ProjectPeoplerow on the current project.
Unmatched mentions are silently dropped.
Silence notifications for a role on a specific transition¶
Admin → Workflow → Workflow Transitions → (pick) → untick the
relevant notify_* flag → save. Effective on next transition fire
for that transition.
Disable admin CC for a tenant¶
Admin → dynamic_preferences → set
notifications__disable_admin_cc=True. Effective on next signal
fire.
Troubleshooting¶
| Symptom | Likely cause | First check |
|---|---|---|
| Get emails about workflow events but no row in the table | Tenancy: missing config tenant. Email goes via ProjectPeople-linked user; frontend can't render rows because workflow lookups fail. |
TENANCY — assign config tenant to the user |
| Activity panel empty after a recent change | Frontend cached the previous fetch. Try hard refresh OR check the notification was created (admin → LivefeedNotification list) | LivefeedNotification rows for this user |
| @mention sent but recipient didn't get notified | The mentioned @username didn't resolve. User must exist + have a linked Contact + a ProjectPeople row on this project |
The recipient's User → Contact → ProjectPeople chain |
| Subscriber not notified on document change | Subscriber lost project access (tenancy change, removed from ProjectPeople) OR User has no email / is_active=False | Subscriber's project membership + email validity |
| Bell icon persists after viewing the related event | Bell tied to BELLICON LivefeedNotification, only cleared on /delete-discussion-notifications/ — viewing without that endpoint fire (e.g. via direct URL) won't clear it |
Hit the discussion thread via the kebab; it fires the delete endpoint |
| Too many admin CC emails | notifications__disable_admin_cc=False. Default is True; an install changed it explicitly |
dynamic_preferences for this setting |
| Mention emails coming from the wrong address | email__mention_address set in global prefs takes precedence over project.email_from for mentions |
Global pref value |
| Discussion paperclip missing on a row with attachments | show_discussion_paperclip not set on the parent event. The signal sets it when a comment-with-attachment lands; pre-existing data may need backfill |
Parent event's show_discussion_paperclip flag |
| "Older mentions may exist" shows but I can't find more | The mention panel is truncated past the server cap. Increase BYOP_PAGINATION['livefeed_notification']['default'] (currently 100) — see devdocs/BYOP-PLAN.md Phase 6 |
Settings tuning |
| Activity panel awareness label shows "N shown · M total" with M very large | Many unread + read notifications accumulated. Use the "clear read" action OR adjust retention via a prune_livefeed_notifications management command (if/when implemented — pending per BYOP-PLAN) |
Existing LivefeedNotification row count for this user |
| Email never arrives despite the in-app notification appearing | Either the user's User.email is invalid/missing, the user is_active=False, or the QueuedEmail row failed to send | QueuedEmail admin list — check status + last_error |
For developers¶
This page is for users and admins. Implementation pointers:
- Models:
apps.livefeed.models.LivefeedNotification(the row),core.models.QueuedEmail+QueuedEmailAttachment(outgoing email). - Signal handlers:
apps.workflow.signals.py— workflow event transitions, transmittals, @-mention resolution + dispatch.apps.edms.signals.py— document changes, subscriber notifications, mention resolution within document discussions.apps.workflow.signals.create_chat_mqtt_refresh— MQTT-side refresh for real-time updates (out of scope for this page).- Live transport / SSE fallback (
mqtt__transport): - Frontend:
apps/livefeed/static/livefeed/js/app_livefeed.jsconnect_user_notifications— branches on themqtt_transportJS global;start_sse()is the EventSource fallback. Both the WS and SSE paths route through the sameon_live_messagehandler. - Backend:
apps/livefeed/sse.py— a raw ASGI handler (mounted inserver/asgi.pyat/sse/notifications/) that subscribes to the same broker topics and relays them as SSE events. Raw ASGI because it must stream on Django < 4.2; runs on the event loop, so all DB/pref reads go throughsync_to_async(see the module docstring). - Email queue:
core.utils.email_user(user, ...)andemail_queue_worker(the background worker that drains QueuedEmail). - Frontend rendering:
- Activity panel:
app_dashboard.js: render_notifications(#activityPanel). - Mention panel:
app_dashboard.js: render_mentions_sidebar(#mentionPanel). - Bell icons:
app_dashboard.js: apply_bellicons+ the per-row decorators inapply_bellicon_notifications. - The two fetches:
notification_url(no?discussions=) anddiscussion_notification_url(?discussions=true). Same endpoint, same pagination class. - Awareness labels: activity panel — set in
render_notificationsfromdata.countvsdata.results.length. Mention panel — set inrender_bellicon_notificationsbased on the discussion-feed truncation (combined count, less precise). - Pagination class:
apps.livefeed.pagination.LivefeedNotificationPagination. Settings viaBYOP_PAGINATION['livefeed_notification'].
See devdocs/ for codebase-level material and devdocs/BYOP-PLAN.md
for the BYOP-era awareness-label rollout history.