Metadata-Version: 2.4
Name: mailsenpai
Version: 1.0.0
Summary: Python client for SMTP Senpai by MailSenpai: EU SMTP relay and transactional email API, with a Django email backend and a CrewAI tool.
Author: MailSenpai
License-Expression: MIT
Project-URL: Homepage, https://en.mailsenpai.com/integrations/
Project-URL: Documentation, https://en.mailsenpai.com/smtp-api/
Project-URL: Source, https://gitlab.com/smtp-senpai/mailsenpai-python
Project-URL: Issues, https://gitlab.com/smtp-senpai/mailsenpai-python/-/issues
Project-URL: Support, https://en.mailsenpai.com/contact-us/
Keywords: email,smtp,transactional email,email api,django,crewai,mailsenpai,smtp senpai
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Framework :: Django
Classifier: Topic :: Communications :: Email
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: django
Requires-Dist: Django>=4.2; extra == "django"
Provides-Extra: crewai
Requires-Dist: crewai>=0.100; extra == "crewai"
Requires-Dist: pydantic>=2; extra == "crewai"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# mailsenpai (Python)

Python client for **SMTP Senpai by MailSenpai**, the EU-hosted SMTP relay and transactional email API.
It has no runtime dependencies (standard library only) and includes:

- a typed, synchronous API client: send, account status, event log (with a cursor iterator), suppression list, webhook settings;
- webhook signature verification;
- a Django email backend: `mailsenpai.django.EmailBackend`;
- a CrewAI tool: `mailsenpai.crewai.SendEmailTool` (optional extra).

Docs: https://en.mailsenpai.com/smtp-api/ · Product: https://en.mailsenpai.com/smtp-senpai/ · All integrations: https://en.mailsenpai.com/integrations/
Source code and issues: https://gitlab.com/smtp-senpai/mailsenpai-python

## Install

```bash
pip install mailsenpai              # client + Django backend (Django itself not included)
pip install "mailsenpai[crewai]"    # adds CrewAI
```

Python 3.9+. Get your API key (it starts with `msp_`) in the MailSenpai customer area, SMTP Senpai page.
Call the API from your server only: it does not accept browser requests.

## Send an email

```python
from mailsenpai import MailSenpai, Attachment

client = MailSenpai("msp_...")          # or set MAILSENPAI_API_KEY

result = client.send(
    to="customer@example.com",          # or a list of up to 50 addresses: each gets its own copy
    from_email="orders@yourdomain.com", # must be on a domain verified in your account
    from_name="Your shop",
    subject="Order 10293 confirmed",
    html="<p>Thanks, your order is confirmed.</p>",
    text="Thanks, your order is confirmed.",
    reply_to="support@yourdomain.com",
    headers={"X-Order": "10293"},       # only X-... headers are kept
    attachments=[Attachment.from_path("invoice.pdf")],  # up to 10 files, 10 MB in total
    idempotency_key="order-10293",      # same key within 24h = no second send
)

print(result.sent, result.message_id, result.remaining)
for r in result.not_sent:               # e.g. recipients on the suppression list
    print(r.email, r.reason or r.error)
```

`SendResult` normalises single and multi-recipient responses: `results` (one `RecipientResult` per address),
`sent`, `sent_count`, `message_id`, `message_ids`, `remaining`, `over_plan`, `replayed` and the `raw` JSON.

## Account, log and suppression list

```python
client.status()                        # SMTP details, monthly volume, tracking, suppressed count
client.stats(days=7)                   # counts and delivery/bounce/complaint rates

page = client.events(limit=50, type="bounce")   # latest events, newest first

# Read everything new since the last event you processed (oldest first, follows the cursor)
for event in client.iter_events(after=last_event_id):
    handle(event)
    last_event_id = int(event["event_id"])

client.suppressions(search="example.com")       # SuppressionList(total, items)
client.suppress("someone@example.com", note="asked to stop")
client.suppress("@competitor.example")          # a whole domain
client.unsuppress("someone@example.com")        # only entries you added yourself
```

Event types: `sent`, `delivered`, `bounce`, `defer`, `complaint`, `dropped`, `open`, `click`
(`open` and `click` need tracking on). Dates are UTC (`YYYY-MM-DD HH:MM:SS`).

## Errors

Every error subclasses `mailsenpai.MailSenpaiError` and has `message` (the API text, in Italian),
`status_code` and `body`:

| Exception | When |
|---|---|
| `BadRequestError` (400) | missing or invalid data |
| `AuthenticationError` (401) | key missing or not valid |
| `PermissionDeniedError` (403) | sender domain not verified, account suspended or being activated |
| `NotFoundError` (404) | entry not found |
| `ConflictError` (409) | same Idempotency-Key in progress; suppression entry added by a bounce/complaint |
| `PayloadTooLargeError` (413) | attachments over 10 MB |
| `UnprocessableEntityError` (422) | Idempotency-Key reused with different data |
| `RateLimitError` (429) | monthly volume used up or spending cap reached |
| `UpstreamError` (502) | the sending server did not accept the message |
| `APIConnectionError` | network problem, timeout |

The client does not retry on its own: if you retry a send, pass an `idempotency_key`.

## Webhooks

Set the webhook once (`client.set_webhook("https://yoursite.example/hooks/mailsenpai", ["delivered", "bounce", "complaint"])`)
and keep the returned `segreto` (`whsec_...`). Verify every call on the **raw** body:

```python
from mailsenpai import verify_webhook, WebhookVerificationError

def mailsenpai_webhook(request):               # Django example
    try:
        event = verify_webhook(
            request.body,
            request.headers.get("X-MailSenpai-Signature"),
            settings.MAILSENPAI_WEBHOOK_SECRET,
        )                                      # 5 minutes tolerance by default
    except WebhookVerificationError:
        return HttpResponse(status=400)
    if event["type"] == "bounce":
        ...
    return HttpResponse(status=204)
```

The signature is `t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<body>")>`. Answer 2xx quickly:
failed deliveries are retried for about 24 hours.

## Django

```python
# settings.py
EMAIL_BACKEND = "mailsenpai.django.EmailBackend"
MAILSENPAI_API_KEY = "msp_..."   # or the MAILSENPAI_API_KEY environment variable
# optional: MAILSENPAI_TIMEOUT = 30
```

With Django 6.1+ you can use `MAILERS` instead:

```python
MAILERS = {
    "default": {
        "BACKEND": "mailsenpai.django.EmailBackend",
        "OPTIONS": {"api_key": "msp_..."},
    },
}
```

Then use Django as usual (`send_mail`, `EmailMessage`, `EmailMultiAlternatives`). Supported: text and HTML
(`attach_alternative(html, "text/html")` or `content_subtype = "html"`), attachments, `reply_to`, custom `X-...`
headers (other headers are dropped by the API), display names in `from_email`.

Good to know:

- every recipient (To, Cc and Bcc) gets an individual copy, so Cc addresses are not visible to the
  other recipients; use SMTP if you need a visible Cc;
- messages with more than 50 recipients are split into several API calls;
- inline images (`Content-ID`) are sent as regular attachments;
- after sending, `message.mailsenpai_message_ids` and `message.mailsenpai_results` hold the outcome;
- recipients on the suppression list are not an error: they are logged as a warning on the `mailsenpai` logger.

Tested with Django 5.2 LTS and 6.1.

## CrewAI

```python
from crewai import Agent
from mailsenpai.crewai import SendEmailTool

email_tool = SendEmailTool(
    from_email="assistant@yourdomain.com",   # or MAILSENPAI_FROM_EMAIL
    from_name="Your company",
    allowed_recipients=["@yourdomain.com", "partner@example.com"],  # optional safety net
)

agent = Agent(role="Support assistant", goal="...", backstory="...", tools=[email_tool])
```

The agent fills `to`, `subject`, `body`, `is_html` and `reply_to`. The tool returns a short text with the outcome
(errors are returned as text so the agent can react). The key comes from `api_key=` or `MAILSENPAI_API_KEY`.
Use `allowed_recipients` to make sure an agent can only email the people you expect.

## SMTP

If you prefer SMTP: host `relay.mailsenpai.com`, port `2525`, STARTTLS, username and password from the
customer area (`client.status()["smtp"]` shows host, port and username).

## Development

```bash
pip install -e ".[test]" django
pytest
python -m build
```

## License

MIT, © MailSenpai.
