Metadata-Version: 2.5
Name: mailengin
Version: 0.1.0
Summary: Official Python SDK for the MailEngin Email API
Project-URL: Homepage, https://mailengin.app
Project-URL: Repository, https://github.com/mailengin/mailengin-python-sdk
Project-URL: Issues, https://github.com/mailengin/mailengin-python-sdk/issues
Project-URL: Documentation, https://mailengin.app/dashboard/docs
Author: MailEngin
License: MIT License
        
        Copyright (c) 2026 MailEngin
        
        Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
License-File: LICENSE
Keywords: email,email-api,mailengin,transactional-email
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions>=4.6
Description-Content-Type: text/markdown

# MailEngin Python SDK

[![Python](https://img.shields.io/badge/Python-3.10%2B-3776ab.svg)](https://www.python.org/)
[![Typing](https://img.shields.io/badge/typing-strict-2563eb.svg)](https://mypy.readthedocs.io/)
[![License: MIT](https://img.shields.io/badge/License-MIT-111827.svg)](./LICENSE)

The official Python SDK for sending transactional email through [MailEngin](https://mailengin.app). It provides synchronous and asynchronous clients, typed dataclass models, configurable timeouts, and structured errors.

> [!IMPORTANT]
> This package is for server-side applications only. Never expose a MailEngin API key in browser, mobile, desktop, or other client-distributed code.

## Requirements

- Python 3.10 or newer
- A MailEngin API key
- A verified sending domain

## Installation

```bash
python -m pip install mailengin
```

For a specific version:

```bash
python -m pip install "mailengin==0.1.0"
```

## Before You Send

1. [Verify a sending domain](https://mailengin.app/dashboard/domains).
2. [Create an API key](https://mailengin.app/dashboard/api-keys) and save the full secret.
3. [Create and publish a Developer Template](https://mailengin.app/dashboard/dev-templates).
4. Copy the template API name, such as `welcome-email`.

Store the API key in a server-side environment variable:

```env
MAILENGIN_API_KEY=re_your_full_secret_key
```

MailEngin displays the full key only once. A masked key cannot authenticate requests.

## Quick Start

```python
import os

from mailengin import MailEngin, SendEmailRequest

with MailEngin(os.environ["MAILENGIN_API_KEY"]) as client:
    email = client.emails.send(
        SendEmailRequest(
            to="user@example.com",
            from_email="hello@yourdomain.com",
            template_name="welcome-email",
            variables={"first_name": "Asha"},
        )
    )

print(email.id)
```

The published template supplies the subject and HTML. Values in `variables` replace matching template variables such as `{{first_name}}`.

## Async Client

`AsyncMailEngin` exposes the same email methods as coroutines and uses `httpx.AsyncClient` internally:

```python
import asyncio
import os

from mailengin import AsyncMailEngin, SendEmailRequest


async def main() -> None:
    async with AsyncMailEngin(os.environ["MAILENGIN_API_KEY"]) as client:
        email = await client.emails.send(
            SendEmailRequest(
                to="user@example.com",
                template_name="welcome-email",
                variables={"first_name": "Asha"},
            )
        )
        print(email.id)


asyncio.run(main())
```

Cancel the surrounding asyncio task to cancel an in-flight asynchronous request.

## Send One Email

```python
request = SendEmailRequest(
    to="customer@example.com",
    from_email="hello@yourdomain.com",
    template_name="account-verification",
    variables={
        "first_name": "Asha",
        "verification_url": "https://yourapp.com/verify/token",
    },
    reply_to_mailengin=True,
)

email = client.emails.send(request)
print(email.id, email.from_email, email.created_at)
```

### Send request fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | `str` | Yes | Recipient email address. |
| `template_name` | `str` | Recommended | Published template API name or exact display name. |
| `template_id` | `str` | No | Legacy template identifier. Prefer `template_name`. |
| `variables` | `dict[str, Any]` | No | Values used to render template variables. |
| `subject` | `str` | Raw HTML only | Template subject override, or required subject for raw HTML. |
| `from_email` | `str` | Recommended | Sender on a verified domain authorized for the API key. |
| `html` | `str` | Advanced | Raw HTML used when no template is supplied. |
| `reply_to_mailengin` | `bool` | No | Route recipient replies into the MailEngin inbox. |

Exactly one content source is required: `template_name`, `template_id`, or `html`. Raw HTML sends also require `subject`.

## Send Personalized Bulk Email

Bulk requests support up to 1,000 recipients. Request-level variables apply to every recipient; recipient variables take precedence.

```python
from mailengin import BulkRecipient, SendBulkEmailRequest

job = client.emails.send_bulk(
    SendBulkEmailRequest(
        to=[
            BulkRecipient(
                email="asha@example.com",
                variables={"first_name": "Asha"},
            ),
            BulkRecipient(
                email="ben@example.com",
                variables={"first_name": "Ben"},
            ),
        ],
        from_email="hello@yourdomain.com",
        template_name="product-update",
        variables={"product_name": "MailEngin"},
    )
)

print(job.job_id, job.queued_count)
```

For the same content without recipient-specific variables, use strings:

```python
job = client.emails.send_bulk(
    SendBulkEmailRequest(
        to=["a@example.com", "b@example.com"],
        template_name="maintenance-notice",
    )
)
```

A successful bulk response confirms that recipients were queued. It is not a guarantee that every message was delivered.

## Send Raw HTML

Published templates are recommended for reusable product email. For a one-off message, provide both `subject` and `html`:

```python
email = client.emails.send(
    SendEmailRequest(
        to="user@example.com",
        from_email="reports@yourdomain.com",
        subject="Your report is ready",
        html="<h1>Report ready</h1><p>You can download it now.</p>",
    )
)
```

## Sender Selection

MailEngin resolves the sender in this order:

1. `from_email` supplied in the request.
2. Sender saved in the published Developer Template.
3. `noreply@<authorized-domain>` fallback.

The sender domain must be verified and authorized for the API key. Set the sender in the template or request for predictable production sends.

## Error Handling

API, timeout, malformed-response, and network failures raise `MailEnginError`:

```python
from mailengin import MailEnginError, SendEmailRequest

try:
    client.emails.send(
        SendEmailRequest(
            to="user@example.com",
            template_name="welcome-email",
        )
    )
except MailEnginError as error:
    print(error)
    print(error.status)        # HTTP status, when available
    print(error.code)          # Machine-readable error code
    print(error.request_id)    # Include when contacting support
    print(error.retry_after)   # Seconds supplied with HTTP 429
    print(error.body)          # Parsed JSON or response text
    print(error.is_retryable)
```

`is_retryable` is true for network errors, timeouts, HTTP `408`, HTTP `429`, and `5xx` responses. The SDK never retries sends automatically because a retry could create a duplicate email until idempotency keys are supported.

## Configuration

```python
client = MailEngin(
    os.environ["MAILENGIN_API_KEY"],
    base_url="https://api.mailengin.app",
    timeout=15.0,
)
```

| Option | Default | Description |
| --- | --- | --- |
| `api_key` | None | Full server-side MailEngin API key. |
| `base_url` | `https://api.mailengin.app` | Override for local, test, or dedicated environments. |
| `timeout` | `30.0` | Request timeout in seconds. |
| `http_client` | New HTTPX client | Injectable `httpx.Client` or `httpx.AsyncClient`. |

When you inject an HTTP client, your application owns its lifecycle. Otherwise, use the client as a context manager or call `close()` when finished.

## Testing With an Injected Transport

No real API key is required in unit tests. Inject an HTTPX client backed by `MockTransport`:

```python
import httpx

from mailengin import MailEngin


def handler(request: httpx.Request) -> httpx.Response:
    assert request.headers["authorization"] == "Bearer test_key"
    return httpx.Response(
        200,
        json={
            "id": "email_123",
            "from": "hello@example.com",
            "to": "user@example.com",
            "template_name": "welcome-email",
            "created_at": "2026-08-31T12:00:00Z",
        },
    )


http_client = httpx.Client(transport=httpx.MockTransport(handler))
client = MailEngin("test_key", http_client=http_client)
```

## Development

```bash
python -m pip install -e . pytest pytest-asyncio ruff mypy build twine
ruff check .
mypy src
pytest
python -m build
python -m twine check dist/*
```

See [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution rules and [PUBLISHING.md](./PUBLISHING.md) for maintainer release instructions.

## Resources

- [MailEngin API documentation](https://mailengin.app/dashboard/docs)
- [Developer Templates](https://mailengin.app/dashboard/dev-templates)
- [API keys](https://mailengin.app/dashboard/api-keys)
- [Security policy](./SECURITY.md)
- [Changelog](./CHANGELOG.md)

## License

Released under the [MIT License](./LICENSE). Copyright 2026 MailEngin.
