Skip to content

Services


Overview

The services layer is the core orchestration layer of the notifications app.

It is responsible for: - creating notifications - resolving recipients - applying preferences - rendering templates - handling deduplication - creating delivery records

All write-side logic flows through this layer.


Key Entry Points

notify()

Primary function for creating notifications.

Responsibilities: - normalize payload data - render templates when needed - apply preference checks - enforce deduplication via dedupe_key - create Notification records - create NotificationDelivery records for external channels

Returns: - NotifyResult (created notifications + skipped users)


notify_one()

Convenience wrapper for a single recipient.


notify_many()

Convenience wrapper for multiple recipients.


notify_org()

Targets all users in an organization.

Supports: - excluding users (e.g. actor)


notify_teams()

Targets users belonging to specific teams.


notify_org_roles()

Targets users by org-level roles.


notify_teams_roles()

Targets users by team-level roles.


notify_event()

Higher-level helper combining: - template context (ctx) - deep link / routing data (link)

Used for cleaner event-driven calls.


notify_one_event()

Single-recipient version of notify_event().


Supporting Services

cleanup_cleared_notifications()

Removes soft-deleted notifications after retention period.

Used by: - cleanup tasks


channels_for_key()

Returns channels configured for a notification key.

Fallback: - defaults to ["in_app"] if key is unknown


NotifyResult

Simple dataclass containing: - created: list of Notification objects - skipped_users: list of user IDs

Used to: - inspect results - support testing - debug preference filtering


Responsibilities

Services layer

  • orchestrate notification creation
  • resolve recipients via targets
  • apply user preference logic
  • normalize payload structure
  • render templates
  • create delivery records
  • enforce idempotency

Models layer

  • persist data
  • expose helper methods (mark_read, clear)

Tasks layer

  • execute delivery
  • handle retries and backoff

Data Flow

  1. Caller invokes notify() or a wrapper
  2. Payload is normalized
  3. Templates are rendered (if needed)
  4. Recipients are filtered by preferences
  5. Deduplication is applied
  6. Notification rows are created
  7. Delivery rows are created for external channels
  8. Tasks later process delivery asynchronously

Service Flow

flowchart TD

A[Caller: notify / notify_event] --> B[Resolve recipients]
B --> C[Normalize payload]
C --> D[Render templates if needed]

D --> E[Preference check]
E -->|disabled| F[Skip user]
E -->|enabled| G[Continue]

G --> H[Dedupe check]
H -->|exists| I[Skip creation]
H -->|new| J[Create Notification]

J --> K[Resolve channels from registry]

K -->|in_app| L[Stored as Notification only]
K -->|push/email| M[Create NotificationDelivery rows]

M --> N[Async tasks pick up deliveries]

N --> O[Send via provider]
O --> P[Update delivery status]

Event Helper flow

flowchart LR

A[notify_event] --> B[Merge ctx + link]
B --> C[Call notify()]
C --> D[Standard service flow]

target resolution flow

flowchart TD

A[notify_* wrapper] --> B{Target type}

B -->|org| C[users_for_org]
B -->|teams| D[users_for_teams]
B -->|roles| E[users_for_org_roles]
B -->|team roles| F[users_for_teams_roles]

C --> G[Recipient queryset]
D --> G
E --> G
F --> G

G --> H[notify()]

Deduplication Strategy

  • dedupe_key ensures idempotency per user
  • prevents duplicate notifications on retries
  • checked at creation time

Preference Handling

  • handled via preference_resolver.user_wants_notification
  • injectable via pref_check for testing/extensibility
  • bypassable via force=True

Channel Handling

  • defined in registry
  • resolved via channels_for_key()
  • in_app is implicit (no delivery row)
  • other channels create NotificationDelivery rows

What services do not do

Services do not:

  • send push notifications directly
  • perform retries or scheduling
  • manage device state
  • enforce permissions
  • expose API responses

Best practices

  • always use notify() (or wrappers) instead of creating models directly
  • use dedupe_key for idempotent operations
  • keep payloads small and structured
  • prefer notify_event() for event-driven flows
  • avoid bypassing preference checks unless necessary

Extensibility

The services layer is designed to support:

  • additional channels (email, SMS)
  • richer templating systems
  • advanced targeting strategies
  • per-org customization
  • analytics and tracking hooks

Summary

The notifications services layer is the central orchestration point.

It ensures: - consistent notification creation - clean separation of concerns - extensibility across channels and features

This makes the system: - reliable - scalable - easy to evolve