Metadata-Version: 2.4
Name: foolmail
Version: 0.1.0
Summary: Python client for FoolMail / omegaonix — disposable mailboxes, custom domains, and account management.
Project-URL: Homepage, https://github.com/thefool/foolmail
Project-URL: Documentation, https://foolmail.readthedocs.io
Project-URL: Bug Tracker, https://github.com/thefool/foolmail/issues
License: MIT
Keywords: api-client,disposable-email,email,foolmail,mailbox,tempmail
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.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 :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.9.0; extra == 'aiohttp'
Provides-Extra: dev
Requires-Dist: aioresponses>=0.7; extra == 'dev'
Requires-Dist: hatchling; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Description-Content-Type: text/markdown

# foolmail

Python client for **FoolMail / TalkinAPI** — disposable temp-mail, custom domain management, and account operations — with full **sync** and **async** support.

```
pip install foolmail              # httpx (sync + async)
pip install "foolmail[aiohttp]"   # + aiohttp async transport
```

---

## Quick start

```python
from foolmail import FoolMailClient

client = FoolMailClient(
    api_key  = "Thefool-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
    base_url = "https://your-server.com",
)

# Account snapshot — returns AccountSummary object
snap = client.account.summary()
print(snap.account.points_balance)   # e.g. 380.0
print(snap.key.plan)                 # "free" | "paid" | …

# Create a random mailbox — returns Mailbox object
box = client.mail.create_random()
print(box.email)   # e.g. a3f9bc12@mail.example.com

# Read inbox — returns InboxList object
inbox = client.mail.list_emails(box.email)
for msg in inbox.emails:
    print(msg.subject, msg.from_addr)

# Read a full message — returns Email object
full = client.mail.get_email(box.email, inbox.emails[0].id)
print(full.text)

# Always available: .json gives the raw dict
print(snap.json["account"]["points_balance"])
```

---

## Three client flavours

| Class | Transport | When to use |
|---|---|---|
| `FoolMailClient` | httpx sync | Scripts, CLI tools, Django, Flask |
| `AsyncFoolMailClient` | httpx async | FastAPI, async Django, asyncio apps |
| `AioFoolMailClient` | aiohttp async | Apps already using aiohttp |

All three expose **identical resource namespaces**:

```
client.account   →  AccountClient / AsyncAccountClient
client.mail      →  MailClient    / AsyncMailClient
client.domains   →  DomainsClient / AsyncDomainsClient
```

---

## Sync client (httpx)

```python
from foolmail import FoolMailClient

# Use as a plain object
client = FoolMailClient(api_key="Thefool-...", base_url="https://...")
snap   = client.account.summary()
client.close()   # release connections

# Or as a context manager (recommended)
with FoolMailClient(api_key="...", base_url="...") as client:
    box = client.mail.create_random("mail.example.com")
    print(box.email)
```

---

## Async client — httpx

```python
import asyncio
from foolmail import AsyncFoolMailClient

async def main():
    async with AsyncFoolMailClient(api_key="Thefool-...", base_url="https://...") as c:
        snap  = await c.account.summary()
        box   = await c.mail.create_random()
        inbox = await c.mail.list_emails(box.email)
        print(inbox.total, "emails")

asyncio.run(main())
```

---

## Async client — aiohttp

```python
import asyncio
from foolmail import AioFoolMailClient

async def main():
    async with AioFoolMailClient(api_key="Thefool-...", base_url="https://...") as c:
        box = await c.mail.create_custom("devtest", "mail.example.com")
        print(box.email)

asyncio.run(main())
```

---

## Response objects

Every API call returns a **typed object** instead of a plain dict.
All objects have:
- Named attributes for every response field (`box.email`, `snap.key.plan`, …)
- A `.json` attribute with the raw dict for full flexibility

```python
from foolmail import (
    AccountSummary, Mailbox, MailboxList,
    InboxList, EmailSummary, Email, DeleteResult, CreditsBalance,
    Domain, DomainList, DomainDnsInfo, DomainVerifyResult, DomainHealth,
    DnsRecord, DnsChecks,
)
```

---

## Account

```python
snap = client.account.summary()   # → AccountSummary
```

| Attribute | Type | Description |
|---|---|---|
| `snap.key` | `KeyInfo` | plan, status, rate_limit, total_requests, expires_at |
| `snap.account` | `AccountInfo` | points_balance, active_keys |
| `snap.mail` | `MailStats` | active_mailboxes |
| `snap.domains` | `DomainStats` | total, verified |
| `snap.webhooks` | `WebhookStats` | total, active |
| `snap.costs` | `CostTable` | create_mailbox, add_domain, … |

```python
print(snap.key.plan)                    # "free"
print(snap.account.points_balance)      # 380.0
print(snap.costs.create_mailbox)        # 10.0
print(snap.costs.create_custom_mailbox) # 15.0
```

---

## Mail — full reference

### Create mailboxes

```python
# Random username — 10 pts → Mailbox
box = client.mail.create_random()
box = client.mail.create_random(domain="mail.example.com")

# Custom username — 15 pts → Mailbox
box = client.mail.create_custom("alice", "mail.example.com")

print(box.email)              # "alice@mail.example.com"
print(box.active_for_minutes) # 60
print(box.credits_remaining)  # 365.0
```

### Manage mailboxes

```python
# List all mailboxes for this key → MailboxList
result = client.mail.list_mailboxes()
result = client.mail.list_mailboxes(status="active", limit=10)
for mb in result.mailboxes:
    print(mb.email, mb.seconds_left)

# Get one mailbox → Mailbox
mb = client.mail.get_mailbox("alice@mail.example.com")
print(mb.seconds_left)

# Reactivate expired mailbox — 8 pts → Mailbox
result = client.mail.reactivate_mailbox("alice@mail.example.com")
print(result.active_until)

# Delete (soft-delete, irreversible) → DeleteResult
result = client.mail.delete_mailbox("alice@mail.example.com")
print(result.success)  # True
```

### Read inbox

```python
# List emails (free) → InboxList
inbox = client.mail.list_emails("alice@mail.example.com")
inbox = client.mail.list_emails("alice@mail.example.com",
                                 page=2, limit=50, unread_only=True)
inbox = client.mail.list_emails("alice@mail.example.com",
                                 sender="orders@shop.com",
                                 subject="invoice")
print(inbox.total, inbox.pages)
for msg in inbox.emails:           # list of EmailSummary
    print(msg.id, msg.subject, msg.from_addr)

# Get full email — auto-marks as read (free) → Email
msg = client.mail.get_email("alice@mail.example.com", "64a1b2c3...")
print(msg.subject)
print(msg.text)
print(msg.html)
for att_id in msg.attachment_ids:
    data = client.mail.download_attachment(att_id)  # → bytes

# Delete email → DeleteResult
client.mail.delete_email("alice@mail.example.com", "64a1b2c3...")

# Points balance → CreditsBalance
bal = client.mail.credits()
print(bal.points_balance)
```

---

## Domains — full reference

```python
# Add a custom domain — 50 pts → Domain
result = client.domains.add("mail.mycompany.com")
for rec in result.dns_records:   # list of DnsRecord
    print(rec.type, rec.host, rec.value)

# List all domains → DomainList
result = client.domains.list()
for d in result.domains:          # list of Domain
    print(d.domain, d.status, d.is_active)

# Get DNS records (if you lost them) → DomainDnsInfo
records = client.domains.dns_records("mail.mycompany.com")

# Trigger DNS verification → DomainVerifyResult
result = client.domains.verify("mail.mycompany.com")
if result.checks.all_ok:
    print("Domain is live!")
print(result.checks.mx, result.checks.dkim)

# Live health check (no DB update) → DomainHealth
h = client.domains.health("mail.mycompany.com")
print(h.checks.all_ok)

# Delete domain + all mailboxes (irreversible!) → DeleteResult
client.domains.delete("mail.mycompany.com")
```

---

## Error handling

```python
from foolmail import (
    FoolMailClient,
    AuthenticationError,
    InsufficientCreditsError,
    ConflictError,
    NotFoundError,
    RateLimitError,
    FoolMailAPIError,
)

try:
    box = client.mail.create_random()
except AuthenticationError:
    print("Invalid API key or key revoked")
except InsufficientCreditsError as e:
    print(f"Not enough points: {e.detail}")
except ConflictError:
    print("Address already taken — retry")
except RateLimitError:
    print("Slow down — hit rate limit")
except FoolMailAPIError as e:
    print(f"Unexpected error {e.status_code}: {e}")
```

All exceptions inherit from `FoolMailError` which carries:
- `.status_code` — HTTP status int
- `.detail` — raw detail from the API (string or dict)

---

## Client options

```python
client = FoolMailClient(
    api_key    = "Thefool-...",
    base_url   = "https://your-server.com",
    timeout    = 60.0,    # seconds (default 30)
    verify_ssl = False,   # disable TLS verify for local dev
)
```

---

## Pagination example

```python
skip  = 0
limit = 50
while True:
    result = client.mail.list_mailboxes(limit=limit, skip=skip)
    for mb in result.mailboxes:
        print(mb.email)
    if skip + limit >= result.total:
        break
    skip += limit
```

---

## License

MIT © 2025 TheFool
