Skip to content

OCS Notifications App

Overview

The ocs_notifications app provides a team-scoped notification system for Open Chat Studio. It alerts team members about important events through in-app messages and email, allowing users to control delivery preferences and severity thresholds.

This system is designed to:

  • Alert team members about important events (custom action health checks, evaluations, data syncs, etc.)
  • Notify users asynchronously without blocking your feature code
  • Support multi-channel delivery with granular user preferences (in-app, email, level thresholds)
  • Reduce notification fatigue through deduplication and severity filtering
  • Persist notifications so users can view, filter, and manage them later

Core Concepts

Notification Levels

Notifications use a three-tier severity system:

Level Purpose Example
INFO General information, non-critical updates "Data sync completed successfully"
WARNING Concerning but non-breaking changes "Custom action response time is slow"
ERROR Critical issues requiring attention "Custom action is unreachable"

Users configure separate thresholds for in-app and email delivery. For example, a user might see INFO and WARNING in-app but only receive ERROR emails.

The Identifier System

Notifications are deduplicated by combining a slug and event data. When you call create_notification() with the same slug and event_data, the system updates the existing notification rather than creating a duplicate.

How deduplication works:

  • Same slug + Same event_data → Updates existing notification, re-notifies users who previously read it
  • Same slug + Different event_data → Creates separate notification (different event thread)

Example:

# First time: Creates notification
create_notification(
    slug="payment-api-timeout",
    event_data={"api_id": 1},  # Unique to this API
    # ... other fields
)

# Later: Same API times out again
create_notification(
    slug="payment-api-timeout",
    event_data={"api_id": 1},  # SAME → updates existing
    # ... other fields
)
# → Existing notification is updated
# → Users who read it are marked unread (gets re-notified)

# Different API times out
create_notification(
    slug="payment-api-timeout",
    event_data={"api_id": 2},  # DIFFERENT → separate notification
    # ... other fields
)
# → New notification thread for this API

The Data Model

Notifications are split into two models:

  • Notification (per team, per slug+event_data): Shared across all team members; contains the title, message, and event data
  • UserNotification (per user): Tracks which users have seen the notification, read/unread status for each user

When you call create_notification(), the system creates/updates both automatically, applying permission filters when determining which users receive it.

How to Create Notifications

Basic Usage

Use the create_notification() utility function from apps.ocs_notifications.utils:

from apps.ocs_notifications.models import LevelChoices
from apps.ocs_notifications.utils import create_notification

create_notification(
    title="Custom Action is down",
    message="The custom action 'My API' is currently unreachable.",
    level=LevelChoices.ERROR,
    team=team_object,
    slug="custom-action-health-check",
    event_data={"action_id": 123},
)

Where to Add Notification Methods

For better code organization and maintainability, preferably add your notification helper functions in apps/ocs_notifications/notifications.py.

Required Parameters

  • title (str): Short notification heading (shown in list)
  • message (str): Detailed notification content
  • level (LevelChoices): Severity level (INFO, WARNING, ERROR)
  • team (Team): Team to notify (determines who receives this notification)
  • slug (str): Identifier for notification type; groups related notifications
  • Use format: "feature-event-type" (e.g., "custom-action-health-check")
  • Same slug + same event_data = updates existing notification

Optional Parameters

  • event_data (dict): Additional JSON data stored with the notification
  • Combined with slug to create the deduplication identifier
  • Use for: IDs, status flags, context needed for re-delivery decisions
  • Best practice: Include minimal identifiers needed for deduplication logic
  • Default: empty dict

  • permissions (list[str]): Django permission codenames to filter recipients

  • Format: "app_label.action" (e.g., "custom_actions.change_customaction")
  • Only team members with ALL specified permissions will receive the notification
  • Permissions are checked per-team (combines Django perms + team membership)
  • Default: None (notify all team members in the team)

  • links (dict): A dictionary of label → URL pairs to attach to the notification

  • These are rendered as clickable chips/buttons in the notification UI
  • Use for: linking to the relevant bot, session, or admin page
  • Example: {"View Bot": "/experiments/123/", "View Session": "/sessions/456/"}
  • Default: empty dict

When & Where to Call create_notification()

The best place to call create_notification() depends on your use case. All contexts are safe:

Adding Notifications to Your Feature

Follow this checklist when integrating notifications:

  1. Identify the event: What user-facing event should trigger the notification?
  2. Examples: Custom action down, evaluation complete, data sync failed

  3. Choose a slug: A descriptive identifier for the notification type

  4. Format: "feature-event-type" (e.g., "data-sync-failed")
  5. Use consistent slug for the same type of event

  6. Design event_data: What minimal context identifies this specific event instance?

  7. Include IDs to distinguish between different instances (e.g., action_id, experiment_id)
  8. Keep it small and JSON-serializable
  9. Don't include sensitive data (passwords, tokens, etc.)

  10. Pick a severity level: Should this be INFO, WARNING, or ERROR?

  11. INFO: Confirmations, completions, general information
  12. WARNING: Performance issues, retry scenarios, unusual conditions
  13. ERROR: System failures, unreachable services, data loss risks

  14. Determine scope: Who should receive this notification?

  15. All team members? Or only users with specific permissions?
  16. Use permissions parameter if role-based filtering is needed
  17. Test with users having different permissions

  18. Choose where to call it: Signal, view, or Celery task?

  19. Signals: For model lifecycle events
  20. Views: For user-triggered actions
  21. Celery: For background job results

  22. Write tests: Create unit tests verifying the notification is sent

  23. Use UserNotification.objects.filter(...) to assert notification was created
  24. Test permission filters if used
  25. Test with different notification level preferences

  26. Document it: Add code comments explaining what triggers the notification

  27. What slug/event_data is used and why
  28. Who receives it and why
  29. Example: Link to AGENTS.md or add inline docstring