Skip to content

Repository files navigation

Amalthea

Amalthea is a Rust-based tenant-scoped email routing platform with optional Python rule evaluation, a web admin dashboard, DKIM signing support, and multi-backend storage.

Key Features

  • Tenant-aware email routing for addresses like local_part@tenant.domain.tld
  • Built-in support for:
    • SQLite (default)
    • PostgreSQL
    • MongoDB (mongodb feature)
  • Optional Python routing rules via scripts/routing_rules.py
  • Admin dashboard with tenant and routing rule management
  • HTTP health and Prometheus metrics endpoints
  • Inbound SMTP listener via mailin-embedded
  • Outbound SMTP relay support via lettre
  • DKIM signing for outbound mail

How It Works

Address format

Valid email addresses are parsed as:

  • role@domain.tld
  • role@tenant.domain.tld

Where:

  • role is the local part before @
  • tenant is an optional subdomain used as a tenant identifier
  • domain.tld is the registered domain
  • role may include +metadata, e.g. support+ticket123@tenant.example.com

The parser accepts exactly 2 or 3 host labels and rejects deeper subdomains.

Routing behavior

Incoming email is routed by:

  1. Parsing the address
  2. Validating the registered domain
  3. Looking up the tenant by subdomain
  4. Loading tenant routing rules from the configured database
  5. Matching the local_part against rule patterns
    • exact match
    • wildcard *
    • glob patterns like admin*
  6. Applying Python rule overrides if enabled
  7. Returning one of:
    • Discard
    • Webhook
    • Forward
    • Store
    • Reject

Project Layout

  • src/main.rs — binary entrypoint
  • src/lib.rs — library root and run() entrypoint
  • src/compile_config.rs — defaults and env var names
  • src/parsers/email_address.rs — address parsing model
  • src/routing/mod.rs — routing engine
  • src/db/ — database pool and repository
  • src/dashboard/ — web dashboard, templates, auth
  • src/smtp/ — inbound SMTP listener
  • src/mailer/ — outbound SMTP relay + DKIM signing
  • src/dkim/ — DKIM configuration and signing
  • src/python/ — Python rule integration
  • scripts/routing_rules.py — optional Python routing rule file
  • templates/ — Tera HTML templates
  • static/ — frontend assets
  • sql/ — backend-specific SQL queries
  • migrations/ — schema DDL

Build and Run

Local development

cp .env.example .env
cargo run

This starts:

  • HTTP dashboard on 127.0.0.1:8080
  • SMTP listener on 0.0.0.0:22000

Build release

cargo build --release

Optional features

  • SQLite only (default)
  • PostgreSQL:
    cargo build --release --features postgres
  • MongoDB:
    cargo build --release --features mongodb
  • Python routing rules:
    cargo build --release --features "python-rules"

To enable both SQLite and Python rules:

cargo build --release --features "sqlite python-rules"

Docker

docker compose up -d --build

The compose file exposes:

  • 8080:8080 — HTTP dashboard
  • 22000:22000 — SMTP listener

Optional database services are available under profiles:

  • postgres
  • mongodb

Dashboard & API

  • GET /dashboard
  • GET /dashboard/tenants
  • GET /dashboard/rules
  • GET /dashboard/health
  • GET /dashboard/metrics
  • GET /login

API endpoints:

  • GET /api/tenants
  • POST /api/tenants
  • DELETE /api/tenants/{id}
  • GET /api/rules
  • POST /api/rules
  • DELETE /api/rules/{id}
  • POST /api/route-test
  • POST /api/mail/send
  • POST /api/auth/login
  • POST /api/auth/logout

Configuration

Important environment variables:

  • DATABASE_TYPEsqlite (default), postgres, or mongodb
  • SQLITE_DB_PATH — default ./amalthea.db
  • DATABASE_URL — PostgreSQL connection string
  • MONGODB_URL — MongoDB connection string
  • SERVER_BIND_HOST — default 127.0.0.1
  • SERVER_BIND_PORT — default 8080
  • SMTP_BIND_HOST — default 0.0.0.0
  • SMTP_SERVER_PORT — default 22000
  • ADMIN_USERNAME — default admin
  • ADMIN_PASSWORD — default changeme
  • JWT_SECRET — required for dashboard login
  • DKIM_DOMAIN — domain used for routing and signing
  • DKIM_SELECTOR — DKIM selector
  • DKIM_PRIVATE_KEY_PATH — path to private key
  • SMTP_RELAY_HOST — outbound relay host
  • SMTP_RELAY_PORT — relay port
  • SMTP_RELAY_USERNAME — relay username
  • SMTP_RELAY_PASSWORD — relay password
  • SMTP_RELAY_FROM — default outbound From address
  • INTERNAL_API_KEY — required for /api/mail/send

Note: if JWT_SECRET is not set, admin login is disabled by design.


Python Routing Rules

Enable Python rule support with the python-rules Cargo feature.

The Python script should define:

  • route_email(email_address)

It must return a dict with one of:

  • {"action": "default"}
  • {"action": "override", "target_type": "DISCARD"}
  • {"action": "override", "target_type": "STORE"}
  • {"action": "override", "target_type": "WEBHOOK", "target_value": "https://..."}
  • {"action": "override", "target_type": "FORWARD", "target_value": "user@example.com"}
  • {"action": "reject", "reason": "..."}

The default sample script is scripts/routing_rules.py.


Email Routing Rules

Supported TargetType values in tenant routing rules:

  • DISCARD
  • WEBHOOK
  • FORWARD
  • STORE

Rule matching supports:

  • exact local-part match
  • wildcard *
  • glob patterns via glob::Pattern

DKIM and Outbound Mail

  • src/dkim/mod.rs signs outbound mail using openssl
  • src/mailer/mod.rs sends mail through the configured SMTP relay
  • If DKIM is not configured, mail is sent unsigned with a warning
  • Outbound mail requires SMTP_RELAY_HOST

Inbound SMTP

Inbound SMTP is handled by src/smtp/mod.rs with mailin-embedded.

  • Accepts mail for the configured registered domain
  • Uses the same routing engine as the dashboard test endpoint
  • Supports STORE, FORWARD, and DISCARD
  • WEBHOOK delivery is currently logged and stored instead of dispatched
  • Plaintext only — no STARTTLS/TLS. If you're internet-facing on this listener, put a real MTA (Postfix/Exim/etc.) in front of it to terminate TLS; see DEPLOYMENT.md for a worked example.

Deployment

SMTP_SERVER_PORT defaults to 22000, not 25 — real inbound MX delivery needs something routing 25 → this port (a local MTA, DNAT, or cap_net_bind_service), plus DNS-only (unproxied) mail records at whatever provider you use. See DEPLOYMENT.md for a full Hetzner + Postfix walkthrough, the TLS caveat above in more detail, and alternative shapes (different VPS/DNS providers, no separate MTA, outbound-only via relay, managed Postgres/Mongo, driving the API from non-Rust services).


Security Notes

  • Change default ADMIN_USERNAME / ADMIN_PASSWORD
  • Always set JWT_SECRET for admin access
  • Do not expose the admin dashboard or SMTP listener without proper network controls
  • Secure DKIM_PRIVATE_KEY_PATH

Testing

cargo test
cargo test -- --nocapture
cargo clippy
cargo fmt

License

MIT License. See LICENSE.

About

Tenant-scoped email routing service with SMTP ingestion, DKIM signing, tenant-aware routing, optional Python rule support, and a web dashboard.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages