expo-server-sdk-python

Python SDK for sending Expo push notifications from your own server, with chunking and receipt validation built in.

SDK
PyPI
v2.2.0
181stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
39/100Needs Attention
Development Activity0
Maintenance0
Community76
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
50/100Fair
Architecture55
Code Quality35
Innovation40
Learning Curve70

exponent-server-sdk-python (published to PyPI as exponent_server_sdk) is the community-maintained Python client for Expo’s push notification service. It wraps the HTTP/2 push API that Expo Go and standalone Expo/React Native apps rely on, giving backend developers a typed PushClient instead of hand-rolled requests calls against the raw API.

The library models the full push lifecycle as Python objects: a PushMessage namedtuple for building a notification payload, PushTicket and PushReceipt wrappers for interpreting Expo’s async two-step delivery confirmation, and a family of typed exceptions (DeviceNotRegisteredError, MessageTooBigError, MessageRateExceededError, InvalidCredentialsError) so callers can branch on specific push failures without parsing raw JSON error strings. It automatically chunks large batches into Expo’s documented limits (100 messages per publish call, 1000 IDs per receipt check) so a caller can push to arbitrarily large audiences with a single publish_multiple call.

What You Get

  • PushClient - a session-based client for POST /push/send and POST /push/getReceipts, with configurable host, API path, and timeout for testing or self-hosted Expo-compatible endpoints
  • PushMessage - a namedtuple covering every documented Expo push field (title, body, data, sound, badge, priority, ttl, channel_id, subtitle, mutable_content) with sane defaults so only to is required
  • Automatic chunking - publish_multiple and check_receipts_multiple split large batches into Expo’s 100-message / 1000-receipt limits without the caller managing pagination
  • Typed push errors - DeviceNotRegisteredError, MessageTooBigError, MessageRateExceededError, and InvalidCredentialsError map directly onto Expo’s documented receipt error codes
  • force_fcm_v1 support - opt Android delivery into FCM’s V1 API independent of the credentials configured on Expo’s side

Common Use Cases

  • Push notification backend - a Django/Flask/FastAPI server sending transactional or marketing pushes to a React Native app’s registered Expo push tokens
  • Token hygiene jobs - a scheduled task that calls check_receipts_multiple and deactivates any token that returns DeviceNotRegisteredError
  • Bulk broadcast - fan-out messaging to a large user base in one publish_multiple call, relying on the SDK’s built-in chunking instead of writing batching logic
  • Retry-and-report pipelines - wrapping publish calls with a task queue (e.g. Celery) and error reporting service (the README documents a pyrollbar-based pattern) to retry transient ConnectionError/HTTPError failures

Under The Hood

Architecture The entire package lives in a single module, exponent_server_sdk/__init__.py. PushClient is the sole entry point: its _publish_internal and _check_receipts_internal methods talk directly to Expo’s push endpoints over a shared requests.Session, while publish_multiple and check_receipts_multiple sit on top as thin chunking wrappers using itertools.islice. Data flows one way through immutable namedtuples — a caller builds a PushMessage, the client serializes it via get_payload(), and Expo’s JSON response is deserialized back into PushTicket/PushReceipt namedtuples rather than plain dicts. There is no dependency injection or plugin system; the only extension points are subclassing PushMessage to customize get_payload() and passing a preconfigured Session for auth headers. Because everything funnels through the two _internal methods, changing Expo’s request/response envelope would mean touching just those two functions, but there is also no abstraction layer between this SDK and Expo’s specific wire format.

Tech Stack The library targets Python 3 and declares exactly two runtime dependencies in setup.py: requests for HTTP and six for the isinstance(token, six.string_types) check in is_exponent_push_token — a legacy Python 2/3 compatibility shim left over from the project’s 2016 origin. Packaging is classic setuptools/setup.py (no pyproject.toml, no build backend declaration, no pinned dependency versions), and there is no CI configuration, Dockerfile, or linter config anywhere in the repository.

Code Quality No test files exist anywhere in the repository — there is no tests/ directory, no pytest/unittest usage, and no CI workflow to run them even if they existed. Error handling is deliberate and reasonably thorough for a small library: PushServerError and the PushTicketError subclasses carry structured context (response_data, errors, push_response) rather than swallowing failures, and validate_response() explicitly maps Expo’s documented error strings onto specific exception classes. Naming is consistent and mirrors Expo’s own API vocabulary (ticket vs. receipt, to/data/ttl fields), but there is no type-hinting, no docstring-generated docs, and no formatter/linter enforcing style.

API Design The public surface is small and low-boilerplate: constructing a PushClient() with no arguments works out of the box against Expo’s production host, and sending a notification is a single publish(PushMessage(to=..., body=...)) call. Optional fields default to None via PushMessage.__new__.__defaults__, so callers only ever specify what they need. The main ergonomic gap is documentation depth — docstrings exist on most classes and explain the what, but usage guidance beyond the README’s one code sample is limited, and callers must read Expo’s separate push-notification docs to fully understand receipt semantics.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search