Skip to main content

Self-hosted SMTP relay for auth emails.

Project description

authmail-relay

PyPI Python License: MIT

Self-hosted SMTP relay for auth emails.

한국어 README · HTML Usage Guide · 한국어 사용 가이드

HTML guides render through GitHub Pages.

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.

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.

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
pip install authmail-relay
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 for the rename entry.


Install

# 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:

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.

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:

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.

30-second SMTP smoke test

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

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.

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.

For the HTTP client SDK (EmailServiceClient), see 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.

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


Docker

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 (no real SMTP needed):

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.

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.


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.


Examples

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


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
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.

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


Development

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

authmail_relay-0.5.0.tar.gz (60.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

authmail_relay-0.5.0-py3-none-any.whl (36.0 kB view details)

Uploaded Python 3

File details

Details for the file authmail_relay-0.5.0.tar.gz.

File metadata

  • Download URL: authmail_relay-0.5.0.tar.gz
  • Upload date:
  • Size: 60.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for authmail_relay-0.5.0.tar.gz
Algorithm Hash digest
SHA256 9500650ff2c1d7a17553eda6c6c83114c957315343fc3e1308edc855619d1d60
MD5 6ecdfad07cb631c5e25f2fb4ea13408f
BLAKE2b-256 1f85cca4e2161f4328a962d2801c9cb698e0b008174d6e2db293ea2ff46c3ffd

See more details on using hashes here.

Provenance

The following attestation bundles were made for authmail_relay-0.5.0.tar.gz:

Publisher: release.yml on hwan96-ai/authmail-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file authmail_relay-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: authmail_relay-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 36.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for authmail_relay-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ec26134bc2731112429bfc059a0335515370948d664a8dc6131a2573e9cbf349
MD5 6d6d3f01693e53f683dc50a344d9bf4e
BLAKE2b-256 1092e014f0b84b342750188e38ad1f6981ea17c3ddc9af4487b129201609a377

See more details on using hashes here.

Provenance

The following attestation bundles were made for authmail_relay-0.5.0-py3-none-any.whl:

Publisher: release.yml on hwan96-ai/authmail-relay

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page