Skip to main content

Lightr

A lightweight mail engine. Lightr owns SMTP, identity, policy, and the operator surface; Dovecot serves mailboxes over IMAP.

That split is deliberate. Implementing IMAP well is a multi-year job that Dovecot has already done — UIDs, flags, BODYSTRUCTURE, partial fetch, IDLE, and the client quirks that come with them. Lightr does the part that is actually its own: getting mail in and out, deciding who may do what, and giving an operator a CLI and an API that answer questions without a database client.

   :25 / :587  ──►  ┌──────────────┐  ── LMTP ─────────────►  ┌─────────┐
   REST :8080  ──►  │              │                          │         │ ──► Maildir
   CLI         ──►  │    Lightr    │  ◄─ passdb Lua / HTTP ───│ Dovecot │
                    │   (Python)   │                          │         │
                    │              │  ── IMAP client ────────►│         │ ◄── :143 / :993
                    └──────┬───────┘  ── doveadm ────────────►└─────────┘     mail clients
                           │
                    SQLite / Postgres

What Lightr owns

  • SMTP receive (:25), submission (:587), and outbound relay
  • DKIM signing, SPF, DMARC, spam scoring
  • Outbound queue with retries, bounce handling, suppression
  • Organizations, domains, accounts, aliases, API keys
  • Authentication, local or offloaded to LDAP, OIDC, or an HTTP endpoint
  • Webhooks, the REST API, and the CLI
  • Dovecot itself — configuration, master user, Sieve installation, quota reads, and reloads. Lightr drives it via doveadm.

What Dovecot owns

IMAP, message storage, flags, UIDs, folders, Sieve execution, quota enforcement, and encryption at rest.

Install

Linux only. Requires Python 3.11+, and Dovecot for mailboxes.

pipx install lightr

Or from a Debian package, which pulls Dovecot in as a dependency:

sudo apt install ./lightr_0.3.0_all.deb

Quick start

lightr init --hostname mail.example.com

lightr domain create example.com
lightr domain dkim example.com --generate
lightr domain dns example.com           # records to publish
lightr domain verify example.com        # check what you published

lightr account create ops@example.com   # prompts for a password
lightr serve

There is no step for Dovecot. lightr init generates its auth key and master user, writes its configuration, verifies that configuration with Dovecot's own parser, and reloads the service. lightr serve does the same check on every start, so an install that drifts — a hand-edited file, a package upgrade that replaced one — comes back into line on its own. Creating an account provisions its mailbox.

You never edit dovecot.conf, hash a master password, or restart Dovecot. If Dovecot is missing or broken, Lightr says so and keeps running: the API, the queue, and SMTP receive still work while mailboxes do not.

The CLI

Every object is addressable by its human name — an email address, a domain, an organization name. No command requires a UUID.

lightr account list --domain example.com
lightr account get ops@example.com
lightr account passwd ops@example.com          # prompts; never a flag

lightr mailbox folders ops@example.com
lightr mailbox list ops@example.com --unread
lightr mailbox read ops@example.com 4821
lightr mailbox search ops@example.com --from billing@ --since 2026-08-01
lightr mailbox download ops@example.com 4821 --attachment 2 -o invoice.pdf

lightr auth add ldap corp --domain example.com   --set uri=ldaps://dc.corp --set base_dn=ou=people,dc=corp
lightr auth enable ops@example.com             # stop using the local password
lightr auth test ops@example.com               # which provider answered, and why

lightr webhook create billing https://api.example.com/hooks/lightr
lightr webhook test billing                    # does the receiver accept it?
lightr webhook deliveries billing              # did they get it?

lightr backup create /var/backups/lightr/      # everything Lightr owns
lightr mailbox import ops@example.com old.mbox # mbox, Maildir, or .eml
lightr mailbox export ops@example.com -o ops.mbox

lightr status
lightr dovecot status                          # what Lightr sees of Dovecot
lightr dovecot quota                           # real usage, as Dovecot measures it
lightr queue stats
lightr suppression check someone@example.com   # why did mail stop?

--format table|json|yaml works on every list and get. Output defaults to a table on a terminal and JSON when piped, so lightr account list | jq works without a flag.

Passwords are never accepted as command-line arguments — that would put them in shell history and in ps output for every user on the machine. Use the prompt, --stdin, or --generate.

The API

curl -H "X-API-Key: $KEY" https://mail.example.com/v1/domains

Keys are scoped to an organization, a domain, or a single account, and a scoped key cannot read another tenant's data. Authorization: Bearer works too.

Webhooks are managed over the same API — /v1/webhooks, with test, rotate, and deliveries subroutes. A signing secret is returned once on creation and masked everywhere else: it is stored in the clear because HMAC needs it, which is exactly why handing it back over the wire would let the holder forge every event the server sends.

Configuration

/etc/lightr/config.yaml. lightr init writes a working one.

An existing config from the Go engine loads unchanged — the retired imap block is ignored, replaced by a dovecot block describing how to reach Dovecot.

Storage

Maildir, with single-instance attachment storage off. One file per message, never modified after write, every operation atomic — so corruption is confined to a single message, and attachments live inside the message file rather than in a shared store whose loss would break many mails at once.

Upgrading from the Go engine

The Go implementation is preserved on the archive/go-engine branch, tagged v0.1.0-go-final.

lightr migrate    # adopts an existing database in place

The schema is shared, so lightr migrate adds only what is missing.

Migration 0003 drops the old messages, encryption_keys, and encrypted_messages tables — Dovecot owns the message store now, and mail encrypted by the Go engine is not carried over. Migration 0002 still preserves them, so stop there and take a backup first if any of it matters:

lightr migrate --revision 0002

Note that IMAP clients will resync from scratch on first connect. The Go engine's IMAP server used positional UIDs that shifted on every delete, so every existing client cache is already stale.

Deploying

Publishing to PyPI and installing on a server are both covered in docs/DEPLOY.md, including the two things that must be sorted before anything else works: a PTR record for your IP, and an unblocked port 25.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,sqlite,postgres]"
pytest
ruff check src tests

Scope

Lightr is a mail engine, not a mail product. It does not own templates, open/click tracking, unsubscribe flows, campaign analytics, mailing lists, autoresponders, or groupware. Those belong in applications built on top of it. See docs/CORE.md.

License

MIT

Download files

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

Source Distribution

lightr-0.3.0.tar.gz (236.7 kB view details)

Uploaded Source

Built Distribution

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

lightr-0.3.0-py3-none-any.whl (188.6 kB view details)

Uploaded Python 3

File details

Details for the file lightr-0.3.0.tar.gz.

File metadata

  • Download URL: lightr-0.3.0.tar.gz
  • Upload date:
  • Size: 236.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for lightr-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b4276056ef2d1936f5ea5a4ddee97113b65a8787f667d2f1a8a3ede088b781df
MD5 c88d23edef1d702bff30e01cc7aefdb2
BLAKE2b-256 dd85741e183665c0957ca355f2ecfa1b77e084c3ff0113e338b43345a1358951

See more details on using hashes here.

File details

Details for the file lightr-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: lightr-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 188.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for lightr-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3dcc5e8c60d22eaed5f3be0e3d9025852a43c58611958f0aa2f90a93a938dc27
MD5 50d421e75d80cb11a026e58e23b074b6
BLAKE2b-256 6ebf4f998cdf6da6c3f270a59750e2c4c3ddcf2062f77fd4989ebff701e027d0

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page