rolltop is a single-container Go app that mirrors multiple IMAP inboxes per local user into local storage for search, viewing, composing, and mailbox moves. Production mail data stays in the user's own Docker instance. Project site: https://rolltop.app, coming soon. Contact: graham@rolltop.app.
- SQLite metadata at
/data/rolltop.db - Bleve search index at
/data/bleve - Raw
.emland attachment blobs under/data/blobs/users/{user_id}/... - Incremental sync progress in
sync_runs - A compiled React + Vite + TypeScript frontend served by the Go process
- Browser routes derive the current user from a server-side session.
- Normal user routes never accept
user_idfrom browser input. - Sessions use opaque random tokens; only SHA-256 token hashes are stored in SQLite.
- Cookies are
HttpOnlyandSameSite=Lax. - POST routes require CSRF tokens.
- App passwords are hashed with Argon2id.
- IMAP passwords are encrypted at rest with
ROLLTOP_MASTER_KEY. - Admins can create users, but V1 does not give admins access to other users' mail.
- Message sending uses the configured SMTP server.
- Mailbox moves are explicit user actions and are mirrored to IMAP.
- Read-state sync may update only the IMAP
\Seenflag when a message is read locally.
Required:
test -f .env.rolltop || (
umask 077
printf 'ROLLTOP_MASTER_KEY=%s\n' "$(openssl rand -base64 32)" > .env.rolltop
)
set -a
. ./.env.rolltop
set +aCommon optional variables:
export ROLLTOP_ADDR=":8080"
export ROLLTOP_DATA_DIR="/data"
export ROLLTOP_DB_PATH="/data/rolltop.db"
export ROLLTOP_INDEX_PATH="/data/bleve"
export ROLLTOP_SESSION_TTL="720h"
export ROLLTOP_SYNC_INTERVAL="15m"
export ROLLTOP_INBOX_POLL_INTERVAL="1m"
export ROLLTOP_BLOB_RETENTION="336h"
export ROLLTOP_COOKIE_SECURE="false"
export ROLLTOP_WEBHOOK_TOKEN=""Set ROLLTOP_COOKIE_SECURE=true when serving over HTTPS.
npm install
npm run build
npm run build:plugins
go test ./...
test -f .env.rolltop || (
umask 077
printf 'ROLLTOP_MASTER_KEY=%s\n' "$(openssl rand -base64 32)" > .env.rolltop
)
set -a
. ./.env.rolltop
set +a
ROLLTOP_DATA_DIR="./data" go run ./cmd/rolltopOpen http://localhost:8080. If no users exist, /setup creates the first admin.
docker pull ghcr.io/grahamsz/rolltop:latest
test -f .env.rolltop || (
umask 077
printf 'ROLLTOP_MASTER_KEY=%s\nROLLTOP_COOKIE_SECURE=false\n' "$(openssl rand -base64 32)" > .env.rolltop
)
docker run --rm -p 8080:8080 \
--env-file .env.rolltop \
-v rolltop-data:/data \
ghcr.io/grahamsz/rolltop:latestKeep .env.rolltop with the same care as the Docker volume. Changing or losing ROLLTOP_MASTER_KEY makes stored IMAP passwords undecryptable.
- First admin creates the initial account at
/setup. - Admin creates additional local users at
/admin/users. - Each user logs in and configures their own IMAP account at
/settings/account. - The user clicks
Sync now, chooses per-folderauto,manual, ornever, or scheduled sync runs onROLLTOP_SYNC_INTERVAL. - Sync runs are planned per mailbox, with INBOX prioritized before background folders. Each mailbox task estimates pending work from IMAP
STATUS, streams messages in UID batches, and updates current folder, UID, seen, total, stored, and skipped counts. - Message bodies, attachment names, and searchable text-like attachments are indexed with the current user's
user_id. - SQLite stores compact body previews; full body search lives in Bleve and message display uses the local raw
.emlor fetches the message from IMAP by UID when the raw blob has aged out. - Raw
.emlblobs are retained forROLLTOP_BLOB_RETENTIONonly, defaulting to 14 days. Set it to0to keep all raw blobs. - Attachment bytes are read from the raw
.emlwhile indexing and are not stored as separate blobs for new syncs. /mail, folder views,/search, and/messages/{id}only return current-user records.- Folder counts show unread messages.
- Dragging a message onto a folder immediately removes it from the current view, shows a moving toast, and then applies the IMAP move.
In account settings, Folder scope can be:
INBOXfor only inbox.INBOX,Sentfor a comma-separated subset.*for all selectable IMAP folders.
Search supports Gmail-style operators:
has:attachmentfilename:pdforfilename:"report.csv"is:readis:unread
The web app is installable as a limited offline PWA. It caches the shell and recent GET API responses, so previously opened mail/search views can render when offline. Browser notifications can be enabled from the top bar; these are local PWA notifications driven by the app's authenticated server-sent event stream, not VAPID/web-push delivery from a remote push service. Notifications are only counted for recent INBOX arrivals after the mailbox has already completed an initial sync, so archive/backfill syncs do not create browser popups.
rolltop uses IMAP IDLE for INBOX wakeups when the server supports it and keeps the scheduled INBOX poll as a fallback. Remote deletes and moves are reconciled after folder syncs by comparing local UIDs with the server's current UID set.
rolltop is intended to be distributed under the AGPL-3.0-or-later. By contributing code, documentation, assets, or other original work to this repository, you agree to license that contribution under AGPL-3.0-or-later unless you have a separate written agreement with the project owner.
npm run build
npm run build:plugins
go test ./...
docker build -t rolltop:dev .