# Docker Mailserver User Management Panel A small full-stack admin tool for managing Docker Mailserver accounts with Bun, ElysiaJS, and React. ## 1. Proposed stack ### Backend - Bun - ElysiaJS - Bun SQLite - Docker CLI through fixed argument arrays - Zod for small, explicit validation Why this backend: - Bun keeps the runtime, package manager, and SQLite support simple. - ElysiaJS is a good fit for a small internal API without extra framework weight. - SQLite is enough for admin sessions and password reset tokens, and keeps deployment straightforward. - Docker CLI integration stays close to Docker Mailserver's real operational model instead of hiding it behind unnecessary layers. ### Frontend - React - Vite - Plain CSS Why React: - It is easy for most full-stack teams to read and maintain. - This panel only needs basic local state and forms, so React works well without routers or global state libraries. - The UI stays minimal while still being easy to grow later. ## 2. Folder structure ```text apps/ api/ src/ auth/ docker/ lib/ mail/ users/ web/ src/ api/ components/ pages/ styles/ packages/ shared/ data/ app.db infra/ nginx/ ``` ## 3. Backend implementation The backend is intentionally flat: - `apps/api/src/index.ts` Defines the HTTP routes and keeps request flow obvious. - `apps/api/src/auth/service.ts` Handles admin login, bootstrap admin seeding, session cookies, and session lookup. - `apps/api/src/docker/` Wraps Docker CLI calls with fixed argument arrays only. No shell interpolation. - `apps/api/src/users/service.ts` Contains the mailbox user operations and password reset flow. - `apps/api/src/mail/service.ts` Sends reset emails through `sendmail` inside the Docker Mailserver container. - `apps/api/src/lib/` Holds config parsing, SQLite access, validation, HTTP helpers, and small security utilities. ### API design - `GET /api/auth/session` - `POST /api/auth/login` - `POST /api/auth/logout` - `GET /api/users` - `POST /api/users` - `PATCH /api/users/:email/password` - `DELETE /api/users/:email` - `POST /api/users/:email/password-reset` - `POST /api/password-resets/inspect` - `POST /api/password-resets/consume` ## 4. Frontend implementation The frontend is a small React app with three views: - Login page - Dashboard page - Public reset-password page The dashboard includes: - mailbox listing - simple search - create user modal - edit password modal - delete action - send reset action The create flow intentionally generates a temporary password on the server and immediately emails a one-time reset link to the user. That means the admin panel never needs to expose an initial mailbox password in the UI. ## 5. Password reset flow The reset flow is built to stay secure without becoming heavy: 1. An admin triggers a reset for an existing mailbox. 2. The backend generates a random opaque token. 3. Only the SHA-256 hash of that token is stored in SQLite. 4. The user receives an email with the one-time reset link. 5. The reset page validates the token before showing the password form. 6. On submit, the token is claimed for one use only. 7. The backend updates the mailbox password through Docker Mailserver. 8. Used or expired tokens are rejected. ## 6. Docker CLI integration strategy The app talks to Docker Mailserver through `docker exec` with explicit argument arrays: - `setup email list` - `setup email add ` - `setup email update ` - `setup email del ` - `sendmail -t -i` Security choices here: - No `sh -c` - No user input inserted into shell strings - Input is validated before command execution - Docker commands have a timeout - Errors are returned as API failures instead of leaking raw implementation detail to the browser ## 7. Security notes - Admin authentication uses an Argon2id-hashed bootstrap account from environment variables. - Sessions are server-side, opaque, and stored in an `HttpOnly` cookie with `SameSite=Strict`. - Password reset tokens are random, one-time, time-limited, and stored only as hashes. - Admin-only routes require a valid session cookie. - Validation is applied to logins, emails, passwords, and reset payloads. - Allowed origins are restricted by `APP_ORIGINS`. - Reset emails are sent from the backend only. No secrets are exposed to the frontend. - Docker Mailserver access is treated as privileged infrastructure access. ## 8. Run locally ### Prerequisites - Bun installed - Docker available locally - A running Docker Mailserver container - The API process must be allowed to access Docker ### Setup 1. Copy `.env.example` to `.env`. 2. Update at least: - `HOSTED_DOMAINS` - `MAILSERVER_CONTAINER` - `APP_ADMIN_EMAIL` - `APP_ADMIN_PASSWORD` - `APP_MAIL_FROM` 3. Install dependencies: ```bash bun install ``` 4. Start the API: ```bash bun run dev:api ``` 5. In another terminal, start the frontend: ```bash bun run dev:web ``` 6. Open `http://localhost:5173`. ### Production-style local run If you want the built frontend to be served by the API itself: ```bash bun run build bun run dev:api ``` Then open `http://localhost:3001`. The API serves files from `apps/web/dist` by default, configurable with `WEB_DIST_PATH`. ### Docker permission note If you see: ```text permission denied while trying to connect to the docker API at unix:///var/run/docker.sock ``` the app itself is fine, but the API process cannot talk to Docker. Common fixes: - Run the API with a user that belongs to the `docker` group - If the API runs in a container, mount `/var/run/docker.sock:/var/run/docker.sock` - Make sure the Docker daemon is actually running - Make sure `MAILSERVER_CONTAINER` matches the real Docker Mailserver container name ## Notes and assumptions - This panel assumes Docker Mailserver is using the file-based account provisioner and supports the standard `setup email` commands. - The admin account for the panel is separate from mailbox users. - The app stores only panel-specific metadata in SQLite. Mailbox accounts themselves remain managed by Docker Mailserver.