Metadata-Version: 2.4
Name: mailflat
Version: 0.4.1
Summary: Official Python SDK for MailFlat — disposable, automation-friendly email inboxes with one-line OTP retrieval.
Project-URL: Homepage, https://mailflat.net
Project-URL: Documentation, https://mailflat.net
Project-URL: Source, https://github.com/onderyentar/mailflat
Author-email: MailFlat <support@mailflat.net>
License: MIT
License-File: LICENSE
Keywords: ai-agents,automation,disposable-email,email,otp,temporary-email,testing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: Email
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.24
Provides-Extra: dev
Requires-Dist: langchain-core>=0.2; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2; extra == 'langchain'
Description-Content-Type: text/markdown

# MailFlat — Python SDK

Official Python client for [MailFlat](https://mailflat.net): disposable, automation-friendly
email inboxes with **one-line OTP retrieval**. Spin up a real inbox, read the verification
code your app just sent, and move on — no flaky polling, no shared mailbox state.

```bash
pip install mailflat
```

## Quickstart

```python
from mailflat import MailFlat

mf = MailFlat(api_key="mf_live_...")  # or set MAILFLAT_API_KEY

# 1 · spin up a disposable inbox
inbox = mf.create(label="signup-test")
print(inbox.address)            # → signup-test-8f3@x7k2m.mailflat.net

# 2 · your app/browser submits the form using inbox.address ...

# 3 · grab the OTP (polls until it arrives or times out)
otp = inbox.wait_for_otp(timeout=30)
print(otp)                      # → "123456"

# inbox auto-clears in 2h — no cleanup needed (or call inbox.delete())
```

## For AI agents

Hand an agent one API key and it spins up real inboxes on demand:

```python
mf = MailFlat()  # reads MAILFLAT_API_KEY

inbox = mf.create(label="deep-research")
browser.fill("#email", inbox.address)
browser.click("Sign up")

otp = inbox.wait_for_otp(timeout=30)
browser.fill("#code", otp)
```

## API

### `MailFlat(api_key=None, *, base_url="https://mailflat.net", timeout=30.0, max_retries=2)`
Client. `api_key` falls back to the `MAILFLAT_API_KEY` environment variable. Use `base_url`
for self-hosted / BYOD deployments. Supports use as a context manager (`with MailFlat() as mf:`).

- `create(*, prefix=None, label=None, subdomain=None, domain=None, retention_hours=None) -> Inbox`
  — open a new inbox. `create_inbox(...)` is an alias.
- `list() -> list[Inbox]` — inboxes opened with this key.
- `inbox(address) -> Inbox` — attach to an existing address without a network call.

### `Inbox`
- `.address` — the email address.
- `.messages(*, direction="in") -> list[Message]` — messages, newest first.
- `.latest(*, direction="in") -> Message | None` — most recent message.
- `.wait_for_otp(*, timeout=30, poll_interval=1.0) -> str` — poll until an OTP arrives; returns the code.
- `.wait_for_message(*, timeout=30, poll_interval=1.0, direction="in") -> Message` — poll until a message arrives.
- `.send(to, *, subject="", body="", html=None) -> dict` — send a DKIM-signed email from this inbox.
- `.mark_read(message_id) -> dict` — mark a message read so later polls can skip it.
- `.burn() -> dict` — delete every message but keep the address.
- `.download_attachment(message_id, attachment_id) -> bytes` — fetch an attachment's bytes.
- `.delete() -> dict` — delete the inbox and all its messages.

> **Reads return received mail by default.** `direction="out"` returns mail you sent from
> this address, `"all"` returns both. This matters for agent-to-agent flows: without it,
> `send()` followed by `wait_for_message()` returns your own outgoing message.

### `Message`
`.otp`, `.subject`, `.sender`, `.text`, `.html`, `.to_address`, `.direction`, `.received_at`,
`.links`, `.attachments`, `.spam`, `.headers`, `.is_read`, `.raw`.

- `.links` — URLs found in the body (HTML hrefs first). For "click the verification link" flows.
- `.attachments` — metadata; `msg.attachments[0].download()` fetches the bytes.
  ⚠️ `send()` cannot attach files yet, so you cannot produce an attachment from the SDK
  alone — send one from a normal mail client to test this path.
- `.spam` — `{score, required, is_spam, rules, scanner}`, or `None` when never scanned
  (which is not the same as a score of 0). ⚠️ Spam scanning is currently **disabled** on
  mailflat.net, so today this is `None` for every message.
- `.headers` — raw headers, exactly as they arrived. Names are case-insensitive per
  RFC 5322 but this is a plain dict, so **do not index it**: the real key is `Message-ID`,
  and `headers["Message-Id"]` raises `KeyError`. Use `.header("message-id")` or `.message_id`.
- `.mark_read()` / `.delete()` — act on this message directly.

## Errors

All errors subclass `MailFlatError`: `AuthenticationError` (401), `MailFlatPermissionError` (403,
still exported as `PermissionError` for compatibility — the new name no longer shadows the built-in),
`NotFoundError` (404), `RateLimitError` (429, carries `.retry_after` when the server sent one),
`APIError` (other), `OTPTimeoutError`
(no OTP before timeout), `EncryptedInboxError` (the inbox is end-to-end encrypted, so the
server cannot read its contents — use a non-encrypted inbox for agent automation).

## License

MIT
