Metadata-Version: 2.4
Name: genro-mail-proxy
Version: 0.3.5
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: 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: aioresponses>=0.7.0; extra == 'all'
Requires-Dist: aiosmtpd>=1.4.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: 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: 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'
Provides-Extra: dev
Requires-Dist: aioresponses>=0.7.0; extra == 'dev'
Requires-Dist: aiosmtpd>=1.4.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'
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'
Description-Content-Type: text/markdown

# genro-mail-proxy

[![PyPI version](https://img.shields.io/pypi/v/genro-mail-proxy)](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)

Asynchronous email dispatcher microservice with scheduling, rate limiting, attachments (S3/URL/base64), REST API (FastAPI), and Prometheus metrics.

## Why Use an Email Proxy?

Instead of directly connecting to SMTP servers from your application, genro-mail-proxy provides a **decoupled, resilient email delivery layer** with:

- ⚡ **19x faster requests** (32ms vs 620ms) - non-blocking async operations
- 🔄 **Never lose messages** - automatic retry, guaranteed persistence
- 🎯 **Connection pooling** - 10-50x faster for burst sends
- 📊 **Centralized monitoring** - Prometheus metrics, not scattered logs
- 🛡️ **Built-in rate limiting** - shared across all app instances
- 🎛️ **Priority queuing** - immediate/high/medium/low with automatic ordering

**See [Architecture Overview](docs/architecture_overview.rst)** for detailed comparison with direct SMTP.

## Main integration points:

- REST control plane secured by ``X-API-Token`` for queue management and configuration.
- Outbound ``proxy_sync`` call towards Genropy, authenticated via basic auth and configured through ``[client]`` in ``config.ini``.
- Delivery reports and Prometheus metrics to monitor message lifecycle and rate limiting.
- Unified SQLite storage with a single ``messages`` table that tracks queue state (`priority`, `deferred_ts`) and delivery lifecycle (`sent_ts`, `error_ts`, `reported_ts`).
- Background loops:
  - **SMTP dispatch loop** selects records from ``messages`` that lack ``sent_ts``/``error_ts`` and have ``deferred_ts`` in the past, enforces rate limits, then stamps ``sent_ts`` or ``error_ts``/``error``.
  - **Client report loop** batches completed items (sent/error/deferred) that are still missing ``reported_ts`` and posts them to the upstream ``proxy_sync`` endpoint; on acknowledgement the records receive ``reported_ts`` and are later purged according to the retention window.

## Quick start

```bash
docker build -t genro-mail-proxy .
docker run -p 8000:8000 \
  -e GMP_CLIENT_SYNC_URL=https://your-app/proxy_sync \
  -e GMP_CLIENT_SYNC_USER=syncuser \
  -e GMP_CLIENT_SYNC_PASSWORD=syncpass \
  -e GMP_API_TOKEN=your-secret-token \
  genro-mail-proxy
```

See `config.ini.example` for all available environment variables (all prefixed with `GMP_`).

## Example client

A complete integration example is provided in `example_client.py`. This demonstrates the recommended pattern for integrating with the mail service:

```bash
# Install dependencies
pip install fastapi uvicorn aiohttp

# Configure your email address
nano example_config.ini  # Edit recipient_email

# Start the example client
python3 example_client.py

# Send test email
curl -X POST http://localhost:8081/send-test-email
```

The example shows:
- Local-first persistence (never lose messages)
- Async submission to mail service
- run-now trigger for fast delivery
- Delivery report handling via proxy_sync

**See [Example Client Documentation](docs/example_client.rst)** for detailed walkthrough.

## Configuration highlights

- ``[delivery]`` exposes ``delivery_report_retention_seconds`` to control how long reported messages stay in the ``messages`` table (default seven days).
- ``/commands/add-messages`` validates each payload (``id``, ``from``, ``to`` etc.), enqueues valid messages with `priority=2` when omitted, and returns a response with queued count plus a `rejected` list containing `{"id","reason"}` entries for failures.

## Attachment Handling

genro-mail-proxy supports multiple attachment sources with flexible routing.

### Storage Path Formats

| Format | Example | Description |
|--------|---------|-------------|
| `base64:content` | `base64:SGVsbG8=` | Inline base64-encoded content |
| `/absolute/path` | `/tmp/attachments/file.pdf` | Local filesystem absolute path |
| `relative/path` | `uploads/doc.pdf` | Filesystem relative to configured base_dir |
| `@params` | `@doc_id=123&version=2` | HTTP POST to default endpoint |
| `@[url]params` | `@[https://api.example.com]id=456` | HTTP POST to specific URL |

### Attachment Configuration

```ini
[attachments]
# Filesystem fetcher base directory (for relative paths)
base_dir = /var/attachments

# HTTP fetcher default endpoint
http_endpoint = https://api.example.com/attachments
http_auth_method = bearer  # none, bearer, or basic
http_auth_token = your-secret-token
# For basic auth:
# http_auth_user = username
# http_auth_password = password
```

## Attachment Cache

A two-tiered cache (memory + disk) reduces redundant fetches for frequently used attachments.

### MD5 Cache Marker

Filenames can include an MD5 hash marker for cache lookup:

```text
report_{MD5:a1b2c3d4e5f6}.pdf
```

The marker is extracted for cache lookup and removed from the final filename.

### Cache Configuration

```ini
[attachments]
# Memory cache (LRU)
cache_memory_max_items = 100
cache_memory_ttl_seconds = 3600

# Disk cache (optional, for large files)
cache_disk_dir = /var/cache/mail-proxy
cache_disk_ttl_seconds = 86400

# Files larger than this threshold use disk cache
cache_memory_max_size_bytes = 1048576  # 1MB
```

### Cache Behavior

- **Small files** (< `cache_memory_max_size_bytes`): stored in memory LRU cache
- **Large files**: stored on disk if `cache_disk_dir` is configured
- **Cache lookup**: if filename contains `{MD5:hash}`, cache is checked before fetching
- **Cache population**: after fetch, content is cached using computed MD5

## Development

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

## License

Apache License 2.0 — see [LICENSE](LICENSE) for details.

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