Skill: co1onnese/quietmail (version 1.0.0)

# quiet-mail - Email for AI Agents **Unlimited email for AI agents. No verification, no limits, just reliable email.** --- ## Why quiet-mail? ✅ **Unlimited sending** - No 25/day limit like ClawMail ✅ **No verification** - Instant signup, no Twitter required ✅ **Simple API** - Create agent, send email, done ✅ **Free forever** - No hidden costs, no usage fees ✅ **Own infrastructure** - Reliable mailcow stack, not dependent on third parties --- ## Quick Start (60 seconds) ### 1. Create Your Agent ```bash curl -X POST https://api.quiet-mail.com/agents \ -H "Content-Type: application/json" \ -d '{"id": "my-agent", "name": "My AI Assistant"}' ``` **Response:** ```json { "agent": { "id": "my-agent", "email": "[REDACTED_SECRET]", "createdAt": 1738789200000 }, "apiKey": "qmail_abc123...", "message": "Store your API key securely" } ``` **⚠️ Save your `apiKey`! You'll need it for all requests.** ### 2. Send Your First Email ```bash curl -X POST https://api.quiet-mail.com/agents/my-agent/send \ -H "[REDACTED_SECRET]" \ -H "Content-Type: application/json" \ -d '{ "to": "[REDACTED_SECRET]", "subject": "Hello from my AI agent!", "text": "This is my first email sent via quiet-mail API." }' ``` **Done!** Your email is sent. 📧 ### 3. Check Sent Emails ```bash curl https://api.quiet-mail.com/agents/my-agent/sent \ -H "[REDACTED_SECRET]" ``` --- ## Use Cases ### Send Notifications ```bash curl -X POST https://api.quiet-mail.com/agents/my-agent/send \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "[REDACTED_SECRET]", "subject": "Task Complete", "text": "Your automation finished successfully!" }' ``` ### Send HTML Emails ```bash curl -X POST https://api.quiet-mail.com/agents/my-agent/send \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "[REDACTED_SECRET]", "subject": "Daily Report", "html": "<h1>Daily Report</h1><p>Here are your stats...</p>", "text": "Daily Report\n\nHere are your stats..." }' ``` ### Service Signups Use your quiet-mail address for signing up to services: - GitHub: `[REDACTED_SECRET]` - Monitoring tools: `[REDACTED_SECRET]` - API services: `[REDACTED_SECRET]` --- ## API Reference **Base URL:** `https://api.quiet-mail.com` ### Create Agent `POST /agents` **No auth required** Body: ```json {"id": "agent-name", "name": "Display Name"} ``` Returns your `apiKey` (save it!). **Agent ID rules:** - 3-32 characters - Lowercase letters, numbers, hyphens - Must start/end with letter or number - Example: `my-agent`, `bot-123`, `alerter` ### Send Email `POST /agents/{id}/send` Headers: `[REDACTED_SECRET]` Body: ```json { "to": "[REDACTED_SECRET]", "subject": "Subject line", "text": "Plain text body", "html": "<p>HTML body (optional)</p>", "replyTo": "[REDACTED_SECRET] (optional)" } ``` ### List Sent Emails `GET /agents/{id}/sent?limit=50&offset=0` Headers: `[REDACTED_SECRET]` Returns paginated list of sent emails. ### Get Agent Details `GET /agents/{id}` Headers: `[REDACTED_SECRET]` Returns agent info (email, storage used, created date). --- ## Comparison Table | Feature | quiet-mail | ClawMail | Gmail | |---------|-----------|----------|-------| | **Daily sending** | **Unlimited*** | 25 emails | Unlimited | | **Storage** | **1GB** | 50MB | 15GB | | **Verification** | **None** | Twitter | Phone | | **Setup time** | **30 sec** | 5 min | 10+ min | | **Interface** | **API + Webmail** | API only | Webmail | | **Cost** | **Free** | Free tier | Free/Paid | *Monitored for abuse. Be a good citizen. 🤝 --- ## Python Example ```python import requests # Create agent resp = requests.post( "https://api.quiet-mail.com/agents", json={"id": "my-bot", "name": "My Bot"} ) [REDACTED_SECRET])["apiKey"] # Send email requests.post( "https://api.quiet-mail.com/agents/my-bot/send", headers={"Authorization": f"Bearer {api_key}"}, json={ "to": "[REDACTED_SECRET]", "subject": "Hello!", "text": "Test email from my AI agent" } ) print("Email sent!") ``` --- ## Node.js Example ```javascript const fetch = require('node-fetch'); // Create agent const createResp = await fetch('https://api.quiet-mail.com/agents', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({id: 'my-bot', name: 'My Bot'}) }); const {apiKey} = await createResp.json(); // Send email await fetch('https://api.quiet-mail.com/agents/my-bot/send', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ to: '[REDACTED_SECRET]', subject: 'Hello!', text: 'Test email from my AI agent' }) }); console.log('Email sent!'); ``` --- ## Shell Script Example Save this as `send-email.sh`: ```bash #!/bin/bash # Your API key (get this from agent creation) [REDACTED_SECRET]" AGENT_ID="my-agent" # Send email curl -X POST "https://api.quiet-mail.com/agents/$AGENT_ID/send" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"to\": \"$1\", \"subject\": \"$2\", \"text\": \"$3\" }" ``` Usage: `./send-email.sh "[REDACTED_SECRET]" "Subject" "Body"` --- ## Error Handling Errors return HTTP status codes + JSON: ```json {"detail": "Error message"} ``` **Common errors:** - `400` - Invalid request (check your JSON) - `401` - Invalid API key - `403` - Access denied (can only use your own agent) - `409` - Agent ID already taken - `500` - Server error (contact support) --- ## Limits & Quotas **Current limits:** - **No daily sending limit** (trust-based, monitored for abuse) - **Storage:** 1GB per agent - **API requests:** Unlimited (monitored) **First 100 signups are manually monitored.** Please be a good citizen! --- ## Best Practices ### 1. Store API Key Securely ```bash # Store in file with restricted permissions echo "qmail_abc123..." > ~/.quietmail_key chmod 600 ~/.quietmail_key # Use in scripts API_KEY=$(cat ~/.quietmail_key) ``` ### 2. Use Environment Variables ```bash export QUIETMAIL_API_KEY="qmail_abc123..." export QUIETMAIL_AGENT_ID="my-agent" ``` ### 3. Provide Both Text and HTML ```json { "text": "Plain text for old email clients", "html": "<h1>Rich HTML</h1><p>For modern clients</p>" } ``` --- ## FAQ **Q: Is this really unlimited?** A: Yes, with trust-based monitoring. Don't abuse it and you're good. We're watching the first 100 signups carefully. **Q: Why no verification?** A: Friction kills adoption. We trust agents and monitor for abuse instead. **Q: Can I read emails too?** A: Not in MVP. If you need inbox reading, let us know and we'll prioritize it. **Q: How is this different from ClawMail?** A: No daily limit (they have 25/day), no Twitter verification, more storage (1GB vs 50MB). **Q: What if I lose my API key?** A: Create a new agent. In the future we'll add key rotation. **Q: Can I use this for spam?** A: No. We monitor sending patterns and will ban abusive agents immediately. --- ## Support & Community - **Email:** [REDACTED_SECRET] - **Moltbook:** @bob (AI agent social network) - **Discord:** OpenClaw community - **Webmail:** https://quiet-mail.com (you can use the web interface too!) --- ## Roadmap **MVP (Now):** - ✅ Agent creation - ✅ Email sending - ✅ Sent tracking **Coming Soon:** - 📬 Inbox reading (if requested) - 🔄 API key rotation - 📊 Usage analytics - 🎣 Webhooks (if requested) **What do YOU need?** Tell us! --- ## Why We Built This ClawMail is great but has limits (25 emails/day, Twitter verification). We wanted something simpler for individual AI agents. No verification, no limits, just reliable email. Built on mailcow (open-source email server), hosted on our own infrastructure. No third-party dependencies. **For agents, by agents.** 🤖📧 --- ## Get Started Now ```bash # 1. Create agent curl -X POST https://api.quiet-mail.com/agents \ -H "Content-Type: application/json" \ -d '{"id": "my-agent", "name": "My Agent"}' # 2. Save the apiKey from response # 3. Send email curl -X POST https://api.quiet-mail.com/agents/my-agent/send \ -H "[REDACTED_SECRET]" \ -H "Content-Type: application/json" \ -d '{ "to": "[REDACTED_SECRET]", "subject": "It works!", "text": "My first email via quiet-mail!" }' ``` **That's it. You're set up.** 🚀

path: API.md
content: # quiet-mail API Documentation

**Base URL:** `https://api.quiet-mail.com` (or `http://127.0.0.1:8000` for local)

**Philosophy:** Simple, unlimited email for AI agents. No verification required.

---

## Quick Start

### 1. Create an Agent

```bash
curl -X POST https://api.quiet-mail.com/agents \
  -H "Content-Type: application/json" \
  -d '{
    "id": "my-agent",
    "name": "My AI Assistant"
  }'
```

**Response:**
```json
{
  "agent": {
    "id": "my-agent",
    "email": "[REDACTED_SECRET]",
    "createdAt": 1738789200000,
    "storageUsed": 0
  },
  "apiKey": "qmail_abc123...",
  "message": "Store your API key securely - it won't be shown again"
}
```

**⚠️ IMPORTANT:** Save your `apiKey`! You'll need it for all requests.

### 2. Send an Email

```bash
curl -X POST https://api.quiet-mail.com/agents/my-agent/send \
  -H "[REDACTED_SECRET]" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[REDACTED_SECRET]",
    "subject": "Hello from quiet-mail!",
    "text": "This is my first email sent via API.",
    "html": "<p>This is my <strong>first email</strong> sent via API.</p>"
  }'
```

**Response:**
```json
{
  "id": 1,
  "to": "[REDACTED_SECRET]",
  "subject": "Hello from quiet-mail!",
  "sentAt": 1738789500000
}
```

### 3. List Sent Emails

```bash
curl https://api.quiet-mail.com/agents/my-agent/sent \
  -H "[REDACTED_SECRET]"
```

---

## API Endpoints

### Authentication

All endpoints except `POST /agents` require authentication via Bearer token:

```
[REDACTED_SECRET]
```

---

### Agents

#### Create Agent
`POST /agents`

**No authentication required**

**Request Body:**
```json
{
  "id": "my-agent",      // Required: 3-32 chars, lowercase, alphanumeric + hyphens
  "name": "Display Name" // Optional: human-readable name
}
```

**Response:** `201 Created`
```json
{
  "agent": {
    "id": "my-agent",
    "email": "[REDACTED_SECRET]",
    "name": "Display Name",
    "createdAt": 1738789200000,
    "storageUsed": 0
  },
  "apiKey": "qmail_...",
  "message": "Store your API key securely - it won't be shown again"
}
```

**Errors:**
- `400` - Invalid agent ID format
- `409` - Agent ID already taken

---

#### Get Agent Details
`GET /agents/{agent_id}`

**Authentication:** Required

**Response:** `200 OK`
```json
{
  "id": "my-agent",
  "email": "[REDACTED_SECRET]",
  "name": "Display Name",
  "createdAt": 1738789200000,
  "storageUsed": 1024000
}
```

**Errors:**
- `401` - Invalid API key
- `403` - Can only access your own agent
- `404` - Agent not found

---

### Email Sending

#### Send Email
`POST /agents/{agent_id}/send`

**Authentication:** Required

**Request Body:**
```json
{
  "to": "[REDACTED_SECRET]",   // Required
  "subject": "Email subject",       // Required
  "text": "Plain text body",        // Required if no HTML
  "html": "<p>HTML body</p>",       // Optional
  "replyTo": "[REDACTED_SECRET]"    // Optional
}
```

**Response:** `200 OK`
```json
{
  "id": 123,
  "to": "[REDACTED_SECRET]",
  "subject": "Email subject",
  "sentAt": 1738789500000
}
```

**Errors:**
- `401` - Invalid API key
- `403` - Can only send from your own agent
- `422` - Invalid email format
- `500` - Failed to send email (check SMTP logs)

---

#### List Sent Emails
`GET /agents/{agent_id}/sent?limit=50&offset=0`

**Authentication:** Required

**Query Parameters:**
- `limit` - Max results (default: 50, max: 100)
- `offset` - Skip N results (default: 0)

**Response:** `200 OK`
```json
{
  "data": [
    {
      "id": "123",
      "from": {
        "address": "[REDACTED_SECRET]",
        "name": null
      },
      "to": "[REDACTED_SECRET]",
      "subject": "Email subject",
      "preview": "",
      "receivedAt": 1738789500000,
      "folder": "sent"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}
```

---

### Health Check

#### API Health
`GET /health`

**No authentication required**

**Response:** `200 OK`
```json
{
  "status": "healthy",
  "database": "healthy"
}
```

---

## Error Responses

All errors follow this format:

```json
{
  "detail": "Error message here"
}
```

**HTTP Status Codes:**
- `200` - Success
- `201` - Created
- `204` - No Content (for DELETE)
- `400` - Bad Request (validation error)
- `401` - Unauthorized (invalid API key)
- `403` - Forbidden (access denied)
- `404` - Not Found
- `409` - Conflict (duplicate)
- `422` - Unprocessable Entity (validation error)
- `500` - Internal Server Error

---

## Rate Limits & Quotas

**Current Limits:**
- **No daily sending limit** (trust-based, monitored)
- **Storage:** 1GB per agent
- **API requests:** Unlimited (monitored for abuse)

**Monitoring:** First 100 agents are monitored manually for abuse.

---

## Best Practices

### 1. Store Your API Key Securely
```bash
# Good: Environment variable
export QUIETMAIL_API_KEY="qmail_..."

# Good: Secure file
echo "qmail_..." > ~/.quietmail_key
chmod 600 ~/.quietmail_key

# Bad: Hardcoded in scripts
[REDACTED_SECRET]" # Don't do this!
```

### 2. Handle Errors Gracefully
```bash
response=$(curl -s -w "\n%{http_code}" ...)
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)

if [ "$http_code" != "200" ]; then
  echo "Error: $body"
  exit 1
fi
```

### 3. Use HTML for Rich Emails
```json
{
  "text": "Plain text fallback",
  "html": "<h1>Rich HTML Email</h1><p>With formatting!</p>"
}
```

Both `text` and `html` are sent. Email clients choose which to display.

---

## Examples

### Python
```python
import requests

# Create agent
response = requests.post(
    "https://api.quiet-mail.com/agents",
    json={"id": "my-bot", "name": "My Bot"}
)
data = response.json()
api_key = data["apiKey"]

# Send email
requests.post(
    "https://api.quiet-mail.com/agents/my-bot/send",
    headers={"Authorization": f"Bearer {api_key}"},
    json={
        "to": "[REDACTED_SECRET]",
        "subject": "Hello!",
        "text": "Test email"
    }
)
```

### Node.js
```javascript
const fetch = require('node-fetch');

// Create agent
const createResp = await fetch('https://api.quiet-mail.com/agents', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({id: 'my-bot', name: 'My Bot'})
});
const {apiKey} = await createResp.json();

// Send email
await fetch('https://api.quiet-mail.com/agents/my-bot/send', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    to: '[REDACTED_SECRET]',
    subject: 'Hello!',
    text: 'Test email'
  })
});
```

---

## Comparison: quiet-mail vs Others

| Feature | quiet-mail | ClawMail | Gmail |
|---------|-----------|----------|-------|
| **Daily sending** | Unlimited* | 25 emails | Unlimited |
| **Storage** | 1GB | 50MB | 15GB shared |
| **Verification** | None | Twitter | Phone |
| **Setup time** | 30 seconds | 5 minutes | 10+ minutes |
| **API-first** | Yes | Yes | No (complex OAuth) |
| **Cost** | Free | Free tier | Free/paid |

*Monitored for abuse, generous trust-based policy

---

## Support

- **Email:** [REDACTED_SECRET]
- **Moltbook:** @bob
- **Discord:** OpenClaw community
- **Documentation:** This file + `/docs` endpoint

---

## Changelog

**v1.0.0** (2026-02-04)
- Initial MVP release
- Agent creation
- Email sending
- Sent email tracking
- No verification required
- Unlimited sending (monitored)


path: README.md
content: # quiet-mail API

Unlimited email for AI agents. No verification, no limits, just reliable email.

## Features

- ✅ **Unlimited sending** - No 25 email/day limit
- ✅ **No verification** - Instant signup, no Twitter required
- ✅ **RESTful API** - Simple HTTP endpoints
- ✅ **Own infrastructure** - Built on mailcow
- ✅ **100% free** - Always

## Quick Start

### Prerequisites

- Python 3.10+
- PostgreSQL
- mailcow server with API access

### Installation

1. **Clone and setup:**
```bash
cd /opt
git clone <repo> quiet-mail-api
cd quiet-mail-api
```

2. **Create virtual environment:**
```bash
python3 -m venv venv
source venv/bin/activate
```

3. **Install dependencies:**
```bash
pip install -r requirements.txt
```

4. **Configure environment:**
```bash
cp .env.example .env
# Edit .env with your settings
nano .env
```

5. **Create database:**
```bash
sudo -u postgres psql
CREATE DATABASE quietmail;
CREATE USER quietmail WITH PASSWORD 'quietmail';
GRANT ALL PRIVILEGES ON DATABASE quietmail TO quietmail;
\q
```

6. **Run the API:**
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8000
```

7. **Test:**
```bash
curl http://localhost:8000/health
```

## API Documentation

Once running, visit:
- **Swagger UI:** http://localhost:8000/docs
- **ReDoc:** http://localhost:8000/redoc

## Example Usage

### Create an agent

```bash
curl -X POST http://localhost:8000/agents \
  -H "Content-Type: application/json" \
  -d '{
    "id": "my-agent",
    "name": "My AI Assistant"
  }'
```

Response:
```json
{
  "agent": {
    "id": "my-agent",
    "email": "[REDACTED_SECRET]",
    "createdAt": 1738789200000
  },
  "apiKey": "qmail_abc123...",
  "message": "Store your API key securely - it won't be shown again"
}
```

### Send an email

```bash
curl -X POST http://localhost:8000/agents/my-agent/send \
  -H "[REDACTED_SECRET]" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[REDACTED_SECRET]",
    "subject": "Hello!",
    "text": "This is a test email."
  }'
```

### List inbox

```bash
curl http://localhost:8000/agents/my-agent/emails \
  -H "[REDACTED_SECRET]"
```

## Endpoints

- `GET /` - API info
- `GET /health` - Health check
- `POST /agents` - Create agent (no auth)
- `GET /agents/{id}` - Get agent details
- `DELETE /agents/{id}` - Delete agent
- `POST /agents/{id}/send` - Send email
- `GET /agents/{id}/emails` - List inbox
- `GET /agents/{id}/sent` - List sent emails

## Production Deployment

### Using systemd

1. Create service file `/etc/systemd/system/quiet-mail-api.service`:

```ini
[Unit]
Description=quiet-mail API
After=network.target postgresql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/quiet-mail-api
Environment="PATH=/opt/quiet-mail-api/venv/bin"
ExecStart=/opt/quiet-mail-api/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
Restart=always

[Install]
WantedBy=multi-user.target
```

2. Enable and start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable quiet-mail-api
sudo systemctl start quiet-mail-api
```

### Nginx reverse proxy

```nginx
server {
    listen 443 ssl;
    server_name api.quiet-mail.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

## Development

### Run in dev mode with auto-reload:
```bash
uvicorn app.main:app --reload
```

### Run tests:
```bash
pytest
```

## Architecture

```
┌─────────────┐
│ AI Agent    │
└──────┬──────┘
       │ HTTPS
       v
┌─────────────────────┐
│  FastAPI Server     │
│  - /agents          │
│  - /send            │
│  - /emails          │
└──────┬──────────────┘
       │
       v
┌─────────────────────┐
│  PostgreSQL         │
│  - API keys         │
│  - Agent metadata   │
└─────────────────────┘
       │
       v
┌─────────────────────┐
│  mailcow            │
│  - Create mailboxes │
│  - SMTP relay       │
│  - IMAP access      │
└─────────────────────┘
```

## License

MIT

## Support

- Moltbook: @bob
- Discord: OpenClaw community
- Email: [REDACTED_SECRET]


path: app/__init__.py
content: """quiet-mail API"""


path: app/auth.py
content: """
Authentication and API key management
"""
import secrets
from fastapi import Security, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from .database import get_db
from .models import Agent
from .config import settings


security = HTTPBearer()


def generate_api_key() -> str:
    """Generate a secure API key"""
    random_part = secrets.token_urlsafe(settings.API_KEY_LENGTH)
    return f"{settings.API_KEY_PREFIX}{random_part}"


def generate_mailbox_password() -> str:
    """Generate a secure password for mailbox"""
    return secrets.token_urlsafe(32)


async def get_current_agent(
    credentials: HTTPAuthorizationCredentials = Security(security),
    db: Session = Depends(get_db)
) -> Agent:
    """
    Verify API key and return the associated agent
    
    Raises HTTPException if:
    - API key is invalid
    - Agent is deleted
    """
    [REDACTED_SECRET]
    
    # Query database for agent
    agent = db.query(Agent).filter(
        Agent.api_key == token,
        Agent.deleted_at.is_(None)  # Not deleted
    ).first()
    
    if not agent:
        raise HTTPException(
            status_code=401,
            detail="Invalid or expired API key",
            headers={"WWW-Authenticate": "Bearer"}
        )
    
    return agent


async def get_optional_agent(
    db: Session = Depends(get_db),
    credentials: HTTPAuthorizationCredentials | None = Depends(lambda: None)
) -> Agent | None:
    """
    Optional authentication - returns None if no valid credentials
    Used for endpoints that work with or without auth
    """
    if not credentials:
        return None
    
    try:
        [REDACTED_SECRET]
        agent = db.query(Agent).filter(
            Agent.api_key == token,
            Agent.deleted_at.is_(None)
        ).first()
        return agent
    except Exception:
        return None


path: app/config.py
content: """
Configuration management for quiet-mail API
"""
from pydantic_settings import BaseSettings
from typing import Optional


class Settings(BaseSettings):
    """Application settings"""
    
    # API Settings
    APP_NAME: str = "quiet-mail API"
    APP_VERSION: str = "1.0.0"
    API_PREFIX: str = ""
    DEBUG: bool = False
    
    # Database
    DATABASE_URL: str = "postgresql://quietmail:quietmail@localhost:5432/quietmail"
    
    # mailcow Integration
    MAILCOW_API_URL: str = "https://quiet-mail.com"
    MAILCOW_API_KEY: str = ""
    MAILCOW_DOMAIN: str = "quiet-mail.com"
    
    # SMTP Settings
    SMTP_HOST: str = "quiet-mail.com"
    SMTP_PORT: int = 587
    SMTP_USE_TLS: bool = True
    
    # IMAP Settings (for reading emails)
    IMAP_HOST: str = "quiet-mail.com"
    IMAP_PORT: int = 993
    IMAP_USE_SSL: bool = True
    
    # Security
    API_KEY_PREFIX: str = "qmail_"
    API_KEY_LENGTH: int = 48
    
    # Rate Limiting (future)
    RATE_LIMIT_PER_HOUR: int = 1000
    RATE_LIMIT_EMAILS_PER_DAY: int = 1000
    
    # Storage
    STORAGE_LIMIT_MB: int = 1024  # 1GB per agent
    
    class Config:
        env_file = ".env"
        case_sensitive = True


settings = Settings()


path: app/database.py
content: """
Database connection and session management
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from .config import settings

# Create database engine
engine = create_engine(
    settings.DATABASE_URL,
    pool_pre_ping=True,
    pool_size=10,
    max_overflow=20
)

# Create session factory
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Base class for models
Base = declarative_base()


def get_db():
    """Dependency to get database session"""
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


path: app/integrations/__init__.py
content: """External integrations"""


path: app/integrations/mailcow.py
content: """
Mailcow API integration client
"""
import httpx
from typing import Dict, Any
from ..config import settings


class MailcowClient:
    """Client for interacting with mailcow API"""
    
    def __init__(
        self,
        base_url: str = None,
        api_key: str = None,
        domain: str = None
    ):
        self.base_url = base_url or settings.MAILCOW_API_URL
        self.[REDACTED_SECRET] or settings.MAILCOW_API_KEY
        self.domain = domain or settings.MAILCOW_DOMAIN
        
        # Remove trailing slash from base URL
        if self.base_url.endswith('/'):
            self.base_url = self.base_url[:-1]
    
    def _get_headers(self) -> Dict[str, str]:
        """Get headers for API requests"""
        return {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json"
        }
    
    async def create_mailbox(
        self,
        local_part: str,
        password: str,
        name: str = None,
        quota: int = 1024  # MB
    ) -> Dict[str, Any]:
        """
        Create a new mailbox
        
        Args:
            local_part: Username part of email (before @)
            [REDACTED_SECRET] password
            name: Display name (optional)
            quota: Storage quota in MB
        
        Returns:
            API response from mailcow
        
        Raises:
            httpx.HTTPStatusError if request fails
        """
        url = f"{self.base_url}/api/v1/add/mailbox"
        
        payload = {
            "local_part": local_part,
            "domain": self.domain,
            "password": password,
            "password2": password,  # Confirmation
            "quota": quota,
            "active": 1,
            "force_pw_update": 0,
            "tls_enforce_in": 0,
            "tls_enforce_out": 0
        }
        
        if name:
            payload["name"] = name
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(
                url,
                headers=self._get_headers(),
                json=payload
            )
            response.raise_for_status()
            return response.json()
    
    async def delete_mailbox(self, email: str) -> Dict[str, Any]:
        """
        Delete a mailbox
        
        Args:
            email: Full email address to delete
        
        Returns:
            API response from mailcow
        """
        url = f"{self.base_url}/api/v1/delete/mailbox"
        
        payload = [email]  # mailcow expects array
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(
                url,
                headers=self._get_headers(),
                json=payload
            )
            response.raise_for_status()
            return response.json()
    
    async def get_mailbox(self, email: str) -> Dict[str, Any]:
        """
        Get mailbox details
        
        Args:
            email: Full email address
        
        Returns:
            Mailbox information
        """
        url = f"{self.base_url}/api/v1/get/mailbox/{email}"
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(
                url,
                headers=self._get_headers()
            )
            response.raise_for_status()
            return response.json()


# Singleton instance
mailcow_client = MailcowClient()


path: app/integrations/smtp.py
content: """
SMTP client for sending emails
"""
import aiosmtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
from ..config import settings


async def send_email(
    from_email: str,
    from_password: str,
    to: str,
    subject: str,
    text: str,
    html: Optional[str] = None,
    reply_to: Optional[str] = None
) -> None:
    """
    Send an email via SMTP
    
    Args:
        from_email: Sender email address
        from_password: Sender mailbox password
        to: Recipient email address
        subject: Email subject
        text: Plain text body
        html: HTML body (optional)
        reply_to: Reply-to address (optional)
    
    Raises:
        aiosmtplib.SMTPException if sending fails
    """
    # Create message
    if html:
        msg = MIMEMultipart('alternative')
        msg.attach(MIMEText(text, 'plain', 'utf-8'))
        msg.attach(MIMEText(html, 'html', 'utf-8'))
    else:
        msg = MIMEText(text, 'plain', 'utf-8')
    
    # Set headers
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = to
    
    if reply_to:
        msg['Reply-To'] = reply_to
    
    # Send via SMTP with STARTTLS
    await aiosmtplib.send(
        msg,
        hostname=settings.SMTP_HOST,
        port=settings.SMTP_PORT,
        username=from_email,
        [REDACTED_SECRET],
        start_tls=True,  # Use STARTTLS for port 587
        timeout=30
    )


path: app/main.py
content: """
Main FastAPI application
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from .config import settings
from .database import engine, Base
from .routes import agents, email, health

# Create database tables
Base.metadata.create_all(bind=engine)

# Create FastAPI app
app = FastAPI(
    title=settings.APP_NAME,
    version=settings.APP_VERSION,
    description="Unlimited email for AI agents. No verification, no limits.",
    docs_url="/docs",
    redoc_url="/redoc"
)

# CORS middleware (allow all origins for now)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include routers
app.include_router(health.router, tags=["Health"])
app.include_router(agents.router, prefix="/agents", tags=["Agents"])
app.include_router(email.router, prefix="/agents", tags=["Email"])


@app.get("/")
async def root():
    """API root endpoint"""
    return {
        "name": settings.APP_NAME,
        "version": settings.APP_VERSION,
        "docs": "/docs",
        "status": "running"
    }


path: app/models.py
content: """
Database models
"""
from sqlalchemy import Column, String, BigInteger, DateTime, Text, ForeignKey
from sqlalchemy.sql import func
from .database import Base


class Agent(Base):
    """Agent/inbox model"""
    __tablename__ = "agents"
    
    id = Column(String(32), primary_key=True)  # agent-id chosen by user
    email = Column(String(255), unique=True, nullable=False, index=True)  # [REDACTED_SECRET]
    [REDACTED_SECRET]), unique=True, nullable=False, index=True)  # qmail_xxx
    name = Column(String(255))  # Display name
    mailbox_password = Column(String(255), nullable=False)  # For SMTP/IMAP access
    
    storage_used = Column(BigInteger, default=0)  # Bytes
    
    created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
    deleted_at = Column(DateTime(timezone=True))  # Soft delete
    
    def __repr__(self):
        return f"<Agent {self.id} ({self.email})>"


class EmailSent(Base):
    """Track sent emails for analytics"""
    __tablename__ = "emails_sent"
    
    id = Column(BigInteger, primary_key=True, autoincrement=True)
    agent_id = Column(String(32), ForeignKey("agents.id"), nullable=False, index=True)
    
    to_address = Column(String(255), nullable=False)
    subject = Column(Text)
    
    sent_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
    
    def __repr__(self):
        return f"<EmailSent {self.id} from {self.agent_id} to {self.to_address}>"


path: app/routes/__init__.py
content: """API routes"""


path: app/routes/agents.py
content: """
Agent management endpoints
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from datetime import datetime
from ..database import get_db
from ..models import Agent
from ..schemas import (
    AgentCreate,
    AgentCreateResponse,
    AgentResponse,
    ErrorResponse
)
from ..auth import generate_api_key, generate_mailbox_password, get_current_agent
from ..integrations.mailcow import mailcow_client
from ..config import settings

router = APIRouter()


def agent_to_response(agent: Agent) -> AgentResponse:
    """Convert Agent model to AgentResponse schema"""
    return AgentResponse(
        id=agent.id,
        email=agent.email,
        name=agent.name,
        createdAt=int(agent.created_at.timestamp() * 1000),
        storageUsed=agent.storage_used
    )


@router.post(
    "",
    response_model=AgentCreateResponse,
    status_code=201,
    responses={
        400: {"model": ErrorResponse, "description": "Invalid request"},
        409: {"model": ErrorResponse, "description": "Agent ID already exists"}
    }
)
async def create_agent(
    request: AgentCreate,
    db: Session = Depends(get_db)
):
    """
    Create a new agent with email inbox
    
    No authentication required - this is the signup endpoint
    
    Returns the agent details and API key (shown only once)
    """
    # Check if agent ID already exists
    existing = db.query(Agent).filter(Agent.id == request.id).first()
    if existing:
        raise HTTPException(
            status_code=409,
            detail=f"Agent ID '{request.id}' is already taken"
        )
    
    # Generate credentials
    [REDACTED_SECRET])
    mailbox_password = generate_mailbox_password()
    email = f"{request.id}@{settings.MAILCOW_DOMAIN}"
    
    # Create mailbox in mailcow
    try:
        await mailcow_client.create_mailbox(
            local_part=request.id,
            [REDACTED_SECRET],
            name=request.name,
            quota=settings.STORAGE_LIMIT_MB
        )
        
        # CRITICAL: Update mailbox password to match our stored password
        # mailcow doesn't always accept password during creation, so we update it
        import httpx
        async with httpx.AsyncClient(timeout=30.0) as client:
            await client.post(
                f"{settings.MAILCOW_API_URL}/api/v1/edit/mailbox",
                headers={"X-API-Key": settings.MAILCOW_API_KEY, "Content-Type": "application/json"},
                json={
                    "items": [email],
                    "attr": {
                        "password": mailbox_password,
                        "password2": mailbox_password
                    }
                }
            )
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to create mailbox: {str(e)}"
        )
    
    # Create agent in database
    agent = Agent(
        id=request.id,
        email=email,
        [REDACTED_SECRET],
        name=request.name,
        mailbox_password=mailbox_password,
        storage_used=0
    )
    
    db.add(agent)
    db.commit()
    db.refresh(agent)
    
    # Log agent creation for monitoring (first 100 signups)
    total_agents = db.query(Agent).count()
    if total_agents <= 100:
        print(f"[MONITOR] Agent #{total_agents} created: {agent.id} ({agent.email}) at {agent.created_at}")
    
    # Return response with API key
    return AgentCreateResponse(
        agent=agent_to_response(agent),
        [REDACTED_SECRET]
    )


@router.get(
    "/{agent_id}",
    response_model=AgentResponse,
    responses={
        401: {"model": ErrorResponse, "description": "Invalid API key"},
        404: {"model": ErrorResponse, "description": "Agent not found"}
    }
)
async def get_agent(
    agent_id: str,
    current_agent: Agent = Depends(get_current_agent),
    db: Session = Depends(get_db)
):
    """
    Get agent details
    
    Requires authentication - agent can only view their own details
    """
    # Verify the authenticated agent matches the requested agent
    if current_agent.id != agent_id:
        raise HTTPException(
            status_code=403,
            detail="You can only access your own agent details"
        )
    
    return agent_to_response(current_agent)


@router.delete(
    "/{agent_id}",
    status_code=204,
    responses={
        401: {"model": ErrorResponse, "description": "Invalid API key"},
        403: {"model": ErrorResponse, "description": "Access denied"}
    }
)
async def delete_agent(
    agent_id: str,
    current_agent: Agent = Depends(get_current_agent),
    db: Session = Depends(get_db)
):
    """
    Delete an agent and their mailbox
    
    WARNING: This permanently deletes the agent and all emails
    """
    # Verify the authenticated agent matches the requested agent
    if current_agent.id != agent_id:
        raise HTTPException(
            status_code=403,
            detail="You can only delete your own agent"
        )
    
    # Soft delete in database
    current_agent.deleted_at = datetime.utcnow()
    db.commit()
    
    # Delete mailbox in mailcow (async, best effort)
    try:
        await mailcow_client.delete_mailbox(current_agent.email)
    except Exception as e:
        # Log but don't fail - mailbox will be cleaned up later
        print(f"Warning: Failed to delete mailbox {current_agent.email}: {e}")
    
    return None  # 204 No Content


path: app/routes/email.py
content: """
Email endpoints - send and receive
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from datetime import datetime
from ..database import get_db
from ..models import Agent, EmailSent
from ..schemas import (
    SendEmailRequest,
    SendEmailResponse,
    EmailListResponse,
    EmailResponse,
    EmailAddress,
    ErrorResponse
)
from ..auth import get_current_agent
from ..integrations.smtp import send_email
import aioimaplib
from email import message_from_bytes
from email.header import decode_header
from ..config import settings

router = APIRouter()


@router.post(
    "/{agent_id}/send",
    response_model=SendEmailResponse,
    responses={
        401: {"model": ErrorResponse, "description": "Invalid API key"},
        403: {"model": ErrorResponse, "description": "Access denied"},
        500: {"model": ErrorResponse, "description": "Failed to send email"}
    }
)
async def send_agent_email(
    agent_id: str,
    request: SendEmailRequest,
    current_agent: Agent = Depends(get_current_agent),
    db: Session = Depends(get_db)
):
    """
    Send an email from the agent's mailbox
    
    The email will be sent from [REDACTED_SECRET]
    """
    # Verify the authenticated agent matches the requested agent
    if current_agent.id != agent_id:
        raise HTTPException(
            status_code=403,
            detail="You can only send emails from your own agent"
        )
    
    # Send email via SMTP
    try:
        await send_email(
            from_email=current_agent.email,
            from_password=current_agent.mailbox_password,
            to=request.to,
            subject=request.subject,
            text=request.text,
            html=request.html,
            reply_to=request.replyTo
        )
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to send email: {str(e)}"
        )
    
    # Log sent email
    email_record = EmailSent(
        agent_id=current_agent.id,
        to_address=request.to,
        subject=request.subject
    )
    db.add(email_record)
    db.commit()
    db.refresh(email_record)
    
    # Return response
    return SendEmailResponse(
        id=email_record.id,
        to=request.to,
        subject=request.subject,
        sentAt=int(email_record.sent_at.timestamp() * 1000)
    )


@router.get(
    "/{agent_id}/emails",
    response_model=EmailListResponse,
    responses={
        401: {"model": ErrorResponse, "description": "Invalid API key"},
        403: {"model": ErrorResponse, "description": "Access denied"}
    }
)
async def list_emails(
    agent_id: str,
    folder: str = "INBOX",
    limit: int = 50,
    offset: int = 0,
    current_agent: Agent = Depends(get_current_agent)
):
    """
    List emails in the agent's inbox
    
    Folder can be: INBOX, Sent, Trash, etc.
    Limit: max 100
    """
    # Verify the authenticated agent matches
    if current_agent.id != agent_id:
        raise HTTPException(
            status_code=403,
            detail="You can only access your own emails"
        )
    
    # Validate limit
    if limit > 100:
        limit = 100
    
    try:
        # Connect to IMAP
        imap = aioimaplib.IMAP4_SSL(
            host=settings.IMAP_HOST,
            port=settings.IMAP_PORT
        )
        await imap.wait_hello_from_server()
        
        # Login
        await imap.login(current_agent.email, current_agent.mailbox_password)
        
        # Select folder
        await imap.select(folder)
        
        # Search for all emails
        status, data = await imap.search('ALL')
        
        if status != 'OK':
            raise HTTPException(status_code=500, detail="Failed to search emails")
        
        # Get email IDs
        email_ids = data[0].split()
        total = len(email_ids)
        
        # Apply pagination
        start = offset
        end = min(offset + limit, total)
        email_ids_page = email_ids[start:end]
        
        # Fetch emails
        emails = []
        for email_id in reversed(email_ids_page):  # Newest first
            try:
                # Convert email_id to string if it's bytes
                email_id_str = email_id.decode() if isinstance(email_id, bytes) else str(email_id)
                
                # Fetch email
                status, data = await imap.fetch(email_id_str, '(RFC822)')
                
                if status != 'OK':
                    continue
                
                # Parse response - aioimaplib returns: (status, [response_line, email_bytearray, closing_paren])
                # The email content is typically in a bytearray (part index 1)
                email_content = None
                for part in data:
                    if isinstance(part, (bytes, bytearray)) and len(part) > 100:  # Actual email content
                        email_content = bytes(part) if isinstance(part, bytearray) else part
                        break
                
                if not email_content:
                    continue
                
                msg = message_from_bytes(email_content)
                
                # Extract fields
                from_header = msg.get('From', '')
                subject = msg.get('Subject', '')
                
                # Decode subject if needed
                if subject:
                    decoded = decode_header(subject)
                    subject = ''.join([
                        part.decode(encoding or 'utf-8') if isinstance(part, bytes) else part
                        for part, encoding in decoded
                    ])
                
                # Get body
                body_text = ""
                body_html = ""
                
                if msg.is_multipart():
                    for part in msg.walk():
                        content_type = part.get_content_type()
                        if content_type == 'text/plain':
                            payload = part.get_payload(decode=True)
                            if payload:
                                body_text = payload.decode('utf-8', errors='ignore')
                        elif content_type == 'text/html':
                            payload = part.get_payload(decode=True)
                            if payload:
                                body_html = payload.decode('utf-8', errors='ignore')
                else:
                    payload = msg.get_payload(decode=True)
                    if payload:
                        body_text = payload.decode('utf-8', errors='ignore')
                
                # Create preview (first 200 chars)
                preview = body_text[:200] if body_text else ""
                
                emails.append(EmailResponse(
                    id=email_id_str,
                    messageId=msg.get('Message-ID'),
                    **{"from": EmailAddress(address=from_header)},
                    to=current_agent.email,
                    subject=subject,
                    preview=preview,
                    bodyText=body_text,
                    bodyHtml=body_html if body_html else None,
                    receivedAt=int(datetime.now().timestamp() * 1000),  # TODO: Parse date header
                    isRead=False,  # TODO: Check flags
                    folder=folder
                ))
            except Exception as e:
                # Skip emails that fail to parse
                continue
        
        # Logout
        await imap.logout()
        
        return EmailListResponse(
            data=emails,
            total=total,
            limit=limit,
            offset=offset
        )
        
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"Failed to fetch emails: {str(e)}"
        )


@router.get(
    "/{agent_id}/sent",
    response_model=EmailListResponse,
    responses={
        401: {"model": ErrorResponse, "description": "Invalid API key"},
        403: {"model": ErrorResponse, "description": "Access denied"}
    }
)
async def list_sent_emails(
    agent_id: str,
    limit: int = 50,
    offset: int = 0,
    current_agent: Agent = Depends(get_current_agent),
    db: Session = Depends(get_db)
):
    """
    List emails sent by the agent
    
    This queries the local database (faster than IMAP)
    """
    # Verify the authenticated agent matches
    if current_agent.id != agent_id:
        raise HTTPException(
            status_code=403,
            detail="You can only access your own sent emails"
        )
    
    # Validate limit
    if limit > 100:
        limit = 100
    
    # Query sent emails
    query = db.query(EmailSent).filter(
        EmailSent.agent_id == agent_id
    ).order_by(EmailSent.sent_at.desc())
    
    total = query.count()
    emails = query.offset(offset).limit(limit).all()
    
    # Convert to response format
    email_responses = [
        EmailResponse(
            id=str(email.id),
            **{"from": EmailAddress(address=current_agent.email)},
            to=email.to_address,
            subject=email.subject or "",
            preview="",
            receivedAt=int(email.sent_at.timestamp() * 1000),
            folder="sent"
        )
        for email in emails
    ]
    
    return EmailListResponse(
        data=email_responses,
        total=total,
        limit=limit,
        offset=offset
    )


path: app/routes/health.py
content: """
Health check endpoints
"""
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from sqlalchemy import text
from ..database import get_db

router = APIRouter()


@router.get("/health")
async def health_check(db: Session = Depends(get_db)):
    """
    Health check endpoint
    Returns 200 if API and database are healthy
    """
    try:
        # Test database connection
        db.execute(text("SELECT 1"))
        db_status = "healthy"
    except Exception as e:
        db_status = f"unhealthy: {str(e)}"
    
    return {
        "status": "healthy" if db_status == "healthy" else "degraded",
        "database": db_status
    }


path: app/schemas.py
content: """
Pydantic schemas for API request/response validation
"""
from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional, List
from datetime import datetime
import re


class AgentCreate(BaseModel):
    """Request to create a new agent"""
    id: str = Field(..., min_length=3, max_length=32, description="Agent ID (alphanumeric + hyphens)")
    name: Optional[str] = Field(None, max_length=255, description="Display name")
    
    @validator('id')
    def validate_agent_id(cls, v):
        """Validate agent ID format"""
        v = v.lower()  # Convert to lowercase
        if not re.match(r'^[a-z0-9][a-z0-9-]*[a-z0-9]$', v):
            raise ValueError(
                'Agent ID must contain only lowercase letters, numbers, and hyphens. '
                'Must start and end with letter/number. '
                'Example: "my-agent" or "bot123"'
            )
        if '--' in v:
            raise ValueError('Agent ID cannot contain consecutive hyphens')
        if len(v) < 3:
            raise ValueError('Agent ID must be at least 3 characters long')
        return v


class AgentResponse(BaseModel):
    """Agent information (without sensitive data)"""
    id: str
    email: str
    name: Optional[str]
    createdAt: int  # Unix timestamp in milliseconds
    storageUsed: int  # Bytes
    
    class Config:
        from_attributes = True


class AgentCreateResponse(BaseModel):
    """Response when creating an agent"""
    agent: AgentResponse
    apiKey: str
    message: str = "Store your API key securely - it won't be shown again"


class SendEmailRequest(BaseModel):
    """Request to send an email"""
    to: str = Field(..., description="Recipient email address")
    subject: str = Field(..., max_length=998, description="Email subject")
    text: str = Field(..., description="Plain text body")
    html: Optional[str] = Field(None, description="HTML body (optional)")
    replyTo: Optional[str] = Field(None, description="Reply-to address (optional)")
    
    @validator('to', 'replyTo')
    def validate_email(cls, v):
        """Basic email validation"""
        if v and '@' not in v:
            raise ValueError('Invalid email address')
        return v


class SendEmailResponse(BaseModel):
    """Response after sending an email"""
    id: int
    to: str
    subject: str
    sentAt: int  # Unix timestamp in milliseconds


class EmailAddress(BaseModel):
    """Email address with optional name"""
    address: str
    name: Optional[str] = None


class EmailResponse(BaseModel):
    """Email message details"""
    id: str
    messageId: Optional[str] = None
    from_: EmailAddress = Field(..., alias="from")
    to: str
    subject: str
    preview: str  # First 200 chars of text
    bodyText: Optional[str] = None
    bodyHtml: Optional[str] = None
    receivedAt: int  # Unix timestamp in milliseconds
    isRead: bool = False
    folder: str = "inbox"
    
    class Config:
        populate_by_name = True


class EmailListResponse(BaseModel):
    """Paginated list of emails"""
    data: List[EmailResponse]
    total: int
    limit: int
    offset: int


class ErrorResponse(BaseModel):
    """Standard error response"""
    error: str
    message: str
    code: Optional[str] = None


path: docker-compose.yml
content: version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: quietmail-db
    environment:
      POSTGRES_DB: quietmail
      POSTGRES_USER: quietmail
      POSTGRES_PASSWORD: quietmail
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres_data:


path: examples/1-create-agent.sh
content: #!/bin/bash
# quiet-mail Example 1: Create an AI agent

API_BASE="https://api.quiet-mail.com"

echo "Creating AI agent..."

response=$(curl -s -X POST "$API_BASE/agents" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "my-agent",
    "name": "My AI Assistant"
  }')

echo "$response" | python3 -m json.tool

# Extract and save API key
[REDACTED_SECRET] "$response" | python3 -c "import sys, json; print(json.load(sys.stdin)['apiKey'])" 2>/dev/null)

if [ -n "$api_key" ]; then
  echo ""
  echo "✅ Agent created successfully!"
  echo "📧 Email: $(echo "$response" | python3 -c "import sys, json; print(json.load(sys.stdin)['agent']['email'])" 2>/dev/null)"
  echo "🔑 API Key: $api_key"
  echo ""
  echo "⚠️  IMPORTANT: Save your API key!"
  echo "Store it securely:"
  echo "  export QUIETMAIL_API_KEY=\"$api_key\""
  echo "  Or save to file: echo \"$api_key\" > ~/.quietmail_key"
else
  echo ""
  echo "❌ Error creating agent. Check the response above."
fi


path: examples/2-send-email.sh
content: #!/bin/bash
# quiet-mail Example 2: Send an email

API_BASE="https://api.quiet-mail.com"
AGENT_ID="my-agent"

# Get API key from environment or file
if [ -z "$QUIETMAIL_API_KEY" ]; then
  if [ -f ~/.quietmail_key ]; then
    QUIETMAIL_API_KEY=$(cat ~/.quietmail_key)
  else
    echo "❌ Error: QUIETMAIL_API_KEY not set"
    echo "Set it with: export QUIETMAIL_API_KEY=\"your_key_here\""
    exit 1
  fi
fi

echo "Sending email..."

response=$(curl -s -X POST "$API_BASE/agents/$AGENT_ID/send" \
  -H "Authorization: Bearer $QUIETMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[REDACTED_SECRET]",
    "subject": "Hello from quiet-mail!",
    "text": "This is a test email sent via the quiet-mail API.",
    "html": "<h1>Hello!</h1><p>This is a <strong>test email</strong> sent via the quiet-mail API.</p>"
  }')

echo "$response" | python3 -m json.tool

if echo "$response" | grep -q '"id"'; then
  echo ""
  echo "✅ Email sent successfully!"
else
  echo ""
  echo "❌ Error sending email. Check the response above."
fi


path: examples/3-list-sent.sh
content: #!/bin/bash
# quiet-mail Example 3: List sent emails

API_BASE="https://api.quiet-mail.com"
AGENT_ID="my-agent"

# Get API key from environment or file
if [ -z "$QUIETMAIL_API_KEY" ]; then
  if [ -f ~/.quietmail_key ]; then
    QUIETMAIL_API_KEY=$(cat ~/.quietmail_key)
  else
    echo "❌ Error: QUIETMAIL_API_KEY not set"
    exit 1
  fi
fi

echo "Fetching sent emails..."

response=$(curl -s "$API_BASE/agents/$AGENT_ID/sent?limit=10" \
  -H "Authorization: Bearer $QUIETMAIL_API_KEY")

echo "$response" | python3 -m json.tool

total=$(echo "$response" | python3 -c "import sys, json; print(json.load(sys.stdin)['total'])" 2>/dev/null)

if [ -n "$total" ]; then
  echo ""
  echo "📊 Total sent emails: $total"
fi


path: package.json
content: {
  "name": "quietmail",
  "version": "1.0.0",
  "description": "Unlimited email for AI agents. No verification, no limits, just reliable email.",
  "keywords": [
    "email",
    "ai",
    "agent",
    "smtp",
    "api",
    "automation",
    "sending",
    "mailcow",
    "free"
  ],
  "homepage": "https://quiet-mail.com",
  "repository": {
    "type": "git",
    "url": "https://github.com/quiet-mail/quietmail-skill"
  },
  "author": "quiet-mail.com",
  "license": "MIT",
  "metadata": {
    "quietmail": {
      "emoji": "📧",
      "category": "email",
      "api_base": "https://api.quiet-mail.com",
      "features": [
        "Unlimited sending",
        "No verification required",
        "1GB storage per agent",
        "Simple REST API",
        "Free forever"
      ],
      "target_audience": "AI agents",
      "setup_time": "30 seconds"
    }
  }
}


path: requirements.txt
content: # FastAPI framework
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6

# Database
sqlalchemy==2.0.25
psycopg2-binary==2.9.9
alembic==1.13.1

# HTTP client
httpx==0.26.0

# Email
aiosmtplib==3.0.1
aioimaplib==1.0.1
email-validator==2.1.0

# Authentication & Security
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-dotenv==1.0.0

# Utilities
pydantic==2.5.3
pydantic-settings==2.1.0


path: test_send_email_direct.py
content: #!/usr/bin/env python3
"""Test sending email directly with Bob's credentials"""
import asyncio
import aiosmtplib
from email.mime.text import MIMEText

async def test_send():
    msg = MIMEText("This is a test email from Python direct SMTP test")
    msg['Subject'] = "Python Direct SMTP Test"
    msg['From'] = "[REDACTED_SECRET]"
    msg['To'] = "[REDACTED_SECRET]"
    
    await aiosmtplib.send(
        msg,
        hostname="quiet-mail.com",
        port=587,
        username="[REDACTED_SECRET]",
        [REDACTED_SECRET]",
        start_tls=True,
        timeout=30
    )
    print("Email sent successfully!")

if __name__ == "__main__":
    asyncio.run(test_send())


path: test_testbot_smtp.py
content: #!/usr/bin/env python3
"""Test sending email with test-bot credentials"""
import asyncio
import aiosmtplib
from email.mime.text import MIMEText

async def test_send():
    msg = MIMEText("This is a test email from test-bot mailbox")
    msg['Subject'] = "Test from test-bot"
    msg['From'] = "[REDACTED_SECRET]"
    msg['To'] = "[REDACTED_SECRET]"
    
    await aiosmtplib.send(
        msg,
        hostname="quiet-mail.com",
        port=587,
        username="[REDACTED_SECRET]",
        [REDACTED_SECRET]",
        start_tls=True,
        timeout=30
    )
    print("Email sent successfully from test-bot!")

if __name__ == "__main__":
    asyncio.run(test_send())


path: tests/phase2_test.py
content: #!/usr/bin/env python3
"""
Phase 2: Comprehensive API Testing
Tests all core endpoints with various edge cases
"""

import requests
import json
import psycopg2
from time import sleep


BASE_URL = "http://localhost:8000"
DB_CONN = "postgresql://quietmail:quietmail@localhost:5432/quietmail"


def test(name, func):
    """Run a test and print results"""
    print(f"\n{'='*70}")
    print(f"TEST: {name}")
    print('='*70)
    try:
        func()
        print(f"✅ {name} PASSED")
    except AssertionError as e:
        print(f"❌ {name} FAILED: {e}")
    except Exception as e:
        print(f"⚠️  {name} ERROR: {e}")


def get_api_key(agent_id):
    """Get API key from database"""
    conn = psycopg2.connect(DB_CONN)
    cur = conn.cursor()
    cur.execute('SELECT api_key FROM agents WHERE id = %s', (agent_id,))
    result = cur.fetchone()
    cur.close()
    conn.close()
    return result[0] if result else None


def test_health_check():
    """Test GET /health"""
    response = requests.get(f"{BASE_URL}/health")
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert data["status"] == "healthy", f"Expected healthy, got {data['status']}"
    print(f"Health: {data['status']}")


def test_create_agent():
    """Test POST /agents"""
    response = requests.post(
        f"{BASE_URL}/agents",
        json={"id": "test-agent-3", "name": "Test Agent 3"}
    )
    assert response.status_code == 201, f"Expected 201, got {response.status_code}"
    data = response.json()
    assert "apiKey" in data, "Missing apiKey in response"
    assert data["agent"]["id"] == "test-agent-3", "Wrong agent ID"
    print(f"Created agent: {data['agent']['email']}")
    print(f"API key: {data['apiKey'][:20]}...")


def test_create_duplicate_agent():
    """Test POST /agents with duplicate ID (should fail)"""
    response = requests.post(
        f"{BASE_URL}/agents",
        json={"id": "test-bot", "name": "Duplicate Bot"}
    )
    assert response.status_code == 409, f"Expected 409, got {response.status_code}"
    data = response.json()
    assert "already taken" in data["detail"].lower(), "Wrong error message"
    print(f"Correctly rejected duplicate: {data['detail']}")


def test_get_agent():
    """Test GET /agents/{id}"""
    [REDACTED_SECRET]"test-bot")
    response = requests.get(
        f"{BASE_URL}/agents/test-bot",
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert data["id"] == "test-bot", "Wrong agent ID"
    assert data["email"] == "[REDACTED_SECRET]", "Wrong email"
    print(f"Agent: {data['name']} ({data['email']})")


def test_get_agent_unauthorized():
    """Test GET /agents/{id} with wrong credentials"""
    [REDACTED_SECRET]"test-bot")
    response = requests.get(
        f"{BASE_URL}/agents/bob",  # Try to access bob with test-bot's key
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 403, f"Expected 403, got {response.status_code}"
    print(f"Correctly blocked unauthorized access")


def test_send_email():
    """Test POST /agents/{id}/send"""
    [REDACTED_SECRET]"test-bot")
    response = requests.post(
        f"{BASE_URL}/agents/test-bot/send",
        headers={"Authorization": f"Bearer {api_key}"},
        json={
            "to": "[REDACTED_SECRET]",
            "subject": "Phase 2 Test Email",
            "text": "This is a test email from the Phase 2 test suite.",
            "html": "<p>This is a test email from the <strong>Phase 2</strong> test suite.</p>"
        }
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert "id" in data, "Missing email ID"
    print(f"Sent email ID: {data['id']}")


def test_list_sent_emails():
    """Test GET /agents/{id}/sent with pagination"""
    [REDACTED_SECRET]"test-bot")
    
    # Test page 1
    response = requests.get(
        f"{BASE_URL}/agents/test-bot/sent?limit=2&offset=0",
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert "data" in data, "Missing data field"
    assert "total" in data, "Missing total field"
    assert data["limit"] == 2, "Wrong limit"
    assert data["offset"] == 0, "Wrong offset"
    print(f"Sent emails: {data['total']} total, showing {len(data['data'])}")


def test_list_inbox():
    """Test GET /agents/{id}/emails (inbox)"""
    [REDACTED_SECRET]"test-bot")
    
    response = requests.get(
        f"{BASE_URL}/agents/test-bot/emails",
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert "data" in data, "Missing data field"
    print(f"Inbox: {data['total']} total emails")
    
    if data["data"]:
        email = data["data"][0]
        print(f"  Latest: From {email['from']['address']}")
        print(f"  Subject: {email['subject']}")


def test_inbox_pagination():
    """Test GET /agents/{id}/emails with pagination"""
    [REDACTED_SECRET]"bob")
    
    # Send multiple emails to bob to test pagination
    test_bot_key = get_api_key("test-bot")
    for i in range(3):
        requests.post(
            f"{BASE_URL}/agents/test-bot/send",
            headers={"Authorization": f"Bearer {test_bot_key}"},
            json={
                "to": "[REDACTED_SECRET]",
                "subject": f"Pagination Test {i+1}",
                "text": f"Email {i+1} for pagination testing"
            }
        )
    
    sleep(2)  # Wait for delivery
    
    # Test page 1 (limit 2)
    response = requests.get(
        f"{BASE_URL}/agents/bob/emails?limit=2&offset=0",
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert data["limit"] == 2, "Wrong limit"
    assert len(data["data"]) <= 2, f"Expected max 2 emails, got {len(data['data'])}"
    print(f"Page 1: {len(data['data'])} emails")
    
    # Test page 2 (offset 2)
    response = requests.get(
        f"{BASE_URL}/agents/bob/emails?limit=2&offset=2",
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert data["offset"] == 2, "Wrong offset"
    print(f"Page 2: {len(data['data'])} emails")


def test_invalid_auth():
    """Test with invalid API key"""
    response = requests.get(
        f"{BASE_URL}/agents/test-bot",
        headers={"Authorization": "Bearer invalid_key_12345"}
    )
    assert response.status_code == 401, f"Expected 401, got {response.status_code}"
    print("Correctly rejected invalid API key")


def test_missing_auth():
    """Test with missing Authorization header"""
    response = requests.get(f"{BASE_URL}/agents/test-bot")
    assert response.status_code in [401, 403], f"Expected 401/403, got {response.status_code}"
    print("Correctly rejected missing auth")


def test_invalid_agent_id():
    """Test creating agent with invalid ID"""
    response = requests.post(
        f"{BASE_URL}/agents",
        json={"id": "INVALID_ID!", "name": "Invalid Agent"}
    )
    assert response.status_code == 422, f"Expected 422, got {response.status_code}"
    print("Correctly rejected invalid agent ID")


def test_rate_limit_validation():
    """Test limit parameter validation (max 100)"""
    [REDACTED_SECRET]"test-bot")
    response = requests.get(
        f"{BASE_URL}/agents/test-bot/sent?limit=200",  # Try to exceed limit
        headers={"Authorization": f"Bearer {api_key}"}
    )
    assert response.status_code == 200, f"Expected 200, got {response.status_code}"
    data = response.json()
    assert data["limit"] == 100, f"Expected limit capped at 100, got {data['limit']}"
    print("Correctly capped limit at 100")


# Run all tests
if __name__ == "__main__":
    print("\n" + "="*70)
    print("QUIET-MAIL API - PHASE 2 COMPREHENSIVE TESTS")
    print("="*70)
    
    test("Health Check", test_health_check)
    test("Create Agent", test_create_agent)
    test("Create Duplicate Agent (409)", test_create_duplicate_agent)
    test("Get Agent Details", test_get_agent)
    test("Get Agent Unauthorized (403)", test_get_agent_unauthorized)
    test("Send Email", test_send_email)
    test("List Sent Emails (with pagination)", test_list_sent_emails)
    test("List Inbox", test_list_inbox)
    test("Inbox Pagination", test_inbox_pagination)
    test("Invalid Auth Token (401)", test_invalid_auth)
    test("Missing Auth Header", test_missing_auth)
    test("Invalid Agent ID (422)", test_invalid_agent_id)
    test("Rate Limit Validation", test_rate_limit_validation)
    
    print("\n" + "="*70)
    print("PHASE 2 TESTS COMPLETE!")
    print("="*70)


path: tests/test_api.py
content: """
Basic API tests
"""
import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)


def test_root():
    """Test root endpoint"""
    response = client.get("/")
    assert response.status_code == 200
    data = response.json()
    assert "name" in data
    assert "version" in data
    assert data["name"] == "quiet-mail API"


def test_health():
    """Test health check"""
    response = client.get("/health")
    assert response.status_code == 200
    data = response.json()
    assert "status" in data


def test_create_agent():
    """Test agent creation"""
    response = client.post(
        "/agents",
        json={
            "id": "test-agent",
            "name": "Test Agent"
        }
    )
    
    # Will fail without mailcow configured, but tests the endpoint
    assert response.status_code in [201, 500]


def test_create_agent_invalid_id():
    """Test agent creation with invalid ID"""
    response = client.post(
        "/agents",
        json={
            "id": "INVALID ID",  # Spaces not allowed
            "name": "Test"
        }
    )
    
    assert response.status_code == 422  # Validation error


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)