Skip to main content

Notifly Python SDK (notifly-sdk)

Official Python SDK for the Notifly API — open-source, self-hostable notification infrastructure (in-app inbox, push, email, SMS, chat).

Covers the Notifly REST surface (131 operations): events/triggers, subscribers, topics, workflows, messages, notifications, integrations, layouts, environments, and more. Fully typed (py.typed), sync and async on httpx, with a hand-written ergonomic layer on top of a generated core.

Install

pip install notifly-sdk

The distribution name and the import name differ. Install notifly-sdk, then import notifly_py:

# pip install notifly-sdk
from notifly_py import Notifly

The PyPI project is notifly-sdk; the importable Python package stays notifly_py throughout this README and in every code sample below.

Requires Python 3.11+.

Quickstart

from notifly_py import Notifly

notifly = Notifly("<NOTIFLY_SECRET_KEY>")

result = notifly.events.trigger(
    workflow="welcome",                 # workflow trigger identifier
    to="subscriber_123",                # subscriber id, payload object, or a list of them
    payload={"name": "Ada"},
)
print(result.transaction_id)

Async is the same surface, awaited:

from notifly_py import AsyncNotifly

async with AsyncNotifly("<NOTIFLY_SECRET_KEY>") as notifly:
    result = await notifly.events.trigger(workflow="welcome", to="subscriber_123")

The client is configured for the hosted API by default. For a self-hosted deployment pass base_url="https://notifly.internal".

What the client does for you

Behaviour Detail
Response envelope The API wraps single entities as {"data": {...}}. The SDK unwraps that at the transport layer, so you always get a populated model. Paginated bodies (data + totalCount + cursors) are left alone.
Auth prefix Authorization: ApiKey <secret key> by default. The raw generated AuthenticatedClient defaults to Bearer, which silently 401s with a secret key.
Retries 429, 408, 502, 503, 504 and connection errors are retried with exponential backoff + jitter, honouring Retry-After. GET/PUT/DELETE always; POST/PATCH only when you pass an idempotency_key.
Typed errors The facade raises NotFoundError, ValidationError, RateLimitError, … instead of returning a fourteen-member union.
Pagination iter_all() / iter_*() walk every page — cursor, offset and page-number styles all handled.
User-Agent notifly-sdk/<version> python/<x.y.z> httpx/<x.y.z>.

Resources

notifly.events.trigger(workflow=..., to=..., payload=...)
notifly.events.trigger_bulk([...])
notifly.events.broadcast(body=...)
notifly.events.cancel(transaction_id)

notifly.subscribers.create(subscriber_id="u_1", email="ada@example.com")
notifly.subscribers.get("u_1")
notifly.subscribers.update("u_1", last_name="Lovelace")
notifly.subscribers.delete("u_1")
notifly.subscribers.list(limit=50, email="ada@example.com")
notifly.subscribers.iter_all()                       # every page, flattened
notifly.subscribers.get_preferences("u_1")
notifly.subscribers.list_notifications("u_1")
notifly.subscribers.register_device_token("u_1", "fcm", body=...)

notifly.topics.upsert(key="product-updates", name="Product updates")
notifly.topics.subscribe("product-updates", ["u_1", "u_2"])
notifly.topics.unsubscribe("product-updates", ["u_1"])
notifly.topics.iter_subscriptions("product-updates")

notifly.workflows.list() / .get(id) / .create(body) / .patch(id, body) / .delete(id) / .iter_all()
notifly.messages.list() / .iter_all() / .delete(id)
notifly.notifications.list() / .iter_all() / .get(id)
notifly.integrations.list() / .list_active() / .create(body) / .update(id, body) / .delete(id)

Any parameter documented for an operation can be passed through as a keyword argument — for example notifly.subscribers.list(limit=100, order_direction="DESC"). Every method also accepts idempotency_key=....

Errors

from notifly_py import NotFoundError, RateLimitError, ValidationError

try:
    notifly.subscribers.get("missing")
except NotFoundError as error:
    print(error.status_code, error.message, error.error_id)
except ValidationError as error:
    print(error.errors, error.ctx)
except RateLimitError as error:
    print(error.retry_after, error.rate_limit)

Hierarchy: NotiflyErrorNotiflyAPIErrorAuthenticationError (401/403), NotFoundError (404), ValidationError (400/422), ConflictError (409), RateLimitError (429), ServerError (5xx). Notifly does not emit RFC 9457 problem+json; these map its ErrorDto shape.

Configuration

from notifly_py import Notifly, RetryConfig

notifly = Notifly(
    "<NOTIFLY_SECRET_KEY>",
    base_url="https://api.notifly.io",
    retry_config=RetryConfig(max_retries=4, backoff_factor=0.5, max_retry_after=30.0),
    timeout=httpx.Timeout(30.0),
    headers={"x-tenant": "acme"},
)

Notifly(..., max_retries=0) disables retries. unwrap_data_envelope=False disables envelope unwrapping (only needed if the API ever stops wrapping — the scheduled spec-drift job watches for exactly that).

Advanced: the generated client

The ergonomic layer is additive. The generated modules remain the escape hatch for the operations the facade does not name, and they accept the same client:

from notifly_py import NotiflyClient
from notifly_py.api.layouts import layouts_controller_list

client = NotiflyClient(token="<NOTIFLY_SECRET_KEY>")
page = layouts_controller_list.sync(client=client, limit=10)

Endpoints live under notifly_py.api.<tag>, one module per operation, each exposing four call styles:

Function Behavior
sync blocking, returns the parsed body (or None)
sync_detailed blocking, returns Response (status code, headers, parsed body)
asyncio async, returns the parsed body
asyncio_detailed async, returns Response

Tags: audit_logs, channel_connections, channel_endpoints, contexts, default, environment_variables, environments, events, integrations, layouts, messages, notifications, subscribers, topics, workflows. All models live in notifly_py.models.

These functions return unions and never raise on HTTP errors — that is by design; only the facade raises.

Dashboard-only operations

Sixteen shipped operations authenticate with a dashboard JWT rather than a secret key (activity/charts, audit logs, translations, workflow duplication). They are listed in notifly_py.internal_ops.INTERNAL_ONLY_OPERATIONS, excluded from the Notifly facade, and reachable only with notifly_py.from_bearer_token(...).

Testing against the SDK

respx mocks below the SDK's transports, so all of its behaviour (including unwrapping and retries) stays active:

import httpx, respx
from notifly_py import Notifly

@respx.mock
def test_welcome_email():
    respx.post("https://api.notifly.io/v1/events/trigger").mock(
        return_value=httpx.Response(201, json={"data": {"acknowledged": True, "status": "processed"}})
    )
    assert Notifly("sk_test").events.trigger(workflow="welcome", to="u_1").acknowledged

Development

The core of this SDK is generated from the Notifly OpenAPI document. Never hand-edit notifly_py/api/** or notifly_py/models/**.

  • Spec snapshot: openapi.json — the internal-SDK flavor produced by the DevinoSolutions/notifly monorepo (apps/api/exportOpenAPIJSON.ts).
  • Generator: openapi-python-client, pinned in scripts/regenerate.sh.
  • Hand-written modules layered on top (preserved across regeneration): __init__.py, _envelope.py, _transport.py, _version.py, exceptions.py, facade.py, internal_ops.py, notifly_client.py, pagination.py.
uv sync --group dev
uv run pytest            # unit suite, no network
uv run ruff check .
uv run mypy              # strict, hand-written layer only
uv run python scripts/check_drift.py        # committed spec vs the live public document
NOTIFLY_SECRET_KEY=... uv run pytest tests/e2e   # opt-in live smoke

To regenerate after a spec update: replace openapi.json, run scripts/regenerate.sh, run the test suite (the envelope regression tests are the gate), review the diff, bump version in pyproject.toml.

Releases publish to PyPI via GitHub Actions Trusted Publishing (.github/workflows/publish.yml) — no tokens.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

notifly_sdk-0.1.0.tar.gz (285.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

notifly_sdk-0.1.0-py3-none-any.whl (914.8 kB view details)

Uploaded Python 3

File details

Details for the file notifly_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: notifly_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 285.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for notifly_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a2818e2d5a6c74c6011f0de5804fde20a03c7ae12c7afd1ddca623fcfc6048a4
MD5 c50e6f7adf363f6267e0d0c8b6717dd6
BLAKE2b-256 19d6fcb104d3ca61cf03b976a64069212466ccc5a7aaa3752c7fe6083233ec70

See more details on using hashes here.

Provenance

The following attestation bundles were made for notifly_sdk-0.1.0.tar.gz:

Publisher: publish.yml on DevinoSolutions/notifly-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file notifly_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: notifly_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 914.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for notifly_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 21c4c163eb8cff14d293a4ea75d42d8ad8b871d7a9d0afb5999d8ac7004f3f46
MD5 4100a8672774828dd200ad68a2b3f492
BLAKE2b-256 b71a39bac6be3054af2a09b2383167e4502380fd715375d0de0c0c86693f7151

See more details on using hashes here.

Provenance

The following attestation bundles were made for notifly_sdk-0.1.0-py3-none-any.whl:

Publisher: publish.yml on DevinoSolutions/notifly-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page