> ## 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.

# 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.net/v1/YOUR_USERNAME'
)
```

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`.

## 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
```
