Send and keep track of Expo push notifications with Django and Celery
Project description
Django Expo Notifications
A Django app that allows you to keep track of devices, send Expo push notifications and check their tickets for receipts. This project uses Celery and the Expo Server SDK for Python to send push messages and check push receipts in the background.
Features
- Keep track of user device tokens and preferred languages
- Send push notifications efficiently in bulk
- Automatically retry sending messages in case of a failure
- Automatically keep track of message tickets
- Automatically check push receipts
- Automatically retry checking receipts in case of a failure
- Automatically flag inactive devices
- Django admin actions for sending messages and checking receipts
Installation
pip install django-expo-notifications
After installing the Django app, add it to your project's INSTALLED_APPS setting:
INSTALLED_APPS = [
"expo_notifications", # <-- Add this line
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
The expo_notifications app comes with a set of models.
To make them available in your database, run Django's migrate management command:
python manage.py migrate
Finally, make sure your Django project is configured to use Celery. This can be done by following Celery's First steps with Django guide.
Settings
You may optionally set the following settings in your Django project's settings module. The code snippet below shows the default values for each setting.
from datetime import timedelta
EXPO_NOTIFICATIONS_TOKEN = None
EXPO_NOTIFICATIONS_RECEIPT_CHECK_DELAY = timedelta(minutes=30)
EXPO_NOTIFICATIONS_SENDING_TASK_MAX_RETRIES = 5
EXPO_NOTIFICATIONS_SENDING_TASK_RETRY_DELAY = timedelta(seconds=30)
EXPO_NOTIFICATIONS_CHECKING_TASK_MAX_RETRIES = 3
EXPO_NOTIFICATIONS_CHECKING_TASK_RETRY_DELAY = timedelta(minutes=1)
Enhanced Security for Push Notifications
At some point, you most likely want to enable Enhanced Security for Push Notifications in your Expo account.
This will require you to provide an Expo access token in your Django project's settings module like this:
EXPO_NOTIFICATIONS_TOKEN = "your-expo-viewer-access-token-here"
Check out the Expo documentation for more information.
Receipt Check Delay
Expo recommends checking the receipts of push notifications after some delay. This gives Expo time to process the push notifications and generate receipts. The default delay is based on the value used in the official Expo Server SDK for Node. Feel free to adjust this setting to your needs:
EXPO_NOTIFICATIONS_RECEIPT_CHECK_DELAY = timedelta(hours=1)
However, note that Expo only keeps ticket receipts for around a day and Celery generally prefers if tasks are not scheduled too far in the future.
Usage
The most basic usage of this app involves managing user devices and sending messages to them. Everything else (i.e. background task scheduling, retries, checking ticket receipts, deactivating devices, etc.) is handled automatically.
Quickstart
from django.contrib.auth import get_user_model
from expo_notifications.models import Device, Message
some_user = get_user_model().objects.first()
device = Device.objects.create(
user=some_user,
push_token="ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
)
device.messages.send(
title="Hello, World!",
body="This is a test message.",
)
Keeping track of user devices
The Device model represents a user device that can receive push notifications as long as it's is_active flag is True.
A user may have multiple devices, however, each device must have an Expo token that is unique to that device and user.
from django.contrib.auth import get_user_model
from expo_notifications.models import Device
some_user = get_user_model().objects.first()
Device.objects.update_or_create(
user=some_user,
push_token="ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
defaults={"is_active": True},
)
We recommend using update_or_create to avoid duplicate devices and to automatically reactivate existing devices if necessary.
Keeping track of device language
The Device model has an optional lang field that can be used to store the language/region code of the device.g. as per ISO 639-1).
This can be useful if you want to send localized push notifications to your users.
from django.contrib.auth import get_user_model
from expo_notifications.models import Device
some_user = get_user_model().objects.first()
Device.objects.update_or_create(
user=some_user,
push_token="ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
defaults={"is_active": True, "lang": "en"},
)
We recommend using ISO 639-1 and if needed, ISO 3166-1 alpha-2 codes.
Sending push notifications
The recommended way to send messages is to create and send them in bulk like this:
from expo_notifications.models import Device, Message
messages = [
Message(device=device, title="Hello, World!")
for device in Device.objects.active
]
Message.objects.bulk_send(messages)
Similar to bulk_send, we provide a send method that can be used to send a single message:
from expo_notifications.models import Device, Message
device = Device.objects.active.first()
Message.objects.send(device=device, title="Hello, World!")
Both bulk_send and send methods use Django's bulk_create and create under the hood and are also available on related managers:
from expo_notifications.models import Device
device = Device.objects.active.first()
device.messages.send(title="Hello, World!")
Separately create and send messages
While creating and sending messages in one go is the recommended way, you can also create messages first and send them later:
from expo_notifications.models import Message
# Sending a single message (i.e. a model instance)
single_message = Message.objects.first()
single_message.send()
# Sending multiple messages (i.e. a queryset)
multiple_messages = Message.objects.all()
multiple_messages.send()
Django Admin Actions
The expo_notifications app comes with Django admin actions that can be used to send messages and check their receipts.
Note that these actions are mainly meant for troubleshooting and manual testing.
Send Selected Messages
The Send selected messages action can be used to troubleshoot your setup and test sending push notifications.
The action will send the selected messages in bulk via a Celery task and schedule a task to check their receipts.
To see whether the messages were sent successfully, you can check their tickets in the Django admin interface.
Messages which were rejected by Expo will have their is_success flag set to False and their error_message field may contain a rejection reason.
Check Selected Tickets
The Check selected tickets action can be used to manually check the receipts of selected messages without a delay.
This action is solely meant for troubleshooting, since the app automatically schedules tasks to check the receipts of all sent messages.
To see whether a message was successfully sent to a device, you can check its ticket receipts in the Django admin interface.
Messages which could not be sent to a device will have their is_success flag set to False and their error_message field may contain a reason.
Example Project
Take a look at our Django example project under tests/project.
It can be run by executing these commands:
uv syncuv run tests/project/manage.py migrateuv run tests/project/manage.py createsuperuseruv run tests/project/manage.py runserver
Live testing
Sending push notifications and checking their receipts can be done by using the "Send selected messages" action in the Django admin interface. In order for this to work, you need to run a Celery worker and make sure the Celery worker is authorized to communicate with Expo.
Unless the default Celery broker setup works for you (i.e. RabbitMQ on localhost), you need to provide a Celery broker url:
export CELERY_BROKER_URL="redis://localhost:6379/0"
In case you enabled Enhanced Security for Push Notifications in your Expo account, you also need to provide an Expo access token.
export EXPO_NOTIFICATIONS_TOKEN="your-expo-viewer-access-token-here"
Now you can run a Celery worker and the Django dev server in parallel to test sending push notifications and checking their receipts:
uv run celery --workdir tests/project --app project worker -l INFOuv run tests/project/manage.py runserver
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_expo_notifications-0.7.0.tar.gz.
File metadata
- Download URL: django_expo_notifications-0.7.0.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3aca5536b613fbd9bfd57a04e296f43e85c640891b731744a4a82e17db7a71b9
|
|
| MD5 |
a788d00c7b1c8bd21d9598eee583a0a9
|
|
| BLAKE2b-256 |
611b40e3347ac992fef7707383011bf10ba813c50d4caae2f521990c2761beb8
|
File details
Details for the file django_expo_notifications-0.7.0-py3-none-any.whl.
File metadata
- Download URL: django_expo_notifications-0.7.0-py3-none-any.whl
- Upload date:
- Size: 16.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45d6c611789f45f880cae82833b4d7ada32445795b7fbb03ed6823a0179a1e2b
|
|
| MD5 |
09e232b253a5f39bfdbe807d85696446
|
|
| BLAKE2b-256 |
cd09b320a2600066a3faf7d01736537f458d74e6f3519932b16f394095b8c23f
|