Metadata-Version: 2.4
Name: genro-mail-proxy
Version: 0.6.7
Summary: Asynchronous email dispatcher microservice with scheduling, rate limiting, attachments, and a FastAPI REST API.
Project-URL: Homepage, https://github.com/genropy/genro-mail-proxy
Project-URL: Documentation, https://genro-mail-proxy.readthedocs.io
Project-URL: Repository, https://github.com/genropy/genro-mail-proxy
Project-URL: Issues, https://github.com/genropy/genro-mail-proxy/issues
Author-email: Genropy Team <info@genropy.org>
License-Expression: Apache-2.0
License-File: LICENSE
License-File: LICENSE-BSL-1.1
License-File: NOTICE
Keywords: async,email,fastapi,genro,genropy,mail-proxy,smtp
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Topic :: Communications :: Email
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: aiosmtplib>=2.0.0
Requires-Dist: aiosqlite>=0.20.0
Requires-Dist: click>=8.1.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: prometheus-client>=0.20.0
Requires-Dist: pydantic>=2.8.0
Requires-Dist: requests>=2.31.0
Requires-Dist: rich>=13.0.0
Requires-Dist: uvicorn>=0.30.0
Provides-Extra: all
Requires-Dist: aioimaplib>=1.0.0; extra == 'all'
Requires-Dist: aioresponses>=0.7.0; extra == 'all'
Requires-Dist: aiosmtpd>=1.4.0; extra == 'all'
Requires-Dist: bandit>=1.7.0; extra == 'all'
Requires-Dist: fsspec>=2024.1.0; extra == 'all'
Requires-Dist: httpx>=0.27.0; extra == 'all'
Requires-Dist: mypy>=1.0; extra == 'all'
Requires-Dist: myst-parser>=4.0.0; extra == 'all'
Requires-Dist: psycopg-pool>=3.1.0; extra == 'all'
Requires-Dist: psycopg[binary]>=3.1.0; extra == 'all'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0; extra == 'all'
Requires-Dist: pytest-docker>=3.0.0; extra == 'all'
Requires-Dist: pytest>=8.0; extra == 'all'
Requires-Dist: ruff>=0.1.0; extra == 'all'
Requires-Dist: s3fs>=2024.1.0; extra == 'all'
Requires-Dist: sphinx-autodoc-typehints>=3.0.0; extra == 'all'
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == 'all'
Requires-Dist: sphinx>=8.0.0; extra == 'all'
Requires-Dist: sphinxcontrib-mermaid>=1.0.0; extra == 'all'
Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'all'
Provides-Extra: bounce
Requires-Dist: aioimaplib>=1.0.0; extra == 'bounce'
Provides-Extra: dev
Requires-Dist: aioresponses>=0.7.0; extra == 'dev'
Requires-Dist: aiosmtpd>=1.4.0; extra == 'dev'
Requires-Dist: bandit>=1.7.0; extra == 'dev'
Requires-Dist: httpx>=0.27.0; extra == 'dev'
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest-docker>=3.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: testcontainers[postgres]>=4.0.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: myst-parser>=4.0.0; extra == 'docs'
Requires-Dist: sphinx-autodoc-typehints>=3.0.0; extra == 'docs'
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == 'docs'
Requires-Dist: sphinx>=8.0.0; extra == 'docs'
Requires-Dist: sphinxcontrib-mermaid>=1.0.0; extra == 'docs'
Provides-Extra: large-files
Requires-Dist: fsspec>=2024.1.0; extra == 'large-files'
Requires-Dist: s3fs>=2024.1.0; extra == 'large-files'
Provides-Extra: postgresql
Requires-Dist: psycopg-pool>=3.1.0; extra == 'postgresql'
Requires-Dist: psycopg[binary]>=3.1.0; extra == 'postgresql'
Description-Content-Type: text/markdown

# genro-mail-proxy

[![PyPI version](https://img.shields.io/pypi/v/genro-mail-proxy?style=flat)](https://pypi.org/project/genro-mail-proxy/)
[![Tests](https://github.com/genropy/genro-mail-proxy/actions/workflows/tests.yml/badge.svg)](https://github.com/genropy/genro-mail-proxy/actions/workflows/tests.yml)
[![codecov](https://codecov.io/gh/genropy/genro-mail-proxy/branch/main/graph/badge.svg)](https://codecov.io/gh/genropy/genro-mail-proxy)
[![Documentation](https://readthedocs.org/projects/genro-mail-proxy/badge/?version=latest)](https://genro-mail-proxy.readthedocs.io/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

A microservice that decouples email delivery from your application.

## What it does

genro-mail-proxy sits between your application and SMTP servers. Your application sends messages to the proxy via REST API; the proxy handles delivery with:

- **Persistent queue**: Messages are stored in SQLite or PostgreSQL and survive restarts
- **Automatic retry**: Failed deliveries are retried with exponential backoff
- **Rate limiting**: Per-account limits (minute/hour/day) shared across instances
- **Priority queuing**: Four levels (immediate, high, medium, low) with FIFO within each
- **Delivery reports**: Results are posted back to your application via HTTP callback
- **Bounce detection**: IMAP polling for bounces with DSN parsing and hard/soft classification *(BSL 1.1)*
- **Multi-tenancy**: Multiple organizations can share one instance with separate accounts *(BSL 1.1)*
- **PEC support**: Italian certified email (Posta Elettronica Certificata) with receipt tracking *(BSL 1.1)*
- **Large file handling**: Auto-upload attachments to S3/GCS/Azure and replace with download links *(BSL 1.1)*
- **Connection pooling**: SMTP connections are pooled with acquire/release semantics

```text
┌─────────────┐      REST       ┌──────────────────┐      SMTP      ┌─────────────┐
│ Application │ ──────────────► │ genro-mail-proxy │ ─────────────► │ SMTP Server │
└─────────────┘                 └──────────────────┘                └─────────────┘
        ▲                               │
        │                               │
        └───────────────────────────────┘
                 delivery reports
```

## When to use it

Consider this proxy when:

- Multiple application instances need shared rate limits for outbound email
- Email delivery should not block your application's main request flow
- Delivery tracking is needed with central logging and Prometheus metrics
- Retry logic is required without implementing it in every service
- Multi-tenant isolation is needed for different organizations or environments

## When NOT to use it

This proxy adds operational complexity. Direct SMTP may be simpler when:

- You have a single application instance with low email volume
- Latency is acceptable (direct SMTP adds ~500-600ms per send)
- No retry logic is needed (transactional emails with immediate feedback)
- No rate limiting is required by your SMTP provider
- You prefer fewer moving parts in your infrastructure

## Quick start

**Docker**:

```bash
docker run -p 8000:8000 \
  -e GMP_API_TOKEN=your-secret-token \
  genro-mail-proxy
```

**CLI**:

```bash
pip install genro-mail-proxy
mail-proxy start myserver
```

Then configure a tenant, add an SMTP account, and start sending messages.

## Command-line interface

The `mail-proxy` CLI manages instances without going through the HTTP API:

```bash
# Instance management
mail-proxy list                          # List all instances
mail-proxy start myserver                # Start an instance
mail-proxy stop myserver                 # Stop an instance
mail-proxy myserver info                 # Show instance details

# Tenant management
mail-proxy myserver tenants list         # List tenants
mail-proxy myserver tenants add acme     # Add a tenant (interactive)

# Account management (per tenant)
mail-proxy myserver acme accounts list   # List SMTP accounts
mail-proxy myserver acme accounts add    # Add account (interactive)

# Message operations
mail-proxy myserver acme messages list   # List queued messages
mail-proxy myserver acme send email.eml  # Send from .eml file
mail-proxy myserver acme run-now         # Trigger immediate dispatch
```

Each instance stores its configuration in `~/.mail-proxy/<name>/mail_service.db`.
The CLI supports both command-line arguments and interactive prompts for complex operations.

## REST API

The proxy exposes a FastAPI REST API secured by `X-API-Token`:

- `POST /commands/add-messages` - Queue messages for delivery
- `GET /messages` - List queued messages
- `POST /commands/run-now` - Trigger immediate dispatch cycle
- `GET /accounts` - List SMTP accounts
- `GET /metrics` - Prometheus metrics

See [API Reference](https://genro-mail-proxy.readthedocs.io/en/latest/api_reference.html) for details.

## Attachment handling

The proxy supports multiple attachment sources via explicit `fetch_mode`:

| fetch_mode | storage_path example | Description |
| ---------- | -------------------- | ----------- |
| `base64` | `base64:SGVsbG8gV29ybGQ=` | Inline base64-encoded content (requires `base64:` prefix) |
| `filesystem` | `/tmp/file.pdf` | Local filesystem path |
| `endpoint` | `doc_id=123` | HTTP POST to tenant's attachment endpoint |
| `http_url` | `https://storage.example.com/file.pdf` | HTTP GET from external URL |

A two-tiered cache (memory + disk) reduces redundant fetches. Filenames can include an MD5 hash marker (`report_{MD5:abc123}.pdf`) for cache lookup.

### Large file offloading

For attachments exceeding a size threshold, the proxy can upload them to external storage (S3, GCS, Azure, or local filesystem) and replace them with download links in the email body.

```bash
pip install genro-mail-proxy[large-files]
```

Configure per-tenant via `large_file_config`:

```json
{
  "enabled": true,
  "max_size_mb": 10,
  "storage_url": "s3://bucket/mail-attachments",
  "action": "rewrite"
}
```

Actions: `warn` (log only), `reject` (fail message), `rewrite` (upload and replace with link).

## Configuration

Configuration is managed via the CLI. Each instance stores its settings in `~/.mail-proxy/<name>/mail_service.db`.

```bash
# Start an instance (creates it if new)
mail-proxy start myserver

# Add a tenant with attachment endpoint
mail-proxy myserver tenants add
# Interactive prompts: tenant_id, name, base_url, attachment_path, auth method

# Add an SMTP account
mail-proxy myserver acme accounts add
# Interactive prompts: account_id, host, port, user, password, TLS, rate limits
```

For Docker deployments, use environment variables (prefixed with `GMP_`):

```bash
docker run -p 8000:8000 \
  -e GMP_API_TOKEN=your-secret-token \
  -e GMP_DB_PATH=/data/mail_service.db \
  -v mail-data:/data \
  genro-mail-proxy
```

See [Usage](https://genro-mail-proxy.readthedocs.io/en/latest/usage.html) for all options.

## Performance notes

- **Request latency**: ~30ms to queue a message (vs ~600ms for direct SMTP)
- **Throughput**: Limited by SMTP provider rate limits, not the proxy
- **Memory**: Attachment content is held in memory during send; use HTTP endpoints for large files

The SQLite database handles typical workloads but doesn't scale well under high concurrency. For high-volume deployments, use PostgreSQL:

```bash
pip install genro-mail-proxy[postgresql]
```

Then configure the DSN via environment variable or CLI.

## Development

```bash
pip install -e ".[dev]"
pytest
```

## License

This project uses a **dual-license** model:

| License        | Features                                                                                                                       |
|----------------|--------------------------------------------------------------------------------------------------------------------------------|
| **Apache 2.0** | Core functionality: message queue, retry, rate limiting, priority, delivery reports, attachments, SMTP pooling, REST API, CLI |
| **BSL 1.1**    | Multi-Tenancy, Bounce Detection, PEC Support, Large Files                                                                      |

**Apache 2.0** ([LICENSE](LICENSE)): Free for any use.

**BSL 1.1** ([LICENSE-BSL-1.1](LICENSE-BSL-1.1)): Free for testing, development, and non-production. Production use requires a commercial license from Softwell S.r.l. After 2030-01-25, these features convert to Apache 2.0.

See [NOTICE](NOTICE) for full details.

Copyright 2025 Softwell S.r.l. — Genropy Team
