{"id":223,"library":"novu-py","title":"Novu","description":"Open-source notification infrastructure. The official Python SDK is novu-py (pip install novu-py, import novu_py), NOT the older community packages novu or novu-python. Current version is 3.13.0 (Jan 2026). Three separate PyPI packages exist with different APIs — using the wrong one is the #1 footgun.","status":"active","version":"3.13.0","language":"python","source_language":"en","source_url":"https://github.com/novuhq/novu","tags":["notifications","email","sms","push","in-app","multi-channel","open-source"],"install":[{"cmd":"pip install novu-py","lang":"bash","label":"Official SDK (correct)"},{"cmd":"pip install novu","lang":"bash","label":"WRONG — unmaintained community package, different API"},{"cmd":"pip install novu-python","lang":"bash","label":"WRONG — another unmaintained community package"}],"dependencies":[{"reason":"Required. Installed automatically.","package":"httpx","optional":false},{"reason":"Required. Installed automatically.","package":"pydantic","optional":false}],"imports":[{"note":"The old novu community package uses EventApi with name= and recipients= parameters. The official novu-py uses Novu class with workflow_id= and to= parameters. Completely different APIs.","wrong":"# Wrong package — novu community package (unmaintained):\nfrom novu.api import EventApi\nevent_api = EventApi('https://api.novu.co/api/', '<NOVU_API_KEY>')\nevent_api.trigger(\n    name='<YOUR_TEMPLATE_NAME>',  # old API uses 'name', not 'workflow_id'\n    recipients='<SUBSCRIBER_ID>',  # old API uses 'recipients'\n    payload={}\n)","symbol":"Novu","correct":"from novu_py import Novu\n\nwith Novu(secret_key='YOUR_SECRET_KEY') as novu:\n    response = novu.trigger(\n        workflow_id='welcome-onboarding',\n        to={'subscriber_id': 'user-123'},\n        payload={'firstName': 'Alice'}\n    )"}],"quickstart":{"code":"from novu_py import Novu\n\n# Initialize with your Novu API key\nnovu = Novu(secret_key='YOUR_NOVU_API_KEY')\n\n# Trigger a notification workflow\nresponse = novu.trigger(\n    workflow_id='welcome-email',  # workflow ID from Novu dashboard\n    to={\n        'subscriber_id': 'user-123',\n        'email': 'alice@example.com',\n        'first_name': 'Alice'\n    },\n    payload={\n        'company': 'Acme Corp',\n        'action_url': 'https://app.example.com'\n    }\n)\nprint(response)\n\n# Use as context manager for proper cleanup\nwith Novu(secret_key='YOUR_NOVU_API_KEY') as novu:\n    novu.trigger(\n        workflow_id='password-reset',\n        to={'subscriber_id': 'user-456'},\n        payload={'reset_url': 'https://app.example.com/reset/token'}\n    )","lang":"python","description":"Official novu-py SDK. Use context manager for resource cleanup in long-lived apps."},"warnings":[{"fix":"pip install novu-py — import as from novu_py import Novu. Never use pip install novu or pip install novu-python for new projects.","message":"Three separate PyPI packages exist: novu (community, dead), novu-python (community, dead), novu-py (official, active). pip install novu installs the wrong unmaintained package. LLMs almost always generate the wrong package name.","severity":"breaking","affected_versions":"all"},{"fix":"Migrate from: EventApi(url, key).trigger(name=, recipients=) to: Novu(secret_key=).trigger(workflow_id=, to=)","message":"The old community novu package uses EventApi with name= (template name) and recipients= parameters. The official novu-py uses Novu.trigger() with workflow_id= and to= parameters. Code from tutorials using the old package will fail with ImportError or TypeError on the official SDK.","severity":"breaking","affected_versions":"all"},{"fix":"Pin the version in requirements. Check the GitHub releases page before upgrading.","message":"novu-py went through extremely rapid versioning: 0.x → 1.x → 3.x in under a year (Jan–Nov 2025). Each major version has breaking changes. Code from tutorials written for 0.x will not work on 3.x.","severity":"breaking","affected_versions":"all"},{"fix":"Use with Novu(secret_key=...) as novu: pattern, or call novu.close() explicitly after use.","message":"The Novu class should be used as a context manager in long-lived applications to properly close HTTPX connections. Forgetting to close the client leaks HTTP connections.","severity":"gotcha","affected_versions":"all"},{"fix":"Create or upsert subscribers with full contact info before triggering: novu.subscribers.identify(subscriber_id='user-123', email='user@example.com').","message":"Novu requires subscribers to exist before triggering notifications. Triggering to a non-existent subscriber_id auto-creates it, but without email/phone data the notification may silently fail to deliver.","severity":"gotcha","affected_versions":"all"}],"env_vars":null,"last_verified":"2026-05-12T12:00:52.111Z","next_check":"2026-06-27T00:00:00.000Z","problems":[{"fix":"Change the import statement from `import novu` or `from novu import Novu` to `import novu_py` or `from novu_py import Novu`.","cause":"The user has installed the official `novu-py` SDK but is attempting to import an older, community-maintained package named `novu` instead of `novu_py`.","error":"ModuleNotFoundError: No module named 'novu'"},{"fix":"Ensure the `novu-py` package is installed (`pip install novu-py`) and update the import statement to `from novu_py import Novu` and instantiate `Novu` from the `novu_py` module.","cause":"The user has likely imported a `Novu` class from an older, incompatible community package (e.g., `novu` or `novu-python`) but is attempting to call methods (`trigger`, `subscribers`, etc.) that are specific to the official `novu-py` SDK's API.","error":"AttributeError: 'Novu' object has no attribute 'trigger'"},{"fix":"Install the official SDK using pip: `pip install novu-py`. If already installed, ensure you are running your script in the correct Python environment where `novu-py` is installed.","cause":"The `novu-py` package is not installed in the active Python environment, or the Python interpreter cannot find the installed package.","error":"ModuleNotFoundError: No module named 'novu_py'"},{"fix":"Ensure that a valid `secret_key` is passed to the `Novu` client constructor, preferably by loading it from an environment variable. Example: `from novu_py import Novu; import os; novu = Novu(secret_key=os.getenv('NOVU_SECRET_KEY'))`.","cause":"The API key (secret key) provided when initializing the `Novu` client is either missing, incorrect, or invalid, preventing successful authentication with the Novu API.","error":"requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url"},{"fix":"Consult the Novu API documentation for the specific endpoint being called and ensure all required fields in the Data Transfer Object (DTO) are correctly provided and formatted according to the schema.","cause":"The data provided in the request body (e.g., a DTO object like `novu_py.TriggerEventRequestDto`) does not conform to the Novu API's expected schema, such as a missing required field or incorrect data type.","error":"requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: https://api.novu.co/v1/layouts"}],"ecosystem":"pypi","meta_description":null,"install_score":95,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.02,"mem_mb":26.6,"disk_size":"37.5M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":2.09,"mem_mb":26.6,"disk_size":"37M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":4.09,"mem_mb":28.3,"disk_size":"41.3M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.38,"mem_mb":28.3,"disk_size":"41M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.37,"mem_mb":27.5,"disk_size":"32.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.26,"mem_mb":27.5,"disk_size":"32M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.19,"mem_mb":28.6,"disk_size":"32.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":3.13,"mem_mb":28.6,"disk_size":"32M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":2.52,"mem_mb":26.2,"disk_size":"36.6M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":2.17,"mem_mb":26.2,"disk_size":"36M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":1,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":null,"mem_mb":null,"disk_size":null}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}