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-bell badge on individual rows) — per-row unread markers. Click through to clear.
  • Email — the same notifications, queued via email_user(user, ...). Routes to User.email, NOT contact.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-transition notify_* 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:

  1. GET /apps/livefeed/notifications/ (no ?discussions=) — returns rows where is_discussion_notification=False. These populate the activity panel.
  2. GET /apps/livefeed/notifications/?discussions=true — returns rows where is_discussion_notification=True. The frontend then splits them client-side:
  3. message_txt == "BELLICON" → drives the per-row bell icons via apply_bellicons.
  4. 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 to user.email.
  • Skipped if the user has no email, an invalid email, or is_active=False.
  • The email is queued (QueuedEmail model) and sent asynchronously by email_queue_worker — so notifications never block the request that triggered them.
  • The from_email defaults to a global pref (email__originating_address) but signal handlers usually pass override_from=project.email_from so emails come from the project-branded address. Mention emails additionally override to email__mention_address if 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=True flag, when the change is workflow-related.
  • Project admins (project_admin=True on their ProjectPeople row).
  • Document subscribers (users explicitly subscribed to this doc — see EDMS § Subscriptions).
  • Site admins, if notifications__disable_admin_cc global 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's transition_roles.
  • notify_role_leaders (default True) → users matching transition_roles AND role_leader=True.
  • notify_project_admins (default True) → all project_admin=True users in the project.
  • notify_readonly_members (default True) → users matching readonly_roles.
  • notify_commentonly_members (default True) → users matching commentonly_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 individuals listed on the transmittal.
  • All members of groups listed 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:

  1. Extract @username tokens from the event's details text.
  2. Filter ProjectPeople.objects.filter(project=event.project, contact__site_login__username__in=usernames) — only project members with matching site_logins resolve.
  3. Skip any tokens that don't resolve to a project member (silently dropped).

Effect for each resolved mention:

  • Create a LivefeedNotification with message_txt="MENTION", is_discussion_notification=True, message_details=<event summary>. This populates the mention panel.
  • Send an email via email_user() with override_from set to email__mention_address global pref (or project.email_from if 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-cube for EDMS, fa-check-square-o for workflow, fa-pencil-square-o for other).
  • Timestamp.
  • A red fa-bell badge 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_count in app_dashboard.js).
  • User mention count — total mentions in all_user_mentions (rendered in two places: #user-mention-unread-count-top for the top header, #user-mention-unread-count for 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 LivefeedNotification in 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 @username syntax. 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_members
  • notify_role_leaders
  • notify_project_admins
  • notify_readonly_members
  • notify_commentonly_members
  • notify_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.User row, AND
  • That User has a linked DirectoryContact (via site_login), AND
  • That Contact has a ProjectPeople row 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.js connect_user_notifications — branches on the mqtt_transport JS global; start_sse() is the EventSource fallback. Both the WS and SSE paths route through the same on_live_message handler.
  • Backend: apps/livefeed/sse.py — a raw ASGI handler (mounted in server/asgi.py at /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 through sync_to_async (see the module docstring).
  • Email queue: core.utils.email_user(user, ...) and email_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 in apply_bellicon_notifications.
  • The two fetches: notification_url (no ?discussions=) and discussion_notification_url (?discussions=true). Same endpoint, same pagination class.
  • Awareness labels: activity panel — set in render_notifications from data.count vs data.results.length. Mention panel — set in render_bellicon_notifications based on the discussion-feed truncation (combined count, less precise).
  • Pagination class: apps.livefeed.pagination.LivefeedNotificationPagination. Settings via BYOP_PAGINATION['livefeed_notification'].

See devdocs/ for codebase-level material and devdocs/BYOP-PLAN.md for the BYOP-era awareness-label rollout history.