> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paubox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK email client

> Full reference for the Paubox Python SDK email client: composing messages, sending email, tracking delivery, and error handling.

## Instantiation

```python theme={null}
from paubox import PauboxApiClient

# Read credentials from environment variables
client = PauboxApiClient()

# Or pass credentials directly
client = PauboxApiClient(
    api_key = 'YOUR_API_KEY',
    host    = 'https://api.paubox.com/v1'
)
```

See [Authentication](/python-sdk/authentication) for both approaches.

## Composing a message

Use the `Mail` helper to compose messages. It handles formatting and automatically base64-encodes HTML content before sending.

```python theme={null}
from paubox.helpers.mail import Mail

mail = Mail(
    from_      = 'sender@yourdomain.com',    # required
    subject    = 'Your results are ready',   # required
    recipients = ['alice@example.com'],       # required; list of email strings
    content    = {                            # required; at least one key
        'text/plain': 'Plain text body.',
        'text/html':  '<p>HTML body.</p>'    # auto base64-encoded
    },
    optional_headers = {                      # optional
        'reply_to':               'support@yourdomain.com',
        'cc':                     ['manager@example.com'],
        'bcc':                    'audit@example.com',      # str or list
        'allowNonTLS':            False,
        'forceSecureNotification': False,
        'attachments':            [attachment]
    }
)
```

Call `mail.get()` to get the formatted dict ready for the API.

### Attachments

```python theme={null}
import base64

with open('report.pdf', 'rb') as f:
    encoded = base64.b64encode(f.read()).decode('utf-8')

attachment = {
    'fileName':    'report.pdf',
    'contentType': 'application/pdf',
    'content':     encoded
}
```

Pass attachments via the `optional_headers` dict:

```python theme={null}
optional_headers = {
    'attachments': [attachment]
}
```

## Send a message

```python theme={null}
response = client.send(mail.get())
print('Status:', response.status_code)
print('Tracking ID:', response.to_dict.get('sourceTrackingId'))
```

## Check delivery status

```python theme={null}
tracking_id = response.to_dict['sourceTrackingId']

disposition = client.get(tracking_id)
deliveries  = disposition.to_dict['data']['message']['message_deliveries']

for d in deliveries:
    print(d['recipient'], '→', d['status']['deliveryStatus'])
```

Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`.

## Scheduled send

Schedule a message for future delivery, check its status, reschedule, or cancel it.

### Schedule a message

```python theme={null}
response = client.schedule(mail.get(), '2025-12-25T15:00:00Z')
print('Tracking ID:', response.to_dict.get('sourceTrackingId'))
print('Scheduled for:', response.to_dict.get('scheduledAt'))
```

`scheduled_at` must be an ISO 8601 UTC datetime string in the future and within 30 days.

### Get scheduled message status

```python theme={null}
status = client.get_scheduled(source_tracking_id)
data = status.to_dict
print('State:', data.get('state'))          # "pending", "sent", "cancelled"
print('Scheduled for:', data.get('scheduledAt'))
```

### Reschedule a message

```python theme={null}
response = client.reschedule(source_tracking_id, '2025-12-26T10:00:00Z')
print('New time:', response.to_dict.get('scheduledAt'))
```

### Cancel a scheduled message

```python theme={null}
response = client.cancel_scheduled(source_tracking_id)
print('State:', response.to_dict.get('state'))  # "cancelled"
```

## The Response object

Both `send` and `get` return a `Response` object:

| Property | Description |
| - | - |
| `.status_code` | HTTP status code |
| `.headers` | Response headers dict |
| `.text` | Raw response body as a string |
| `.to_dict` | JSON-parsed response body, or `None` if not JSON |

## Error handling

Both methods raise `requests.exceptions.HTTPError` on non-2xx responses. Wrap calls in a try/except block:

```python theme={null}
import requests

try:
    response = client.send(mail.get())
except requests.exceptions.HTTPError as e:
    print('API error:', e.response.status_code, e.response.text)
```

The optional `handle_error` helper prints the error response body before re-raising:

```python theme={null}
from paubox.helpers.errors import handle_error

try:
    response = client.send(mail.get())
except requests.exceptions.HTTPError as e:
    handle_error(e)  # prints body, then re-raises
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.