Metadata-Version: 2.4
Name: emailit
Version: 2.0.1
Summary: Official Python SDK for the Emailit Email API
Author-email: Emailit <support@emailit.com>
License: MIT
Project-URL: Homepage, https://emailit.com
Project-URL: Documentation, https://emailit.com/docs
Project-URL: Repository, https://github.com/emailit/emailit-python
Project-URL: Changelog, https://github.com/emailit/emailit-python/releases
Keywords: emailit,email,api,sdk
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Email
Classifier: Typing :: Typed
Requires-Python: >=3.7
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.20
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: responses>=0.20; extra == "dev"
Dynamic: license-file

# Emailit Python

[![Tests](https://img.shields.io/github/actions/workflow/status/emailit/emailit-python/tests.yml?label=tests&style=for-the-badge&labelColor=111827)](https://github.com/emailit/emailit-python/actions)
[![PyPI Version](https://img.shields.io/pypi/v/emailit?style=for-the-badge&labelColor=111827)](https://pypi.org/project/emailit/)
[![License](https://img.shields.io/github/license/emailit/emailit-python?style=for-the-badge&labelColor=111827)](https://github.com/emailit/emailit-python/blob/main/LICENSE)

The official Python SDK for the [Emailit](https://emailit.com) Email API.

## Requirements

- Python 3.7+
- [Requests](https://requests.readthedocs.io/) 2.20+

## Installation

```bash
pip install emailit
```

## Getting Started

```python
from emailit import EmailitClient

client = EmailitClient("your_api_key")

email = client.emails.send({
    "from": "hello@yourdomain.com",
    "to": ["user@example.com"],
    "subject": "Hello from Emailit",
    "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
})

print(email.id)      # em_abc123...
print(email.status)  # pending
```

All service methods return typed resource objects (`Email`, `Domain`, `Contact`, etc.) with direct attribute access -- just like the Stripe SDK.

## Available Services

| Service | Property | Description |
|---------|----------|-------------|
| Emails | `client.emails` | Send, list, get, cancel, retry emails |
| Domains | `client.domains` | Create, verify, list, manage sending domains |
| API Keys | `client.api_keys` | Create, list, manage API keys |
| Audiences | `client.audiences` | Create, list, manage audiences |
| Subscribers | `client.subscribers` | Add, list, manage subscribers in audiences |
| Templates | `client.templates` | Create, list, publish email templates |
| Suppressions | `client.suppressions` | Create, list, manage suppressed addresses |
| Email Verifications | `client.email_verifications` | Verify email addresses |
| Email Verification Lists | `client.email_verification_lists` | Create, list, get results, export |
| Webhooks | `client.webhooks` | Create, list, manage webhooks |
| Contacts | `client.contacts` | Create, list, manage contacts |
| Events | `client.events` | List and retrieve events |

## Usage

### Emails

#### Send an email

```python
email = client.emails.send({
    "from": "hello@yourdomain.com",
    "to": ["user@example.com"],
    "subject": "Hello from Emailit",
    "html": "<h1>Welcome!</h1>",
})

print(email.id)
print(email.status)
```

#### Send with a template

```python
email = client.emails.send({
    "from": "hello@yourdomain.com",
    "to": "user@example.com",
    "template": "welcome_email",
    "variables": {
        "name": "John Doe",
        "company": "Acme Inc",
    },
})
```

#### Send with attachments

```python
import base64

email = client.emails.send({
    "from": "invoices@yourdomain.com",
    "to": "customer@example.com",
    "subject": "Your Invoice #12345",
    "html": "<p>Please find your invoice attached.</p>",
    "attachments": [
        {
            "filename": "invoice.pdf",
            "content": base64.b64encode(open("invoice.pdf", "rb").read()).decode(),
            "content_type": "application/pdf",
        },
    ],
})
```

#### Schedule an email

```python
email = client.emails.send({
    "from": "reminders@yourdomain.com",
    "to": "user@example.com",
    "subject": "Appointment Reminder",
    "html": "<p>Your appointment is tomorrow at 2 PM.</p>",
    "scheduled_at": "2026-01-10T09:00:00Z",
})

print(email.status)        # scheduled
print(email.scheduled_at)  # 2026-01-10T09:00:00Z
```

#### List emails

```python
emails = client.emails.list({"page": 1, "limit": 10})

for email in emails:
    print(f"{email.id} — {email.status}")

if emails.has_more():
    # fetch next page
    pass
```

#### Cancel / Retry

```python
client.emails.cancel("em_abc123")
client.emails.retry("em_abc123")
```

---

### Domains

```python
# Create a domain
domain = client.domains.create({
    "name": "example.com",
    "track_loads": True,
    "track_clicks": True,
})
print(domain.id)

# Verify DNS
domain = client.domains.verify("sd_123")

# List all domains
domains = client.domains.list()

# Get a domain
domain = client.domains.get("sd_123")

# Update a domain
domain = client.domains.update("sd_123", {"track_clicks": False})

# Delete a domain
client.domains.delete("sd_123")
```

---

### API Keys

```python
# Create an API key
key = client.api_keys.create({
    "name": "Production Key",
    "scope": "full",
})
print(key.key)  # only available on create

# List all API keys
keys = client.api_keys.list()

# Get an API key
key = client.api_keys.get("ak_123")

# Update an API key
client.api_keys.update("ak_123", {"name": "Renamed Key"})

# Delete an API key
client.api_keys.delete("ak_123")
```

---

### Audiences

```python
# Create an audience
audience = client.audiences.create({"name": "Newsletter"})
print(audience.id)
print(audience.token)

# List audiences
audiences = client.audiences.list()

# Get an audience
audience = client.audiences.get("aud_123")

# Update an audience
client.audiences.update("aud_123", {"name": "Updated Newsletter"})

# Delete an audience
client.audiences.delete("aud_123")
```

---

### Subscribers

Subscribers belong to an audience, so the audience ID is always the first argument.

```python
# Add a subscriber
subscriber = client.subscribers.create("aud_123", {
    "email": "user@example.com",
    "first_name": "John",
    "last_name": "Doe",
})

# List subscribers in an audience
subscribers = client.subscribers.list("aud_123")

# Get a subscriber
subscriber = client.subscribers.get("aud_123", "sub_456")

# Update a subscriber
client.subscribers.update("aud_123", "sub_456", {
    "first_name": "Jane",
})

# Delete a subscriber
client.subscribers.delete("aud_123", "sub_456")
```

---

### Templates

```python
# Create a template
result = client.templates.create({
    "name": "Welcome",
    "subject": "Welcome!",
    "html": "<h1>Hi {{name}}</h1>",
})

# List templates
templates = client.templates.list()

# Get a template
template = client.templates.get("tem_123")

# Update a template
client.templates.update("tem_123", {"subject": "New Subject"})

# Publish a template
client.templates.publish("tem_123")

# Delete a template
client.templates.delete("tem_123")
```

---

### Suppressions

```python
# Create a suppression
suppression = client.suppressions.create({
    "email": "spam@example.com",
    "type": "hard_bounce",
    "reason": "Manual suppression",
})

# List suppressions
suppressions = client.suppressions.list()

# Get a suppression
suppression = client.suppressions.get("sup_123")

# Update a suppression
client.suppressions.update("sup_123", {"reason": "Updated"})

# Delete a suppression
client.suppressions.delete("sup_123")
```

---

### Email Verifications

```python
result = client.email_verifications.verify({
    "email": "test@example.com",
})

print(result.status)  # valid
print(result.score)   # 0.95
print(result.risk)    # low
```

---

### Email Verification Lists

```python
# Create a verification list
vlist = client.email_verification_lists.create({
    "name": "Marketing List Q1",
    "emails": [
        "user1@example.com",
        "user2@example.com",
        "user3@example.com",
    ],
})
print(vlist.id)      # evl_abc123...
print(vlist.status)  # pending

# List all verification lists
vlists = client.email_verification_lists.list()

# Get a verification list
vlist = client.email_verification_lists.get("evl_abc123")
print(vlist.stats["successful_verifications"])

# Get verification results
results = client.email_verification_lists.results("evl_abc123", {"page": 1, "limit": 50})

for result in results:
    print(f"{result.email} — {result.result}")

# Export results as XLSX
response = client.email_verification_lists.export("evl_abc123")
with open("results.xlsx", "wb") as f:
    f.write(response.body.encode())
```

---

### Webhooks

```python
# Create a webhook
webhook = client.webhooks.create({
    "name": "My Webhook",
    "url": "https://example.com/hook",
    "all_events": True,
    "enabled": True,
})
print(webhook.id)

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

# Get a webhook
webhook = client.webhooks.get("wh_123")

# Update a webhook
client.webhooks.update("wh_123", {"enabled": False})

# Delete a webhook
client.webhooks.delete("wh_123")
```

---

### Contacts

```python
# Create a contact
contact = client.contacts.create({
    "email": "user@example.com",
    "first_name": "John",
    "last_name": "Doe",
})
print(contact.id)

# List contacts
contacts = client.contacts.list()

# Get a contact
contact = client.contacts.get("con_123")

# Update a contact
client.contacts.update("con_123", {"first_name": "Jane"})

# Delete a contact
client.contacts.delete("con_123")
```

---

### Events

```python
# List events
events = client.events.list({"type": "email.delivered"})

for event in events:
    print(event.type)

# Get an event
event = client.events.get("evt_123")
print(event.type)
print(event.data["email_id"])
```

## Webhook Events

The SDK provides typed event classes for all Emailit webhook event types under the `emailit.events` module, plus a `WebhookSignature` class for verifying webhook request signatures.

### Verifying Webhook Signatures

```python
from emailit import WebhookSignature, ApiErrorException
from emailit.events import EmailDelivered

raw_body = request.body  # raw request body string
signature = request.headers["x-emailit-signature"]
timestamp = request.headers["x-emailit-timestamp"]
secret = "your_webhook_signing_secret"

try:
    event = WebhookSignature.verify(raw_body, signature, timestamp, secret)

    # event is automatically typed based on the event type
    print(event.type)      # e.g. "email.delivered"
    print(event.event_id)  # e.g. "evt_abc123"

    # Access the event data
    data = event.get_event_data()

    if isinstance(event, EmailDelivered):
        # Handle delivered email
        pass

except ApiErrorException as e:
    return Response(e.args[0], status=401)
```

You can disable replay protection by passing `tolerance=None`, or set a custom tolerance in seconds:

```python
# Skip replay check
event = WebhookSignature.verify(raw_body, signature, timestamp, secret, tolerance=None)

# Custom 10-minute tolerance
event = WebhookSignature.verify(raw_body, signature, timestamp, secret, tolerance=600)
```

### Available Event Types

**Emails:** `email.accepted`, `email.scheduled`, `email.delivered`, `email.bounced`, `email.attempted`, `email.failed`, `email.rejected`, `email.suppressed`, `email.received`, `email.complained`, `email.clicked`, `email.loaded`

**Domains:** `domain.created`, `domain.updated`, `domain.deleted`

**Audiences:** `audience.created`, `audience.updated`, `audience.deleted`

**Subscribers:** `subscriber.created`, `subscriber.updated`, `subscriber.deleted`

**Contacts:** `contact.created`, `contact.updated`, `contact.deleted`

**Templates:** `template.created`, `template.updated`, `template.deleted`

**Suppressions:** `suppression.created`, `suppression.updated`, `suppression.deleted`

**Email Verifications:** `email_verification.created`, `email_verification.updated`, `email_verification.deleted`

**Email Verification Lists:** `email_verification_list.created`, `email_verification_list.updated`, `email_verification_list.deleted`

Each event type has a corresponding class under `emailit.events` (e.g. `EmailDelivered`, `DomainCreated`). You can use `isinstance` checks or the `EVENT_TYPE` constant for routing:

```python
from emailit.events import EmailDelivered, EmailBounced, ContactCreated

if isinstance(event, EmailDelivered):
    handle_delivered(event)
elif isinstance(event, EmailBounced):
    handle_bounce(event)
elif isinstance(event, ContactCreated):
    handle_new_contact(event)
else:
    print(f"Unhandled: {event.type}")
```

## Error Handling

The SDK raises typed exceptions for API errors:

```python
from emailit import (
    ApiErrorException,
    AuthenticationException,
    InvalidRequestException,
    RateLimitException,
    UnprocessableEntityException,
    ApiConnectionException,
)

try:
    client.emails.send({...})
except AuthenticationException:
    # Invalid API key (401)
    pass
except InvalidRequestException:
    # Bad request or not found (400, 404)
    pass
except RateLimitException:
    # Too many requests (429)
    pass
except UnprocessableEntityException:
    # Validation failed (422)
    pass
except ApiConnectionException:
    # Network error
    pass
except ApiErrorException as e:
    # Any other API error
    print(e.http_status)
    print(e.http_body)
    print(e.json_body)
```

## License

MIT -- see [LICENSE](LICENSE) for details.
