Metadata-Version: 2.4
Name: authmail-relay
Version: 0.5.0
Summary: Self-hosted SMTP relay for auth emails.
Author: authmail-relay maintainers
License-Expression: MIT
Project-URL: Homepage, https://github.com/hwan96-ai/authmail-relay
Project-URL: Repository, https://github.com/hwan96-ai/authmail-relay
Project-URL: Documentation, https://hwan96-ai.github.io/authmail-relay/
Project-URL: Issues, https://github.com/hwan96-ai/authmail-relay/issues
Project-URL: Changelog, https://github.com/hwan96-ai/authmail-relay/blob/master/CHANGELOG.md
Keywords: email,smtp,fastapi,magic-link,otp,transactional-email,auth-email,smtp-relay
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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 :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=9.0.3; extra == "dev"
Requires-Dist: httpx<1,>=0.28.1; extra == "dev"
Provides-Extra: http
Requires-Dist: fastapi<1,>=0.115; extra == "http"
Requires-Dist: uvicorn<1,>=0.30; extra == "http"
Requires-Dist: httpx<1,>=0.28.1; extra == "http"
Requires-Dist: prometheus-client>=0.25.0; extra == "http"
Requires-Dist: python-json-logger>=2.0; extra == "http"
Dynamic: license-file

# authmail-relay

[![PyPI](https://img.shields.io/pypi/v/authmail-relay.svg)](https://pypi.org/project/authmail-relay/)
[![Python](https://img.shields.io/badge/python-%E2%89%A53.10-blue)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

> Self-hosted SMTP relay for auth emails.

[한국어 README](README.ko.md) · [HTML Usage Guide](https://hwan96-ai.github.io/authmail-relay/usage.html) · [한국어 사용 가이드](https://hwan96-ai.github.io/authmail-relay/usage.ko.html)

<sub>HTML guides render through GitHub Pages.</sub>

`authmail-relay` is a small, self-hosted service that sends **magic-link**, **OTP**,
and **password-reset** emails through your own SMTP account. It keeps SMTP
credentials and email-template logic out of every app that needs to send auth
mail — your apps call one internal HTTP endpoint with a Bearer API key, or
import it as a Python library.

```text
App / Auth server
      │  Bearer API key
      ▼
  authmail-relay    ← SMTP credentials live here
      │
      ▼
 SMTP provider  ──►  User inbox
```

### What it is

- A small internal **auth-email gateway** for teams that already have SMTP.
- Sends transactional auth emails: magic links, OTP codes, password resets,
  plus arbitrary templated mail.
- Built for Python/FastAPI teams, but the HTTP API is language-agnostic.

### What it is *not*

- **Not a mail server** — it talks to your existing SMTP provider (Gmail, SES
  SMTP, an internal relay, etc.). It does not accept inbound mail or handle MX.
- **Not a full auth platform** — it sends auth emails; it does not generate,
  store, verify, or expire login tokens, manage sessions, or store users.
- **Not a marketing/bulk-email platform** — no bounce processing, suppression
  lists, analytics dashboards, or deliverability tooling.
- **Not a managed-email replacement** for Resend, Postmark, SendGrid, Mailgun,
  or SES — those bring deliverability, reputation, and SLAs that a small
  self-hosted gateway cannot match. See [alternatives](docs/alternatives.md).

---

## Package names

The repo, the PyPI distribution, and the Python import package now share the
same project name.

| | Name |
|---|---|
| Repository / service | `authmail-relay` |
| PyPI distribution | `authmail-relay` |
| Python import | `authmail_relay` |

```bash
pip install authmail-relay
```

```python
import authmail_relay
```

> **Migration note.** This project was previously published on PyPI as
> `hwan-email-service` with the import package `email_service`, under the
> repo name `email-service`. A thin `email_service` compatibility shim is
> shipped in this release so existing `import email_service` / `from
> email_service import …` code keeps working and emits a `DeprecationWarning`.
> The shim will be removed in a future major release — update imports to
> `authmail_relay` when convenient. See [CHANGELOG.md](CHANGELOG.md) for the
> rename entry.

---

## Install

```bash
# Library mode (no extra deps)
pip install authmail-relay

# HTTP service mode (FastAPI + uvicorn)
pip install "authmail-relay[http]"
```

Requirements: **Python 3.10+**.

Install the latest unreleased commit straight from git:

```bash
pip install "authmail-relay[http] @ git+https://github.com/hwan96-ai/authmail-relay.git"
```

---

## Quickstart — HTTP service mode

Run `authmail-relay` as a standalone service. Other apps call it over HTTP with
a Bearer API key. SMTP credentials live in this service's environment only.

```bash
pip install "authmail-relay[http]"

export SMTP_HOST=smtp.gmail.com
export SMTP_USER=sender@gmail.com
export SMTP_PASSWORD=app-password
export API_KEY=$(openssl rand -hex 32)

python -m authmail_relay
# → Uvicorn running on http://127.0.0.1:8000
```

In another terminal:

```bash
curl -X POST http://127.0.0.1:8000/send \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"user@example.com","subject":"Hi","html_body":"<p>Hello</p>"}'
# → {"sent":true}
```

> `$API_KEY` only exists in the shell where it was exported. If `curl` runs in
> a second terminal, re-export `API_KEY` there or load it from `.env` first.

OpenAPI docs: <http://127.0.0.1:8000/docs>.

Full HTTP endpoint reference, dry-run mode, idempotency, and the Python client
SDK: [docs/api.md](docs/api.md).

### 30-second SMTP smoke test

If you just want to verify SMTP credentials, skip the HTTP server entirely:

```bash
export SMTP_HOST=smtp.gmail.com
export SMTP_USER=sender@gmail.com
export SMTP_PASSWORD=app-password

python -m authmail_relay test --to me@example.com
#   → SendResult(sent=True, error_code=None, ..., message_id='<...@host>')
```

Exits `0` on success, `1` on failure with `error_code` printed.

---

## Quickstart — library mode

Import `authmail_relay` directly inside one Python/FastAPI app. Useful when you
don't need a separate internal HTTP gateway.

```python
from authmail_relay import SmtpSender, MagicLinkNotifier, OTPNotifier
from authmail_relay.sender import SmtpConfig

sender = SmtpSender(SmtpConfig(
    host="smtp.gmail.com",
    user="sender@gmail.com",
    password="app-password",
))

# One-off HTML mail
sender.send("user@example.com", "Hi", "<p>Hello</p>")

# Magic link
# The caller owns token generation. For custom auth, generate a high-entropy
# opaque token; if you use an auth provider (e.g. Supabase), use the token it issues.
import secrets
token = secrets.token_urlsafe(32)
MagicLinkNotifier(sender, base_url="https://myapp.com").send(
    "user@example.com", "User Name", token,
)

# OTP
OTPNotifier(sender).send("user@example.com", "User Name", "482901")
```

Full library API (`SmtpSender`, `MagicLinkNotifier`, `OTPNotifier`,
`TemplateNotifier`, custom notifiers, retries): [docs/api.md](docs/api.md#library-mode).

For the HTTP client SDK (`EmailServiceClient`), see [docs/api.md#library-mode](docs/api.md#library-mode).

---

## Security — read before deploying

`authmail-relay` is designed as an **internal** service. A self-hosted auth
email service can be abused if exposed incorrectly. Treat the following as
hard requirements before any production deploy:

- **Do not expose directly to the public internet.** Put it behind a reverse
  proxy or API gateway on a private network / VPC.
- **Terminate TLS at the edge** (nginx, Traefik, your gateway).
- **Rate-limit failed auth attempts at the edge.** The app's built-in
  per-bearer rate limit applies to *authenticated* requests; it does not
  protect against blind Bearer-token guessing.
- **Protect `/docs` and `/metrics`** — either disable at the edge or require
  auth. Set `METRICS_REQUIRE_AUTH=true` for `/metrics`.
- **Store `API_KEY`, `WEBHOOK_SECRET`, and SMTP credentials in environment
  variables or a secret manager.** Generate `API_KEY` with
  `openssl rand -hex 32`. Never commit them.

**Trust boundary:** this service sends auth emails. It does **not** generate,
store, verify, or expire login tokens. The caller is responsible for token
entropy (at least `secrets.token_urlsafe(32)`), expiration, single-use
enforcement, replay protection, and account-state checks.

If you front authmail-relay with Supabase Auth or another auth provider, the
provider — not authmail-relay — generates and verifies the token. See
[docs/supabase-auth.md](docs/supabase-auth.md).

For the full production checklist, see [docs/deployment.md](docs/deployment.md).
Vulnerability reporting: [SECURITY.md](SECURITY.md).

---

## Docker

```bash
cp .env.example .env
# Edit .env: set SMTP_HOST / SMTP_USER / SMTP_PASSWORD / API_KEY
#   API_KEY=$(openssl rand -hex 32)

docker compose up -d --build
curl http://127.0.0.1:8000/health   # → {"status":"ok"}
```

The provided `docker-compose.yml` publishes `8000:8000` on the host for
convenience. **Do not expose this port to the public internet** — see the
deployment guide for production hardening.

Local development with [Mailpit](https://mailpit.axllent.org/) (no real SMTP
needed):

```bash
docker compose -f docker-compose.dev.yml up -d --build
# Mailpit UI: http://127.0.0.1:8025
```

---

## Configuration

Required env vars: `SMTP_HOST`, `API_KEY`.

The service fails fast at startup if required vars are missing.

Full env-var reference (rate limits, idempotency, webhook SSRF allowlist,
metrics auth, structured logs, retry tuning): [docs/configuration.md](docs/configuration.md).

A working `.env.example` is included in the repo root.

---

## Webhooks (async send)

Pass `webhook_url` in a `/send*` request body to receive the delivery result
asynchronously. The service signs the payload with both a legacy V1 header and
a V2 timestamp-bound header; new receivers should validate V2.

Webhook payload format, signature verification, the V1 → V2 migration, and
local testing with `docker-compose.dev.yml`: [docs/webhooks.md](docs/webhooks.md).

---

## Observability

Opt-in features, all off by default:

- **Prometheus metrics** at `/metrics` (`METRICS_ENABLED=true`,
  `METRICS_REQUIRE_AUTH=true` recommended).
- **Structured JSON logs** (`EMAIL_SERVICE_LOG_FORMAT=json`). Recipient
  addresses are hashed (SHA-256, first 8 chars) — never logged in plaintext.
- **`X-Request-ID` propagation** end-to-end from gateway → authmail-relay →
  SMTP send logs.
- **SMTP retries** with bounded exponential backoff (library mode,
  `max_retries=N`).

Full operations guide: [docs/operations.md](docs/operations.md).

---

## Examples

End-to-end integration snippets for common Python frameworks:

- [examples/fastapi_integration.py](examples/fastapi_integration.py)
- [examples/django_integration.py](examples/django_integration.py)
- [examples/flask_integration.py](examples/flask_integration.py)
- [examples/integration_test_with_capture.py](examples/integration_test_with_capture.py)
  — `.eml` capture mode for integration tests without a real SMTP server.

---

## When to use what

| If you need… | Use |
|---|---|
| Managed deliverability, bounces, SLA, dashboards | Resend / Postmark / SendGrid / Mailgun / Amazon SES |
| Full user/session/RBAC/password flows | Supabase Auth, Ory Kratos, Keycloak, Authentik, Appwrite |
| A mail library inside one FastAPI app | [fastapi-mail](https://github.com/sabuhish/fastapi-mail) |
| An internal HTTP gateway that keeps your existing SMTP credentials out of every app | **authmail-relay** |

A longer comparison, including self-hosted email platforms, lives in
[docs/alternatives.md](docs/alternatives.md).

Using authmail-relay alongside Supabase Auth? See
[Supabase Auth integration notes](docs/supabase-auth.md) — authmail-relay
delivers the email, Supabase Auth still owns tokens, sessions, and
`auth.uid()` identity. Per-provider notes index:
[docs/providers.md](docs/providers.md).

---

## Development

```bash
git clone https://github.com/hwan96-ai/authmail-relay.git
cd authmail-relay

pip install -e ".[dev,http]"
python -m pytest tests/ -v
```

Tests do not connect to a real SMTP server (`smtplib.SMTP` is mocked).

---

## License

[MIT](LICENSE).
