# NOTIFIEDBY-django.md

Read `NOTIFIEDBY.md` first.

## Purpose

Use this file when integrating **NotifiedBy** into a **Python/Django** application.

Public docs describe an official package named `django-notifiedby`. Prefer that package where it covers the use case instead of writing raw HTTP calls from scratch.

## Public Package Facts

From the public docs, the Django package provides:

- a Django email backend: `notifiedby.NotifiedByEmailBackend`
- direct API send helpers
- email querying helpers
- flow subscription helpers
- recipient and flag helpers
- exception classes in `notifiedby.exceptions`
- a `NotifiedByClient` for custom API keys or base URLs

Do **not** invent package settings, helper names, exception types, or behaviours beyond the public package docs.

## Installation and Baseline Configuration

Documented installation:

```bash
pip install django-notifiedby
pip install requests
```

Documented Django settings:

```python
INSTALLED_APPS = [
    # ...
    'notifiedby',
]

EMAIL_BACKEND = "notifiedby.NotifiedByEmailBackend"
NOTIFIEDBY_API_KEY = "YOUR_API_KEY"
```

Optional documented setting for encrypted email content:

```python
NOTIFIEDBY_ENCRYPTION_KEY = "your-encryption-key"
```

## Configuration Rules

- Keep `NOTIFIEDBY_API_KEY` and any encryption key in environment-backed settings or your standard secret-management path.
- Do not hard-code keys in application code.
- Only set `EMAIL_BACKEND` if you intend NotifiedBy to become the default backend for Django email sending.
- Public docs say the backend setting is optional if you only need flow/recipient helpers.

## Decision Rules

### Prefer the Django package when:

- sending standard transactional emails from Django
- using Django's existing email APIs such as `send_mail()` or `EmailMessage`
- subscribing/unsubscribing recipients to flows
- managing recipients or flags
- querying delivery status through documented helpers

### Use direct raw API calls only when:

- the required capability is publicly documented
- the package docs do not expose it
- you keep the fallback isolated and clearly marked as a raw API path

Do not bypass the package just because direct HTTP is quicker to type.

## Sending Email Through Django

Public docs say that when you set:

```python
EMAIL_BACKEND = "notifiedby.NotifiedByEmailBackend"
```

standard Django email functions automatically use NotifiedBy.

Documented examples include:

- `django.core.mail.send_mail(...)`
- `django.core.mail.EmailMessage(...).send()`
- HTML email and attachments through `EmailMessage`

Agent rule:

- prefer standard Django mail objects first when they already fit the use case
- do not write a custom HTTP sender if the backend integration is enough

## Account Template Behaviour in Django

Public docs say the sender's account template is used by default.

Documented package behaviour:

- when using the email backend, set `email.use_account_template = False` before sending if you want to bypass the account template
- when using direct package API sending, pass `use_account_template=False`

Use this only when the app intentionally sends fully rendered HTML as-is.

## Direct Package API Sending

Public docs show direct package use via `send_email_via_api()`.

Implications:

- this is the preferred package-level escape hatch when the Django backend path is too limiting
- prefer this over hand-rolled `requests` code if the package already exposes the needed send behaviour

## Querying Sent Email and Delivery Status

Public docs show helpers:

- `get_email_detail(...)`
- `list_sent_emails(...)`
- `get_email_delivery_status(...)`

Documented delivery status values include:

- `pending`
- `delivered`
- `bounced`
- `complained`
- `failed`

Agent rule:

- if product code tracks post-send state, use these documented helpers/statuses rather than inventing your own interpretation of delivery events

## Working with Flows in Django

Public docs show helpers such as:

- `subscribe_to_flow(...)`
- `unsubscribe_from_flow(...)`
- `list_flow_subscriptions(...)`
- `delete_all_subscriptions()`
- `create_recipient(...)`
- `list_recipients()`
- `set_recipient_flags(...)`
- `clear_recipient_flags(...)`

Documented `subscribe_to_flow(...)` parameters include:

- `flow_trigger`
- `email`
- `first_name`
- `last_name`
- `flags`
- optional `client`

Important public constraint:

- flow features may not be enabled for every account
- auth/feature failures can present as `401`/`403`

So Django code should not assume flow APIs are universally available.

## Error Handling

Public docs say package functions raise exceptions from `notifiedby.exceptions`.

Documented exception types include:

- `NotifiedByError`
- `NotifiedByConfigError`
- `NotifiedByAPIError`
- `NotifiedByValidationError`
- `NotifiedByAuthError`
- `NotifiedByNotFoundError`

Agent rules:

- catch specific NotifiedBy exceptions where useful
- do not collapse everything into bare `except Exception`
- treat config/auth/validation problems differently in user-facing or operational code

Documented meanings:

- config error: missing/invalid local configuration such as absent API key
- validation error: HTTP `400`
- auth error: HTTP `401` or `403`
- not found: HTTP `404`
- API error: other API-side failures

## Custom Client Support

Public docs describe `NotifiedByClient` with documented options:

- `api_key`
- `base_url`

Documented use cases include:

- testing against mock/local servers
- multi-tenant apps using different API keys per organisation
- environment-specific endpoints

Agent rules:

- use `NotifiedByClient` instead of monkey-patching globals when per-tenant or per-environment configuration is needed
- do not invent extra constructor options beyond the public docs
- remember the default base URL in public docs is `https://api.notifiedby.com`

## Recommended Django Integration Shape

Prefer this structure:

- settings layer for `NOTIFIEDBY_API_KEY` and optional `NOTIFIEDBY_ENCRYPTION_KEY`
- dedicated integration module or service wrapper
- thin views/signals/tasks that call that wrapper
- standard Django mail APIs when the email backend is sufficient
- package helper functions for flows/recipients/flags
- targeted exception handling around integration boundaries

Good boundary examples:

- `services/notifiedby.py`
- `integrations/notifiedby/client.py`
- `emails/providers/notifiedby.py`

## Testing Guidance

Use public package features in tests without assuming private internals.

Recommended approach:

- mock at the wrapper or package-call boundary for unit tests
- use `NotifiedByClient(base_url=...)` for controlled test environments when integration-style tests are needed
- test both success and failure paths, especially config/auth/validation errors
- if using flows, test the behaviour when flows are not enabled as well as when they are

Do not make tests depend on undocumented package internals.

## Verification Checklist

Before finishing Django integration work, verify:

- `django-notifiedby` is the documented package being used
- `'notifiedby'` is added to `INSTALLED_APPS` when required
- `NOTIFIEDBY_API_KEY` is configured through approved settings/secrets
- `EMAIL_BACKEND` is only enabled when intended
- use of `send_mail()` / `EmailMessage` / `send_email_via_api()` matches the documented package path
- any `use_account_template=False` usage is intentional
- delivery-status code checks use documented values only
- flow code handles possible `401`/`403` feature/auth failures
- exception handling uses documented `notifiedby.exceptions` classes where appropriate
- any multi-tenant or test-specific behaviour uses `NotifiedByClient`
- no code depends on guessed private package internals

## Escalation Rule

If the required behaviour is not covered by the public Django package docs or the public NotifiedBy API docs, stop and flag the gap. Do not invent a package method, hidden setting, or undocumented workflow.
