Notifications — Tasks¶
Overview¶
The notifications.tasks package contains asynchronous and scheduled execution entry points for the notifications app.
These tasks are responsible for:
- processing pending notification deliveries
- reconciling Expo push receipts
- firing scheduled notifications
- cleaning up old notifications
- deactivating stale push devices
The task layer is intentionally thin.
Its primary role is to:
- run work asynchronously
- call service-layer functions
- return summary results
Business logic should live in services, not in tasks.
Responsibilities¶
The task layer is responsible for:
- asynchronous execution via Celery
- scheduled execution of due work
- safe batch processing
- wrapping service functions for workers/beat
- returning operational summaries
It is not responsible for:
- notification creation rules
- preference resolution
- payload rendering
- provider-specific business logic
- delivery modeling
Main tasks¶
process_pending_deliveries¶
Processes pending NotificationDelivery rows that are due.
Typical responsibilities:
- select pending delivery rows
- lock rows safely with
select_for_update(skip_locked=True) - hand off sending to the delivery service
- update delivery status and retry scheduling
- return counts for:
- sent
- failed
- retried
- skipped
This task is the main async executor for push delivery.
check_expo_push_receipts¶
Polls Expo for final receipt status of previously accepted push tickets.
Typical responsibilities:
- find unchecked
ExpoPushTicketrows that are due - query Expo receipts API
- mark tickets checked when terminal
- retry missing receipts with backoff
- deactivate invalid devices when Expo reports
DeviceNotRegistered
This task exists because Expo delivery is asynchronous and ticket acceptance does not guarantee final delivery.
run_scheduled_notifications¶
Fires due ScheduledNotification rows.
Typical responsibilities:
- find active rows with
run_at <= now - call notification services to create actual notifications
- deactivate the scheduled row after successful execution
- return the number of fired rows
This task converts future schedules into real notifications.
cleanup_notifications_task¶
Deletes cleared notifications older than the configured retention window.
Typical responsibilities:
- call cleanup service
- remove soft-deleted rows after retention
- return number of deleted notifications
This keeps the inbox storage bounded over time.
cleanup_stale_push_devices_task¶
Deactivates push devices that have not been seen recently.
Typical responsibilities:
- find active devices with stale
last_seen_at - mark them inactive
- return the number of deactivated rows
This helps keep push delivery targeting accurate.
Task-to-service relationship¶
The tasks package should remain thin and delegate to services.
flowchart TD
A[Celery Task] --> B[Notifications Service]
B --> C[Models]
B --> D[Providers]
B --> E[Retry / status updates]
Examples¶
- process_pending_deliveries -> delivery service
- check_expo_push_receipts -> Expo receipt service
- run_scheduled_notifications -> scheduled notification service
- cleanup_notifications_task -> cleanup service
Batch processing model¶
Most tasks work in batches.
This is important for:
- predictable worker runtime
- safe retry behavior
- reduced DB lock duration
- scalability under load
Typical patterns include:
- batch_size parameters
- ordering by oldest rows first
- due-time filtering
- summary dict return values
Concurrency and locking¶
Some tasks use database row locking for correctness.
Delivery processing¶
process_pending_deliveries should lock rows with:
- select_for_update(skip_locked=True)
This allows multiple workers to run concurrently without processing the same delivery twice.
Expo receipt checking¶
check_expo_push_receipts should also use:
- select_for_update(skip_locked=True)
This prevents duplicate reconciliation work.
Retry behavior¶
Retry behavior belongs to the execution pipeline, not to views.
Delivery retries¶
Pending deliveries may be retried when:
- provider call fails
- rate limiting occurs
- temporary transport errors occur
Retry timing is controlled by config helpers such as:
- backoff calculation
- rate-limit retry delay
- max attempts
Receipt retries¶
Expo receipts may be missing temporarily.
The task tracks:
- missing_attempts
- next_check_at
After a configured maximum number of missing checks, the ticket is marked terminal as missing.
Provider interaction¶
Tasks do not talk to providers directly unless they are the async execution boundary for a provider-backed workflow.
Examples:
- delivery task uses push sender abstraction
- Expo receipts task uses Expo receipts client abstraction
This keeps provider-specific code isolated behind interfaces and helper clients.
Operational summaries¶
Tasks generally return compact summary dicts.
Delivery processing¶
- sent
- failed
- retried
- skipped
Expo receipts¶
- checked
- deactivated
- missing
Scheduled notifications¶
- fired
Cleanup¶
- deleted_notifications
- deactivated
These summaries are useful for:
- logs
- manual runs
- monitoring
- tests
Safety guarantees¶
The task layer should preserve these guarantees:
- no double-processing of locked rows
- retries remain bounded
- invalid tokens are deactivated
- scheduled rows are not fired repeatedly
- cleanup is idempotent
What tasks do not do¶
Tasks do not:
- decide user preference rules
- render templates directly
- create registry definitions
- expose API responses
- replace services as the source of truth
Those responsibilities belong to:
- services
- providers
- serializers
- views
Best practices¶
- keep tasks thin and delegate to services
- use row locking for queue-like processing
- process work in bounded batches
- return compact operational summaries
- isolate provider interactions behind interfaces
- make cleanup tasks idempotent
Summary¶
The notifications.tasks package provides the async execution layer for the notifications system.
It ensures that notification workflows can be processed:
- safely
- in batches
- with retries
- with provider reconciliation
- with long-term cleanup support
This makes the notifications system operationally reliable and scalable.