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

# Advanced

> Advanced Lettr Python SDK features: multiple recipients, reply-to, typed error handling, the async client, and Django integration patterns.

<Note>
  This guide covers advanced Python SDK features. If you're new to the SDK, start with the [Python Quickstart](/quickstart/python/quickstart).
</Note>

## Advanced Features

### Multiple Recipients

Add multiple To, CC, and BCC recipients:

```python theme={null}
response = client.emails.send(
    from_email="notifications@yourdomain.com",
    to=["user1@example.com", "user2@example.com"],
    cc=["cc@example.com", "cc2@example.com"],
    bcc=["bcc@example.com"],
    subject="Team notification",
    html="<p>This email has multiple recipients.</p>",
)
```

<Note>
  BCC recipients are hidden from all other recipients. They receive the email but their addresses are not visible in the headers.
</Note>

### Reply-To Address

Specify a different reply-to address:

```python theme={null}
response = client.emails.send(
    from_email="notifications@yourdomain.com",
    reply_to="support@yourdomain.com",
    to=["user@example.com"],
    subject="Support notification",
    html="<p>Click reply to contact our support team.</p>",
)
```

### Attachments

Add file attachments to your emails:

```python theme={null}
import base64
import lettr

# Read file and encode to base64
with open("invoice.pdf", "rb") as f:
    file_data = f.read()
    encoded_content = base64.b64encode(file_data).decode("utf-8")

response = client.emails.send(
    from_email="billing@yourdomain.com",
    to=["customer@example.com"],
    subject="Your invoice",
    html="<p>Please find your invoice attached.</p>",
    attachments=[
        lettr.Attachment(
            name="invoice.pdf",
            type="application/pdf",
            data=encoded_content,
        )
    ],
)
```

Attach multiple files:

```python theme={null}
import base64
import lettr

def encode_file(filepath):
    with open(filepath, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

attachments = [
    lettr.Attachment(
        name="document.pdf",
        type="application/pdf",
        data=encode_file("document.pdf"),
    ),
    lettr.Attachment(
        name="logo.png",
        type="image/png",
        data=encode_file("logo.png"),
    ),
]

response = client.emails.send(
    from_email="sender@yourdomain.com",
    to=["recipient@example.com"],
    subject="Files attached",
    html="<p>See attached files.</p>",
    attachments=attachments,
)
```

<Warning>
  Attachments must be base64-encoded. The total size of all attachments should not exceed 10MB. Larger files should be hosted and linked instead.
</Warning>

### Templates

Send emails using Lettr-managed templates:

```python theme={null}
response = client.emails.send(
    from_email="notifications@yourdomain.com",
    to=["user@example.com"],
    template_slug="welcome-email",
    substitution_data={
        "name": "John Doe",
        "company": "Acme Corp",
        "verify_url": "https://example.com/verify/abc123",
        "support_email": "support@yourdomain.com",
    },
)
```

<Tip>
  Templates are managed in the [Lettr dashboard](https://app.lettr.com/templates). Use merge tags to personalize content without rebuilding HTML in your code.
</Tip>

### Custom Headers

Add custom email headers:

```python theme={null}
response = client.emails.send(
    from_email="notifications@yourdomain.com",
    to=["user@example.com"],
    subject="Custom headers example",
    html="<p>This email has custom headers.</p>",
    headers={
        "X-Campaign-ID": "summer-2024",
        "X-Priority": "high",
        "X-Mailer": "Acme Mailer v1.0",
    },
)
```

### Tracking

Enable open and click tracking:

```python theme={null}
response = client.emails.send(
    from_email="marketing@yourdomain.com",
    to=["user@example.com"],
    subject="Newsletter",
    html="<p>Check out <a href='https://example.com'>our website</a>!</p>",
    options=lettr.EmailOptions(
        click_tracking=True,
        open_tracking=True,
    ),
)
```

<Info>
  Open tracking works by embedding a transparent pixel image. Click tracking rewrites links to go through Lettr's tracking domain. Both features respect user privacy and comply with email regulations.
</Info>

### Metadata

Attach custom metadata for tracking and filtering:

```python theme={null}
response = client.emails.send(
    from_email="notifications@yourdomain.com",
    to=["user@example.com"],
    subject="Order confirmation",
    html="<p>Your order has been confirmed.</p>",
    metadata={
        "user_id": "12345",
        "order_id": "ORD-98765",
        "campaign": "abandoned-cart",
        "environment": "production",
    },
)
```

Metadata is returned in webhook events and can be used to correlate emails with your application data.

## Error Handling

The SDK provides structured exception types for different error scenarios:

```python theme={null}
import lettr

try:
    response = client.emails.send(
        from_email="invalid@unverified-domain.com",
        to=["user@example.com"],
        subject="Test",
        html="<p>Test</p>",
    )
    print(f"Email sent: {response.request_id}")
except lettr.ValidationError as e:
    # 422 validation errors
    print(f"Validation failed: {e.message}")
    for field, messages in e.errors.items():
        print(f"  {field}: {messages}")
except lettr.AuthenticationError as e:
    # 401 authentication errors
    print(f"Authentication failed: {e.message}")
except lettr.ForbiddenError as e:
    # 403 forbidden
    print(f"Forbidden: {e.message}")
except lettr.NotFoundError as e:
    # 404 not found
    print(f"Not found: {e.message}")
except lettr.RateLimitError as e:
    # 429 rate limit errors
    print(f"Rate limit exceeded: {e.message}")
except lettr.BadRequestError as e:
    # 400 bad request
    print(f"Bad request: {e.message} (code: {e.error_code})")
except lettr.ServerError as e:
    # 500 / 502 server-side error
    print(f"Server error: {e.message}")
except lettr.LettrError as e:
    # Base class — anything else from the SDK
    print(f"Lettr error: {e.message}")
except Exception as e:
    # Network or other errors
    print(f"Request failed: {e}")
```

### Common Error Scenarios

**Unverified domain:**

* Exception: `ValidationError`
* Message: "The from address domain is not verified"
* Solution: Verify your domain in the dashboard

**Invalid API key:**

* Exception: `AuthenticationError`
* Message: "Invalid API key"
* Solution: Check your API key is correct and active

**Rate limit exceeded:**

* Exception: `RateLimitError`
* Message: "Too many requests"
* Solution: Implement exponential backoff retry logic

## Async Support

The SDK provides full async support for non-blocking operations:

```python theme={null}
import asyncio
import lettr

async def send_emails():
    client = lettr.AsyncLettr(os.environ["LETTR_API_KEY"])

    # Send multiple emails concurrently
    tasks = []
    for recipient in ["user1@example.com", "user2@example.com", "user3@example.com"]:
        task = client.emails.send(
            from_email="notifications@yourdomain.com",
            to=[recipient],
            subject="Batch notification",
            html="<p>This is a batch email.</p>",
        )
        tasks.append(task)

    responses = await asyncio.gather(*tasks, return_exceptions=True)

    for i, response in enumerate(responses):
        if isinstance(response, Exception):
            print(f"Failed to send email {i + 1}: {response}")
        else:
            print(f"Sent email {i + 1}: {response.request_id}")

asyncio.run(send_emails())
```

### With Semaphore for Rate Limiting

Limit concurrent requests using asyncio.Semaphore:

```python theme={null}
import asyncio
import lettr

async def send_batch(client, recipients, max_concurrent=10):
    semaphore = asyncio.Semaphore(max_concurrent)

    async def send_with_limit(recipient):
        async with semaphore:
            return await client.emails.send(
                from_email="notifications@yourdomain.com",
                to=[recipient],
                subject="Batch email",
                html="<p>Hello!</p>",
            )

    tasks = [send_with_limit(recipient) for recipient in recipients]
    responses = await asyncio.gather(*tasks, return_exceptions=True)

    for i, response in enumerate(responses):
        if isinstance(response, Exception):
            print(f"Failed: {response}")
        else:
            print(f"Sent to {recipients[i]}: {response.request_id}")

async def main():
    client = lettr.AsyncLettr(os.environ["LETTR_API_KEY"])
    recipients = ["user1@example.com", "user2@example.com", "user3@example.com"]
    await send_batch(client, recipients, max_concurrent=5)

asyncio.run(main())
```

<Tip>
  For large batches, consider using a task queue like Celery or RQ to manage concurrent requests and handle failures gracefully.
</Tip>

## Django Integration

### Settings Configuration

Add Lettr configuration to your Django settings:

```python theme={null}
# settings.py
import os

LETTR_API_KEY = os.environ.get("LETTR_API_KEY")
```

### Email Service

Create an email service module:

```python theme={null}
# services/email.py
from django.conf import settings
import lettr

client = lettr.Lettr(settings.LETTR_API_KEY)

def send_welcome_email(recipient, name):
    """Send a welcome email to a new user."""
    response = client.emails.send(
        from_email="notifications@yourdomain.com",
        to=[recipient],
        subject=f"Welcome to Acme, {name}!",
        html=f"<h1>Welcome, {name}!</h1><p>Thanks for signing up.</p>",
    )
    return response.request_id
```

### Using in Views

Use the email service in your views:

```python theme={null}
# views.py
from django.http import JsonResponse
from .services.email import send_welcome_email

def register(request):
    # ... registration logic ...

    try:
        request_id = send_welcome_email(user.email, user.name)
        return JsonResponse({"success": True, "request_id": request_id})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)}, status=500)
```

## Best Practices

### Use Environment Variables

Never hardcode API keys. Use environment variables or a secrets manager:

```python theme={null}
import os

api_key = os.environ.get("LETTR_API_KEY")
if not api_key:
    raise ValueError("LETTR_API_KEY is required")

client = lettr.Lettr(api_key)
```

### Validate Before Sending

Validate email addresses before making API calls:

```python theme={null}
import re

def is_valid_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

if not is_valid_email(recipient):
    print(f"Invalid email address: {recipient}")
    return
```

### Log Request IDs

Always log the `request_id` from successful sends for tracking and debugging:

```python theme={null}
import logging

logger = logging.getLogger(__name__)

response = client.emails.send(...)
logger.info(
    "Email sent successfully",
    extra={
        "request_id": response.request_id,
        "accepted": response.accepted,
        "to": to,
    },
)
```

### Handle Errors Gracefully

Implement retry logic for transient errors:

```python theme={null}
import time
import lettr

def send_with_retry(client, email_params, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = client.emails.send(**email_params)
            print(f"Email sent: {response.request_id}")
            return response.request_id
        except lettr.ValidationError:
            # Don't retry validation errors
            raise
        except Exception as e:
            if attempt < max_retries:
                backoff = (attempt + 1) * 1.0
                print(f"Attempt {attempt + 1} failed, retrying in {backoff}s: {e}")
                time.sleep(backoff)
            else:
                raise

    raise Exception("Max retries exceeded")
```

### Use Context Managers

Use context managers to ensure proper cleanup:

```python theme={null}
import lettr
import httpx

with httpx.Client(timeout=30.0) as http_client:
    client = lettr.Lettr(api_key, http_client=http_client)
    response = client.emails.send(...)
```

### Reuse the Client

Create a single client instance and reuse it across requests:

```python theme={null}
# Good: Create once, reuse
import lettr

client = lettr.Lettr(os.environ["LETTR_API_KEY"])

def send_email(to, subject, html):
    return client.emails.send(
        from_email="notifications@yourdomain.com",
        to=[to],
        subject=subject,
        html=html,
    )
```

```python theme={null}
# Bad: Creating new client for each request
def send_email(to, subject, html):
    client = lettr.Lettr(os.environ["LETTR_API_KEY"])  # Don't do this
    return client.emails.send(...)
```

## Type Hints

The SDK includes full type hints for better IDE support:

```python theme={null}
from typing import List, Dict, Optional
import lettr

def send_notification(
    client: lettr.Lettr,
    recipients: List[str],
    subject: str,
    html: str,
    metadata: Optional[Dict[str, str]] = None,
) -> str:
    """Send a notification email and return the request ID."""
    response = client.emails.send(
        from_email="notifications@yourdomain.com",
        to=recipients,
        subject=subject,
        html=html,
        metadata=metadata,
    )
    return response.request_id
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Unverified domain error">
    If you see "The from address domain is not verified":

    * Verify your domain in the [Lettr dashboard](https://app.lettr.com/domains)
    * Ensure the `from_email` parameter uses the verified domain
    * Wait for DNS propagation (can take up to 48 hours)
    * See [Domain Verification](/knowledge-base/troubleshooting/domain-verification) for help
  </Accordion>

  <Accordion title="Authentication failed (401 error)">
    If you see authentication errors:

    * Check your API key is correct and starts with `lttr_`
    * Verify the key is 68 characters total (prefix + 64 hex chars)
    * Ensure the key hasn't been revoked in the [dashboard](https://app.lettr.com/api-keys)
    * Confirm you're reading from the correct environment variable
  </Accordion>

  <Accordion title="Request timeout">
    If requests timeout:

    * Increase the httpx client timeout
    * Check your network connectivity and firewall settings
    * Verify `app.lettr.com` is reachable
    * Use a custom httpx client with longer timeout settings
  </Accordion>

  <Accordion title="Import or module errors">
    If you see "ModuleNotFoundError" or import errors:

    * Verify the package is installed: `pip list | grep lettr`
    * Reinstall the package: `pip install --upgrade lettr`
    * Check your Python version is 3.8 or later: `python --version`
    * Activate your virtual environment if using one
  </Accordion>

  <Accordion title="Rate limit exceeded (429 error)">
    If you're hitting rate limits:

    * Implement exponential backoff retry logic (see Best Practices)
    * Use asyncio.Semaphore for controlled concurrent sending
    * Consider upgrading your Lettr plan for higher limits
    * Spread requests over time instead of bursts
  </Accordion>

  <Accordion title="Async/await errors">
    If you encounter async-related errors:

    * Use `AsyncLettr` for async code, not `Lettr`
    * Ensure you're using `await` with async methods
    * Run async functions with `asyncio.run()` at the top level
    * Check that your async runtime is properly configured
  </Accordion>
</AccordionGroup>

## What's Next

<CardGroup cols={2}>
  <Card title="Flask Integration" icon="flask" href="/quickstart/python/send-with-flask">
    Use Lettr with Flask applications
  </Card>

  <Card title="FastAPI Integration" icon="bolt" href="/quickstart/python/send-with-fastapi">
    Use Lettr with FastAPI applications
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Complete API documentation
  </Card>

  <Card title="Templates" icon="file-code" href="/learn/templates/introduction">
    Use Lettr-managed templates
  </Card>
</CardGroup>
