Skip to main content

Notifly Python SDK (notifly-sdk)

Official Python client SDK for the Notifly notification API (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 targets https://api.notifly.io by default. To point it at a different API host, pass base_url="https://api.example.com".

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: NotiflyError → NotiflyAPIError → AuthenticationError (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

This client SDK is MIT-licensed — see LICENSE. The license covers this SDK only; the Notifly platform and API it talks to are proprietary.

Release files for notifly-sdk 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for notifly-sdk 0.1.1
File Size Uploaded
notifly_sdk-0.1.1.tar.gz 285.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for notifly-sdk 0.1.1
File Interpreter ABI Platform
notifly_sdk-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / notifly_sdk-0.1.1.tar.gz

Download URL notifly_sdk-0.1.1.tar.gz
Size 285.7 kB
Tags Source
SHA-256 checksum
How to use checksums
00b9ed365091795791536129d201025b236425800b3ebd48879f27886b3a54b6
BLAKE2b-256 checksum
How to use checksums
5fb4226fda4f490ee230733732794ead1c18faa986160f8f207fb2097bda8d54
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / notifly_sdk-0.1.1-py3-none-any.whl

Download URL notifly_sdk-0.1.1-py3-none-any.whl
Size 914.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
58d84cdc1f7a71da367386f306511c6d9311af2a6db061ca9ad9302b6c8e705a
BLAKE2b-256 checksum
How to use checksums
5c0ef77bc67211488466f69fca1aab5e15559c03536899497ee560a8dbc15690
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page