Install

Docker

Run postkeep from the container image postkeep/postkeep.

sh
docker run --rm postkeep/postkeep generate-key   # keep this key safe

docker run -d --name postkeep -p 127.0.0.1:8080:8080 \
  -v postkeep:/data \
  -e POSTKEEP_PUBLIC_URL=https://mail.example.com \
  -e POSTKEEP_MASTER_KEY=<the key> \
  -e POSTKEEP_ALLOWED_EMAILS[email protected] \
  postkeep/postkeep

Put an HTTPS reverse proxy in front, or use the Docker Compose setup with automatic HTTPS. The image is built for linux/amd64 and linux/arm64. To build it yourself from the repository: docker build -t postkeep/postkeep ..

What every deployment needs

  1. A public HTTPS URL. MCP clients and your browser must reach the server, and OAuth requires HTTPS outside localhost. Terminate TLS in a reverse proxy (Caddy, nginx, Traefik) or use a host that provides it. Set POSTKEEP_PUBLIC_URL to the origin only, such as https://mail.example.com, with no path.

  2. A master key. It encrypts stored app passwords, Microsoft and Google tokens, webhook secrets and stored webhook events (AES-256-GCM). Generate one and keep a backup:

    sh
    docker run --rm postkeep/postkeep generate-key
    # or: postkeep generate-key, or: openssl rand -hex 32

    Pass it as POSTKEEP_MASTER_KEY, or put it in a file and set POSTKEEP_MASTER_KEY_FILE (works well with Docker, Kubernetes and systemd secrets). If the key is lost or changed, none of these can be decrypted: every user must connect again, and webhook secrets and queued events are lost. To change the key without that, rotate it (see configuration).

  3. Persistent storage. By default the server uses a SQLite file (/data/postkeep.db in the image), so mount a volume at /data. Upgrading from 0.2.x with the default SQLite path: 0.3.0 looks for postkeep.db, so rename your existing database file to postkeep.db (in the image, /data/postkeep.db) or point POSTKEEP_DATABASE_URL at it before starting. Account-page users sign in again once. If 0.3.0 has already started and created an empty postkeep.db, stop it and replace that file with your old one. For several replicas or hosts without disks, use PostgreSQL: POSTKEEP_DATABASE_URL=postgres://user:password@host/db. When the database is remote, add ?sslmode=verify-full. That checks the server certificate and host name against the webpki roots compiled into the binary. sslmode=require encrypts without that check. If the provider uses a private CA, also add sslrootcert=/path/ca.pem (a PEM file the process can read). The schema is created and migrated automatically at startup. Each instance opens at most 10 database connections (POSTKEEP_DATABASE_MAX_CONNECTIONS). Behind a proxy or pooler that closes connections, recycle them sooner. For example, Fly Managed Postgres recommends a 300-second idle timeout and a 600-second lifetime (Fly), so set POSTKEEP_DATABASE_IDLE_TIMEOUT=300 and POSTKEEP_DATABASE_MAX_LIFETIME=600. These are examples, not defaults. Migrations run at start. Behind a transaction pooler, run them on a direct connection instead: set POSTKEEP_DATABASE_MIGRATE_URL to it, run postkeep migrate as a release command (it exits 0 when there is nothing to do), and set POSTKEEP_DATABASE_MIGRATE_ON_START=false so the server refuses to start on a schema it has not seen.

The rest of the deployment guide, including Compose, the plain binary and the security checklist, is the deployment overview.