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¶
- Caller invokes notify() or a wrapper
- Payload is normalized
- Templates are rendered (if needed)
- Recipients are filtered by preferences
- Deduplication is applied
- Notification rows are created
- Delivery rows are created for external channels
- 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]
¶
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