Metadata-Version: 2.4
Name: pappymail
Version: 1.0.0
Summary: Official Python SDK for PappyMail - Send transactional emails, manage campaigns, and build email experiences.
Project-URL: Homepage, https://pappymall.com
Project-URL: Documentation, https://pappymall.com/docs/sdk/python
Project-URL: Repository, https://github.com/pappymall/pappymail-python
Project-URL: Issues, https://github.com/pappymall/pappymail-python/issues
Author-email: PappyMall <support@pappymall.com>
License-Expression: MIT
Keywords: email,email-api,mailchimp-alternative,marketing,pappymail,sendgrid-alternative,smtp,transactional
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: httpx>=0.25.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.21.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# PappyMail SDK for Python

Official Python SDK for [PappyMail](https://pappymall.com) - A powerful email platform combining transactional email, campaigns, and inbox capabilities.

## Installation

```bash
pip install pappymail
```

## Quick Start

```python
import asyncio
from pappymail import PappyMail

async def main():
    client = PappyMail("pm_live_your_api_key")
    
    response = await client.send_simple(
        from_email="noreply@yourdomain.com",
        to="user@example.com",
        subject="Hello from PappyMail!",
        html="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
    )
    
    print(f"Message ID: {response.message_id}")
    
    await client.close()

asyncio.run(main())
```

Or use as context manager:

```python
async with PappyMail("pm_live_your_api_key") as client:
    response = await client.send_simple(
        from_email="noreply@yourdomain.com",
        to="user@example.com",
        subject="Hello!",
        html="<h1>Welcome!</h1>",
    )
```

## Features

### Send Transactional Emails

```python
from pappymail import SendEmailRequest, EmailAddress, TrackingSettings

response = await client.mail.send(SendEmailRequest(
    **{
        "from": EmailAddress(email="noreply@yourdomain.com", name="Your App"),
        "to": [EmailAddress(email="user@example.com", name="John Doe")],
        "subject": "Order Confirmation",
        "html_content": "<h1>Order #12345</h1><p>Thank you!</p>",
        "text_content": "Order #12345 - Thank you!",
        "tracking": TrackingSettings(track_opens=True, track_clicks=True),
    }
))
```

### Send with Templates

```python
response = await client.mail.send(SendEmailRequest(
    **{
        "from": EmailAddress(email="noreply@yourdomain.com"),
        "to": [EmailAddress(email="user@example.com")],
        "template_id": "tmpl_welcome_email",
        "template_data": {
            "first_name": "John",
            "order_id": "12345",
        },
    }
))
```

### Send Bulk Personalized Emails

```python
from pappymail import Personalization

response = await client.mail.send(SendEmailRequest(
    **{
        "from": EmailAddress(email="noreply@yourdomain.com"),
        "subject": "Hello {{name}}!",
        "html_content": "<p>Hi {{name}}, welcome!</p>",
        "personalizations": [
            Personalization(
                to=[EmailAddress(email="alice@example.com")],
                substitutions={"name": "Alice"},
            ),
            Personalization(
                to=[EmailAddress(email="bob@example.com")],
                substitutions={"name": "Bob"},
            ),
        ],
    }
))
```

### With Attachments

```python
from pappymail import Attachment

response = await client.mail.send(SendEmailRequest(
    **{
        "from": EmailAddress(email="noreply@yourdomain.com"),
        "to": [EmailAddress(email="user@example.com")],
        "subject": "Your Invoice",
        "html_content": "<p>Please find your invoice attached.</p>",
        "attachments": [
            Attachment.from_file("/path/to/invoice.pdf"),
            Attachment.from_bytes("data.csv", csv_bytes, "text/csv"),
        ],
    }
))
```

### Manage Email Templates

```python
from pappymail import CreateTemplateRequest, UpdateTemplateRequest

# Create template
template = await client.templates.create(CreateTemplateRequest(
    name="Welcome Email",
    subject="Welcome, {{first_name}}!",
    html_content="<h1>Welcome, {{first_name}}!</h1>",
))

# List templates
templates, pagination = await client.templates.list()

# Update template
await client.templates.update(template.id, UpdateTemplateRequest(
    html_content="<h1>Welcome, {{first_name}}!</h1><p>We're excited!</p>",
))

# Delete template
await client.templates.delete(template.id)
```

### Email Campaigns

```python
from pappymail import CreateCampaignRequest

# Create campaign
campaign = await client.campaigns.create(CreateCampaignRequest(
    name="Summer Sale",
    subject="Don't miss our Summer Sale!",
    **{
        "from": EmailAddress(email="marketing@yourdomain.com", name="Your Store"),
        "contact_list_id": "list_abc123",
        "html_content": "<h1>Summer Sale!</h1><p>Up to 50% off!</p>",
        "track_opens": True,
        "track_clicks": True,
    }
))

# Send campaign
result = await client.campaigns.send(campaign.id)

# Or schedule for later
from datetime import datetime, timedelta
await client.campaigns.schedule(campaign.id, datetime.utcnow() + timedelta(days=1))

# Get campaign stats
stats = await client.campaigns.get_stats(campaign.id)
print(f"Opens: {stats.opened}, Clicks: {stats.clicked}")
```

### Contact Management

```python
from pappymail import CreateContactListRequest, CreateContactRequest, UpsertContactRequest

# Create contact list
contact_list = await client.contacts.create_list(CreateContactListRequest(
    name="Newsletter Subscribers",
    description="Users subscribed to our newsletter",
))

# Add contact
contact = await client.contacts.add(contact_list.id, CreateContactRequest(
    email="user@example.com",
    first_name="John",
    last_name="Doe",
    custom_fields={"plan": "premium"},
))

# Import contacts
import_result = await client.contacts.import_contacts(contact_list.id, [
    CreateContactRequest(email="alice@example.com", first_name="Alice"),
    CreateContactRequest(email="bob@example.com", first_name="Bob"),
])

# Upsert contact
await client.contacts.upsert(UpsertContactRequest(
    email="user@example.com",
    list_id=contact_list.id,
    first_name="John Updated",
))
```

### Domain Management

```python
# Add domain
domain = await client.domains.add("yourdomain.com")

# Get DNS records to configure
for record in domain.dns_records or []:
    print(f"{record.type}: {record.name} -> {record.value}")

# Verify domain
verification = await client.domains.verify(domain.id)
if verification.is_verified:
    print("Domain verified successfully!")
```

### Webhooks

```python
# Create webhook
webhook = await client.webhooks.create(
    "https://yourapp.com/webhooks/email",
    ["delivered", "opened", "clicked", "bounced"],
)

# List webhooks
webhooks = await client.webhooks.list()

# Delete webhook
await client.webhooks.delete(webhook.id)

# Verify webhook signature (in your Flask/FastAPI endpoint)
from pappymail import PappyMail

@app.post("/webhooks/email")
async def handle_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("X-PappyMail-Signature", "")
    
    if not PappyMail.verify_webhook_signature(payload.decode(), signature, "your_secret"):
        raise HTTPException(status_code=401)
    
    # Process webhook...
    return {"status": "ok"}
```

### Analytics

```python
from datetime import datetime, timedelta

# Get stats for date range
stats = await client.stats.get(
    start_date=datetime.utcnow() - timedelta(days=30),
    end_date=datetime.utcnow(),
    aggregation="day",
)

for day in stats:
    print(f"{day.date}: Sent={day.sent}, Opened={day.opened}")

# Get global account stats
global_stats = await client.stats.get_global()
print(f"Daily usage: {global_stats.daily_usage}/{global_stats.daily_limit}")
```

## Configuration

### Full Options

```python
from pappymail import PappyMail, PappyMailOptions

client = PappyMail(PappyMailOptions(
    api_key="pm_live_your_api_key",
    base_url="https://pappymall.com/api/v1",  # default
    timeout=30.0,  # seconds
    max_retries=3,  # default
    enable_retry=True,  # default
))
```

### Sandbox Environment

```python
client = PappyMail(PappyMailOptions(
    api_key="pm_test_your_api_key",
    base_url="https://pappymall.com/api/v1/sandbox",
))
```

## Error Handling

```python
from pappymail import (
    PappyMailError,
    ValidationError,
    RateLimitError,
    UnauthorizedError,
)

try:
    response = await client.mail.send(request)
except ValidationError as e:
    print(f"Validation error: {e.message}")
    for detail in e.details:
        print(f"  - {detail.field}: {detail.message}")
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after} seconds")
except UnauthorizedError:
    print("Invalid API key")
except PappyMailError as e:
    print(f"Error: {e.code} - {e.message}")
```

## Requirements

- Python 3.8+
- httpx
- pydantic

## License

MIT License - see LICENSE file for details.

## Support

- Documentation: https://pappymall.com/Public/MailApi
- Contact: https://pappymall.com/Public/Contact
- Email: support@pappymall.com
