Files
imap-copier/README.md
T

3.1 KiB

imap-copier

Single-binary Go server (embeds the React SPA) that copies IMAP mailboxes between a source and a destination account. Non-destructive (copy only, never deletes), deduplicated by Message-ID, resumable/idempotent re-runs.

Quick start (plain HTTP on :80)

cp .env.example .env
# generate a real 32-byte key for ENC_KEY:
sed -i '' "s|ENC_KEY=|ENC_KEY=$(openssl rand -base64 32)|" .env
# edit .env: set POSTGRES_PASSWORD, AUTH_USER, AUTH_PASS, SESSION_SECRET

docker compose build
docker compose up -d
curl -fsS http://localhost/healthz

The app is served through Caddy on port 80 by default — no domain or TLS required. Login with AUTH_USER/AUTH_PASS from .env.

Enabling HTTPS (Let's Encrypt)

Set a real, publicly resolvable domain and an ACME contact email in .env:

DOMAIN=copier.example.com
ACME_EMAIL=you@example.com

Then start with the TLS override instead of the plain compose file:

docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d
# or: make up-tls

This swaps Caddy's config to Caddyfile.tls, which requests and renews a Let's Encrypt certificate automatically for DOMAIN (ports 80/443 must be reachable from the internet for the ACME HTTP-01 challenge). Switch back to plain HTTP with docker compose -f docker-compose.yml up -d (or make up).

Environment variables (.env)

Var Required Notes
POSTGRES_PASSWORD yes Postgres password, also used in DATABASE_URL
AUTH_USER / AUTH_PASS yes Single operator login (no user table)
ENC_KEY yes 32 bytes, base64: openssl rand -base64 32
SESSION_SECRET yes Signs the session cookie
WORKER_CONCURRENCY no (default 4) Parallel accounts copied per run
HTTP_PORT no (default 80) Host port Caddy binds for HTTP
DOMAIN / ACME_EMAIL only for HTTPS See above

Makefile targets

make build    # docker compose build
make up       # docker compose up -d (plain HTTP)
make up-tls   # docker compose up -d with the Let's Encrypt override
make down     # docker compose down
make logs     # docker compose logs -f
make test     # go test ./...
make e2e      # scripts/e2e.sh (full-stack E2E against greenmail)

E2E test

scripts/e2e.sh builds and starts the full stack (postgres, app, caddy) plus a throwaway greenmail IMAP server acting as both the source and destination mailbox host, then drives the real REST API: login, create endpoints/task/account, /test, /run, and a second /run to prove idempotency (nothing is re-copied). It tears everything down afterwards.

bash scripts/e2e.sh
# or: make e2e

Architecture

  • cmd/server — entrypoint: runs DB migrations, then serves HTTP.
  • internal/httpapi — REST API + WebSocket + embedded SPA (webdist/).
  • internal/orchestrator — test/run coordination, per-account concurrency.
  • internal/imapx — IMAP connect/list/copy primitives.
  • internal/store — Postgres access (endpoints, tasks, accounts, runs).
  • web/ — React/Vite SPA, built into internal/httpapi/webdist at image build time (see Dockerfile).