INSTALL GUIDE · DOCKER
Blue Folder Mail with Docker
The canonical self-host path. One container, one volume, one command. Works on any Linux box with the Docker Compose plugin — a VPS, a cloud VM, a Mac, a NAS or a Raspberry Pi.
Prerequisites
- Docker plus the Docker Compose plugin (docker compose version should work).
- A box reachable on a port (default 8080) — or a reverse proxy in front.
- A Resend account for sending, and (recommended) an inbound-email provider for receiving.
- Optional but recommended: a domain name pointed at the box for HTTPS.
Install — one command
The installer checks Docker, fetches the code, prompts for your secrets and starts the stack.
curl -fsSL https://raw.githubusercontent.com/gordonvividtone/bluefolder-mail/main/install.sh | bash
Or do it by hand from a clone
- Clone and configure
git clone https://github.com/gordonvividtone/bluefolder-mail.git cd bluefolder-mail cp .env.example .env
Edit .env and set at least INBOUND_SECRET (a long random value — openssl rand -hex 24) and RESEND_API_KEY.
- Start it
docker compose up -d
- Open and create your mailbox
Go to http://localhost:8080, generate your key and save your backup password — it can't be recovered — then add your domains in Settings.
Where your data lives
The SQLite metadata index and your sealed mail blobs persist in the bfm-data volume. Back that volume up and you've backed up everything.
Default — local disk
Nothing to configure. The container writes to the volume. Good for a single box.
Optional — S3-compatible object storage
To keep sealed blobs in a bucket (AWS S3, Backblaze B2, MinIO or Cloudflare R2), set in .env:
STORAGE_BACKEND=s3 S3_ENDPOINT=... S3_BUCKET=... S3_ACCESS_KEY_ID=... S3_SECRET_ACCESS_KEY=...
A commented MinIO service ships in docker-compose.yml if you want object storage on the same box. With S3, the only stateful piece left is the SQLite index — keep that on the volume.
Sending mail — Resend
Outbound goes through the Resend HTTP API, the same as on Cloudflare.
- Verify your sending domains at resend.com (add the SPF/DKIM records it gives you) so replies are DKIM-signed.
- Put the key in .env as RESEND_API_KEY=..., then docker compose up -d to apply.
Receiving mail — the honest part
Receiving internet email reliably is the genuinely hard part of self-hosting. There are two paths.
Recommended — webhook
Let an email provider do the hard MTA work (MX, TLS, spam, reputation) and have it POST each message to your instance, which seals and stores it through the same ingestion as everything else.
- Set INBOUND_SECRET to a long random value.
- Point the provider's inbound / parse route at https://your-host/_inbound, sending the secret as the x-inbound-secret header or ?secret=… on the URL.
- Prefer the option that forwards the full raw MIME so attachments survive.
Works with Mailgun Routes, SendGrid Inbound Parse, Postal and Resend inbound, among others. (Before your mailbox exists, /_inbound returns 503 so the provider retries; a wrong secret returns 401.)
Advanced — built-in SMTP
Set ENABLE_SMTP=1 and expose port 25 to run a real inbound MTA. Only do this if you can satisfy all of: a static public IP with matching PTR / reverse-DNS, correct MX records, SPF/DKIM/DMARC per domain, and a clean-reputation IP.
HTTPS & the front door
Off Cloudflare there's no Cloudflare Access, so ACCESS_ENFORCE=0 and the port is reachable by anyone who can hit it. Add auth and TLS before exposing it publicly — pick one:
- Built-in Basic Auth — set BASIC_AUTH_USER and BASIC_AUTH_PASS. The browser prompts natively and the app keeps working. Simplest real protection.
- Reverse proxy — Caddy, nginx, Traefik or a Cloudflare Tunnel in front of :8080, optionally with its own identity provider.
- Private network — Tailscale or a VPN so the port is never public.
A two-line Caddy site block gives you automatic Let's Encrypt TLS:
mail.example.com {
reverse_proxy localhost:8080
}