Files
imap-copier/README.md
T

98 lines
3.5 KiB
Markdown

# 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)
```bash
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:
```bash
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
```bash
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
bash scripts/e2e.sh
# or: make e2e
```
## Known limitations
Deduplication key is `UNIQUE(account_id, message_key)` **without folder**. If
the same message appears in multiple source folders (e.g. Gmail `INBOX` +
`[Gmail]/All Mail` + labels-as-folders), it is copied only into whichever
destination folder is processed first; folder placement for such duplicated
messages is not guaranteed. This is intentional per the design spec.
## 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`).