Notifications — Providers¶
Overview¶
The notifications.providers package contains integrations with external push notification systems.
It provides a clean abstraction layer between:
- internal notification logic (services, tasks)
- external delivery providers (Expo, FCM, APNS, etc.)
This layer ensures that:
- providers are interchangeable
- delivery logic is consistent
- external APIs are isolated
Responsibilities¶
The providers layer is responsible for:
- sending push notifications to external services
- translating provider responses into a unified format
- handling provider-specific payload formats
- exposing receipt APIs (when applicable)
It is not responsible for:
- retry logic
- scheduling
- delivery state management
- notification creation
- preference handling
Core Abstractions¶
PushSender (interface)¶
Defines the contract for sending push notifications.
Method:
send_batch(provider, tokens, title, body, data)
Responsibilities:
- accept a batch of tokens
- return per-token results
- remain provider-agnostic
PushSendResult¶
Represents the result of sending to a single device.
Fields:
ok: whether the provider accepted the messageprovider_message_id: provider-specific ID (e.g. Expo ticket)error: error message (if failed)invalid_token: whether token should be deactivated
This unified shape allows the rest of the system to remain provider-independent.
DefaultPushSender¶
The default implementation of PushSender.
Responsibilities:
- route requests to the correct provider implementation
- support multiple providers
- return per-token results even for unsupported providers
Current behavior:
- routes
"expo"to Expo implementation - returns failure results for unsupported providers
This acts as a dispatcher layer.
Expo Provider¶
send_push_to_tokens¶
Main entrypoint for sending Expo push notifications.
Responsibilities:
- chunk tokens into safe batch sizes
- send batches to Expo API
- merge results into a single mapping
Chunking¶
Expo limits the number of messages per request.
The provider:
- splits tokens into chunks (typically 100)
- sends each chunk separately
- aggregates results
HTTP sending¶
The provider supports two modes:
Real HTTP mode¶
- sends requests to Expo endpoints
- parses response payload
- returns per-token results
Stub mode¶
- returns synthetic success results
- used for development and testing
Controlled via settings:
NOTIFICATIONS_EXPO_USE_HTTP
Error handling¶
The provider distinguishes:
Retryable errors¶
- HTTP 429 (rate limiting)
- HTTP 5xx (server errors)
- network failures
These raise exceptions so the task layer can retry.
Non-retryable errors¶
- HTTP 4xx
- per-token errors in response
These return PushSendResult(ok=False, error=...).
Receipt handling¶
Expo push is asynchronous.
Flow:
- Push send returns a ticket ID
- Ticket ID is stored (ExpoPushTicket)
- Later, receipts API is called
- Final delivery status is resolved
expo_get_receipts¶
Fetches delivery receipts from Expo.
Responsibilities:
- query receipt API
- return receipt data per ticket ID
- raise errors for retryable failures
ExpoReceiptsClient¶
Abstraction for receipt fetching.
Allows:
- injecting mock clients in tests
- decoupling HTTP layer from task logic
push_base utilities¶
chunk_list¶
Generic helper for splitting lists into chunks.
Used by:
- Expo provider
- potentially other providers
Provider flow¶
flowchart TD
A[Task: process_pending_deliveries] --> B[PushSender]
B --> C[DefaultPushSender]
C --> D{Provider}
D -->|expo| E[Expo send_push_to_tokens]
E --> F[Chunk tokens]
F --> G[Send HTTP requests]
G --> H[Parse responses]
H --> I[PushSendResult per token]
D -->|unsupported| J[Return failure results]
Receipt flow¶
flowchart TD
A[Task: check_expo_push_receipts] --> B[ExpoReceiptsClient]
B --> C[Expo receipts API]
C --> D[Receipt data]
D --> E[Update ExpoPushTicket]
E --> F[Deactivate invalid devices]
Design principles¶
Provider isolation¶
- all external API logic lives in providers
- no provider code in services or views
Unified result shape¶
- all providers return PushSendResult
- simplifies downstream logic
Batch-first design¶
- providers operate on batches
- improves performance and reduces API calls
Fail-fast for retryable errors¶
- raise exceptions for retryable failures
- allow task layer to handle retries centrally
Testability¶
- injectable sender/client interfaces
- stub mode for development
- no hard dependency on external APIs in tests
What providers do not do¶
Providers do not:
- schedule retries
- persist delivery state
- decide recipients
- apply preferences
- trigger notifications
Those responsibilities belong to:
- services
- tasks
- models
Best practices¶
- always use PushSender abstraction (never call provider directly)
- keep provider implementations stateless
- return results for every token
- raise only for retryable batch-level failures
- isolate HTTP logic inside provider modules
- use stub mode in non-production environments
Extensibility¶
The providers layer is designed to support:
- additional push providers (FCM, APNS)
- email providers
- SMS providers
- multi-provider fallback strategies
- provider-specific optimizations
To add a new provider:
- implement PushSender behavior
- map provider string in DefaultPushSender
- ensure PushSendResult compatibility
Summary¶
The notifications.providers package isolates all external delivery integrations.
It ensures:
- consistent delivery behavior
- provider independence
- testability
- scalability across channels
This allows the rest of the system to remain clean and focused on business logic.