A self-hosted webmail aggregator: one modern inbox view for all your existing mail accounts (generic IMAP/SMTP providers plus Outlook.com via OAuth2), running entirely in a single Docker container. inboxone does not upload your mail to a hosted third-party service. It stores account configuration, encrypted credentials and tokens, sync state, and message metadata in its local SQLite database; full bodies and attachments are fetched on demand and are not persisted.
- Aggregate multiple IMAP/SMTP accounts and Outlook.com into one chronological inbox.
- Color-coded account indicator on every message so you always know which mailbox it came from.
- Search cached subject, sender, and recipient metadata across the unified inbox, with an unread-only filter.
- Reply to or forward messages from any configured account, from a single aggregated view.
- Per-account folder selection — sync just
INBOX, or additional folders/labels too. - A single app-wide password gate protects the whole UI.
- Modern three-pane UI (filter rail / message list / reading pane), not an old-school webmail look.
- Backend: Python (FastAPI), SQLAlchemy async + SQLite, APScheduler for background
IMAP polling,
IMAPClientfor IMAP,aiosmtplibfor basic-auth SMTP sending,msalfor Microsoft OAuth2 device-code sign-in, Microsoft Graph for sending mail from Outlook accounts. - Frontend: React + TypeScript + Vite + TailwindCSS + TanStack Query + Zustand.
- Data model: account configuration, encrypted credentials and tokens, sync state, and message metadata (subject, sender, recipients, date, flags, folder, size, etc.) are stored in a local SQLite database. Bodies and attachments are never persisted — they're fetched live from the mail server when requested.
- Runs as a single Docker container: the API and the background sync scheduler share one Python process (no separate worker process needed).
- Docker and Docker Compose.
Security note: inboxone is a single-user application intended for private or local-network use. Do not expose it directly to the public internet. For remote access, use a trusted VPN or a reverse proxy with independent authentication.
- Copy the example environment file and fill in real values:
cp .env.example .env
- Generate the required secrets:
(If you don't have Python locally, run these one-liners inside the built container instead:
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # SECRET_KEY python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # ENCRYPTION_KEY
docker compose run --rm inboxone python3 -c "...".) - Set
APP_PASSWORDin.envto a password of your choosing (this only seeds the initial password - see "Forgot your password?" below for how to reset it later). - Pull and start (uses the published image from ghcr.io):
docker compose up -d
- Open
http://localhost:8000, log in withAPP_PASSWORD, and add your accounts under Settings.
Use your provider's IMAP/SMTP host, port, and your normal username/password (or an app-specific password, see below). Settings → Add account → "Generic IMAP/SMTP".
Gmail requires an App Password rather than your normal account password once
2-Step Verification is enabled (Google no longer allows plain account passwords for
IMAP/SMTP). Create one at
myaccount.google.com/apppasswords and use
it as the IMAP/SMTP password. Host: imap.gmail.com (port 993), smtp.gmail.com
(port 587).
Outlook.com accounts use OAuth2 via a device-code sign-in flow. Go to Settings → Add
account, choose Outlook, enter a name, and select Connect. Enter the displayed code at
microsoft.com/devicelogin, then sign in with
your Microsoft account. inboxone uses built-in OAuth client configuration, so you do
not need to register an Azure application.
| Variable | Description |
|---|---|
APP_PASSWORD |
Password required to access the web UI. |
SECRET_KEY |
Random secret used to sign the session cookie. |
ENCRYPTION_KEY |
Fernet key used to encrypt stored IMAP/SMTP passwords and OAuth tokens at rest. Do not rotate after accounts exist without a migration - existing encrypted values would become unreadable. |
DATABASE_URL |
SQLite connection string (defaults to the /data bind mount). |
SYNC_DEFAULT_INTERVAL_SECONDS |
Default IMAP polling interval per account (minimum 30s). |
APP_PASSWORD in .env only sets the initial password the first time the container
starts - after that, the password lives in the database and editing .env no longer
has any effect. If you forget it, reset it directly from inside the running container:
docker exec -it inboxone python -m app.cli reset-passwordThis prompts for a new password and updates it immediately, without needing to know the
old one - no need to touch .env or restart anything.
- inboxone does not upload your mail to a hosted third-party service. Its database and application data remain on the server where you run the container.
- The local SQLite database stores the app password hash, account configuration, encrypted credentials and OAuth tokens, sync state, and message metadata (subject, sender/recipients, date, flags, folder, and size).
- Message bodies and attachments are fetched from the provider when you open or download them and are not persisted to disk by inboxone.
- IMAP/SMTP passwords and Microsoft OAuth tokens are encrypted at rest with
ENCRYPTION_KEYand decrypted in memory when needed for a connection. - Deleting a message moves it to the account's Trash or Deleted Items folder on the provider; inboxone does not permanently erase it.
The published image (ghcr.io/dermute/inboxone:latest) is what docker-compose.yml
uses by default. To build and run from your own checkout instead, uncomment the
build: block in docker-compose.yml (and comment out image:), then:
docker compose up -d --build# backend (from the repository root)
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e "./backend[dev]"
cd backend
alembic upgrade head
uvicorn app.main:app --reload
# frontend (separate terminal)
cd frontend
npm install
npm run devThe Vite dev server proxies /api requests to http://localhost:8000.
- No IMAP IDLE or push updates yet — the backend polls each account on an interval (default 60s) and the frontend polls the API every 15s. New mail typically appears within a minute.
- No Gmail OAuth - Gmail accounts are configured as generic IMAP/SMTP using an App Password.
- Single user, single SQLite database - not designed for multiple concurrent users.
- Message list "snippet" preview is not populated yet, since generating it would require fetching bodies during sync (which this app intentionally avoids to keep mail off local storage).
This project was developed with AI assistance.
MIT — see LICENSE.