What the admin UI is for

localmail keeps the database as the single source of truth for account configuration. The admin UI is a thin, server-rendered front-end over that database, mounted at /admin inside the same localmail serve process that powers the desktop app and the API. Everything it does is also doable from the CLI; the UI just makes it pointy-clicky and remote-friendly.

Who can reach it

Only users flagged as admin can sign in to /admin. A regular API user (desktop-app login) has no admin access. The machine API under /v1/* never reads the admin session cookie — the two surfaces are isolated.

Enable it

  1. Add signing keys to config.toml.

    The admin UI signs its session cookie and the Gmail-OAuth state token. Generate one key per line and drop them under [serve]:

    python -c "import secrets; print(secrets.token_urlsafe(32))"
    [serve]
    session_signing_key = "<paste a generated key>"
    state_signing_key   = "<paste another generated key>"

    Without these keys the admin login is disabled. They are secrets — keep them out of version control.

  2. Create the first admin user.

    This is the one bootstrap step that must happen on the server's shell — there is no chicken-and-egg way to make the first admin from the web:

    localmail add-api-user admin --admin

    You'll be prompted for a password (argon2id-hashed). Add --admin to grant admin rights immediately. To promote an existing user instead, use localmail grant-admin USERNAME.

  3. Start the server and open /admin.

    localmail serve --bind 127.0.0.1 --port 8443 \
      --tls-cert ~/.config/localmail/tls.crt \
      --tls-key  ~/.config/localmail/tls.key

    Browse to https://localhost:8443/admin/ and sign in. For localhost-only use you can run with --no-tls --bind 127.0.0.1 and use http:// instead.

Sign in

The login screen takes the username and password you set with add-api-user. Logins are rate-limited (per-user, per-IP, and globally) and every attempt is audited in Postgres, so the limits hold even across multiple serve workers and restarts.

Admin sign-in screen
The /admin/login screen.

After signing in you land on the dashboard, which links to the four management areas. The top nav bar is present on every admin page.

Admin dashboard with links to Accounts, Daemon, Imports, and Users
The dashboard after signing in.

Accounts

The Accounts panel lists every account in the database with its email, authentication method, and sync state. From here you can add a new account, edit or delete one, and pause / resume syncing.

Accounts list showing three accounts with auth methods and sync state
The accounts list. Disable pauses syncing without deleting anything; Delete refuses unless the account holds no messages (or you force it).

The editor exposes every field of an account. Three authentication methods are supported, and the form shows only the fields each one needs:

Auth methodUse it for
passwordAny IMAP server reachable with a username + password (or app password): Fastmail, Microsoft 365, iCloud, generic IMAP.
oauth2Gmail. Click Connect Gmail to run the browser consent flow and store a refresh token — no password is ever held.
archiveA holding account for imported mail (mbox / Maildir). It has no IMAP host and is never synced — see Importing mail.
Account editor for a Gmail OAuth2 account with folder filters and deny flags
Editing a Gmail (OAuth2) account. Test connection verifies credentials live; the folder filters decide which mailboxes are mirrored.

Folder filters

localmail never has to mirror every folder. The editor offers three controls, evaluated together:

Account changes need a daemon restart

The running daemon reads its account set on a periodic reload, but brand-new accounts and credential changes take full effect after the daemon re-reads — use the Daemon panel's Reload now, or restart it.

Daemon

The Daemon panel shows the sync daemon's process state, each worker thread's most recent heartbeat (red when stale), and a tail of its log. What you can do here depends on how the daemon is supervised:

ModeBehaviour
Supervised
(supervise_daemon = true, the default)
serve owns localmail run as a child process. Start / Stop / Restart are live buttons.
External
(supervise_daemon = false)
An init system (systemd / launchd) owns the daemon. Lifecycle buttons are disabled; you still get Reload now, per-account restart-sync, and read-only status.
Daemon control panel showing external supervision and a Reload now button
The daemon panel in external supervision mode — lifecycle is owned by the init system, but reload and per-account restart still work. The panel polls itself every couple of seconds.

Imports

The Imports panel bulk-loads existing mbox / Maildir archives into an archive account. It is covered on its own page — see Importing mail.

Users

The Users panel manages API users: the people and agents that can sign in to the desktop app, the API, or this admin UI.

Users list showing an admin user and a regular user
The users list. The Admin column marks who can reach /admin.

The per-user editor is where access is actually granted. A new user can see nothing until you tick the accounts they're allowed to read — the checklist is the per-user access-control list.

User editor showing account-access checklist, password reset, admin toggle and delete
Editing a user. Tick accounts to grant read access; toggle admin rights; reset the password; revoke active sessions; or disable / delete the user.
Lock-out guards

The UI won't let you delete or demote the last remaining admin, and you can't delete or demote yourself. Those guards are enforced server-side, so a hand-crafted request hits them too.

Security notes