# django-notifiedby

Integrate `django-notifiedby` to send emails and manage email flows through the NotifiedBy API.

## Features

- **Email Backend**: Drop-in replacement for Django's email backend
- **Flow Management**: Subscribe users to automated email sequences (onboarding, drip campaigns, newsletters)
- **Recipient Management**: Create and manage recipients with metadata and flags for segmentation
- **Email Querying**: Check delivery status and query email history
- **Simple API**: Pythonic functions that are easy to use

## Installation

Using [pip](https://pip.pypa.io/en/stable/):

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

## Configuration

Add the `notifiedby` email backend to your Django settings:

```python
# my_project/settings.py

# Required: Set your NotifiedBy API key
EMAIL_BACKEND = "notifiedby.NotifiedByEmailBackend"
NOTIFIEDBY_API_KEY = "your-api-key-here"

# Optional: Add encryption key if you use encrypted emails
NOTIFIEDBY_ENCRYPTION_KEY = "your-encryption-key"  # Optional
```

API Keys can be purchased from [NotifiedBy.com](https://notifiedby.com/)

## Usage

### Sending Transactional Emails

The built-in Django emails will automatically send through NotifiedBy:

```python
from django.core.mail import EmailMessage

email = EmailMessage(
    subject='Subject Here',
    body='Body of the email here',
    from_email='your_email@example.com',
    to=['recipient@example.com'],
    cc=['cc_recipient@example.com'],  # CC
    bcc=['bcc_recipient@example.com'],  # BCC
)

# Attach a file
email.attach('example.pdf', b'Some binary data', 'application/pdf')
email.send()
```

### Flow Management

Subscribe users to automated email sequences:

```python
from notifiedby import subscribe_to_flow, unsubscribe_from_flow

# Subscribe a user to a welcome email series
subscribe_to_flow(
    flow_trigger='welcome_series',
    email='user@example.com',
    first_name='John',
    last_name='Doe',
    flags=['free_plan', 'new_user']
)

# Unsubscribe from a flow
unsubscribe_from_flow('newsletter_weekly', 'user@example.com')

# List all flow subscriptions
from notifiedby import list_flow_subscriptions

subscriptions = list_flow_subscriptions(page=1)
print(f"Total subscriptions: {subscriptions['count']}")
for sub in subscriptions['results']:
    print(f"{sub['recipient_email']} - {sub['flow_name']} ({sub['status']})")
```

#### Common Flow Patterns

**User Signup Flow:**
```python
from django.contrib.auth.signals import user_signed_up
from notifiedby import subscribe_to_flow

def on_user_signup(sender, user, **kwargs):
    """Automatically subscribe new users to onboarding flow"""
    subscribe_to_flow(
        flow_trigger='onboarding',
        email=user.email,
        first_name=user.first_name,
        flags=['free_plan']
    )

user_signed_up.connect(on_user_signup)
```

**Subscription Upgrade Flow:**
```python
from notifiedby import subscribe_to_flow, set_recipient_flags

def upgrade_to_premium(user):
    # Update user flags
    set_recipient_flags(user.email, ['premium', 'paid'])

    # Subscribe to premium onboarding
    subscribe_to_flow(
        flow_trigger='premium_onboarding',
        email=user.email,
        first_name=user.first_name
    )
```

### Recipient Management

Create and manage recipients with metadata:

```python
from notifiedby import (
    create_or_update_recipient,
    set_recipient_flags,
    clear_recipient_flags,
    list_recipients
)

# Create or update a recipient
recipient = create_or_update_recipient(
    email='user@example.com',
    first_name='Jane',
    last_name='Smith',
    flags=['premium', 'beta_tester']
)
print(f"Recipient ID: {recipient['sqid']}")

# Set flags for segmentation (adds to existing flags)
set_recipient_flags('user@example.com', ['annual_plan', 'power_user'])

# Clear specific flags
clear_recipient_flags('user@example.com', ['trial', 'pending'])

# List all recipients
recipients = list_recipients(page=1)
for recipient in recipients['results']:
    print(f"{recipient['email']} - Created: {recipient['created_at']}")
```

**Flags** are custom tags for segmentation (e.g., 'premium', 'churned', 'early_adopter'). Use them to:
- Trigger different email flows based on user status
- Filter and segment your audience
- Track user lifecycle stages

### Email Querying

Check email delivery status and query sent emails:

```python
from notifiedby import (
    get_email_detail,
    list_sent_emails,
    get_email_delivery_status,
    send_email_via_api
)
from django.core.mail import EmailMessage

# Send an email and get the ID
message = EmailMessage(
    subject='Test Email',
    body='Hello!',
    to=['user@example.com']
)
email_id = send_email_via_api(message)

# Check delivery status
if email_id:
    status = get_email_delivery_status(email_id)
    print(f"Delivery status: {status}")

# Get email details by ID
email = get_email_detail(sqid='abc123xyz')
print(f"Subject: {email['subject']}")
print(f"Sent to: {email['recipient']}")

# Get most recent email to a recipient
email = get_email_detail(email='user@example.com')

# List all sent emails
emails = list_sent_emails(page=1)
print(f"Total emails sent: {emails['count']}")

# Filter by recipient
user_emails = list_sent_emails(recipient='user@example.com')
for email in user_emails['results']:
    print(f"{email['subject']} - {email['created_on']}")
```

## Error Handling

The library provides specific exceptions for different error scenarios:

```python
from notifiedby import subscribe_to_flow
from notifiedby.exceptions import (
    NotifiedByAPIError,
    NotifiedByValidationError,
    NotifiedByAuthError,
    NotifiedByConfigError,
    NotifiedByNotFoundError,
)

try:
    subscribe_to_flow('welcome', 'user@example.com')
except NotifiedByConfigError as e:
    # API key not configured
    print(f"Configuration error: {e}")
except NotifiedByValidationError as e:
    # Invalid data (bad email, unknown flow, etc.)
    print(f"Validation error: {e}")
except NotifiedByAuthError as e:
    # Authentication failed (invalid API key)
    print(f"Auth error: {e}")
except NotifiedByNotFoundError as e:
    # Resource not found
    print(f"Not found: {e}")
except NotifiedByAPIError as e:
    # Other API errors
    print(f"API error: {e.status_code} - {e}")
```

## Advanced Usage

### Custom Client for Testing

You can inject a custom client for testing or using a different API endpoint:

```python
from notifiedby import NotifiedByClient, subscribe_to_flow

# Create a test client
test_client = NotifiedByClient(
    api_key='test_key',
    base_url='http://localhost:8000'
)

# Use with any function
subscribe_to_flow(
    'welcome',
    'test@example.com',
    client=test_client
)
```

### Direct API Usage

For more control, use the `NotifiedByClient` directly:

```python
from notifiedby import NotifiedByClient

client = NotifiedByClient()

# Make custom API calls
response = client.post('/flows/subscribe/', json={
    'flow_trigger': 'custom_flow',
    'email': 'user@example.com'
})

# GET request with parameters
subscriptions = client.get('/flows/subscriptions/', params={'page': 2})

# DELETE request
client.delete('/flows/subscriptions/')
```

## Contributing

In order to contribute to the plugin itself so just need to clone the repo, create the venv and install the dev requirements.

```bash
git clone git@gitlab.com:rezero-consulting/django-notifiedby.git
python -m venv venv
source venv/bin/activate
pip install -r dev-requirements.txt
```

## Updating PyPi

This plugin is hosted on [PiPy](https://pypi.org/project/django-notifiedby/) and needs to be uploaded when anything is updated

When a change has been make you need to update the version number in pyproject.toml
You then need to build the distribution files
```bash
cd django-notifiedby
python -m build
```
This will build new version files in the dist folder and update some other files in the django_notifiedby.egg-info folder.
All these files can be added to the repo but only the two new files in the dist folder need uploading to PyPi.

In order to upload this plugin to PyPi you will need a valid key for the PyPi project. Contact rich@rezero.net about this.
Once you have a key upload to PyPi with:

```bash
cd django-notifiedby
twine upload dist/django_nofitiedby-[version]*
```
Where [version] is the version you've just built.
