LanQin Email is a self-hosted full-stack webmail solution. The frontend is built with React + TypeScript + shadcn/ui, the backend uses Go + SQLite, and deployment can run as a single all-in-one container with API, Web, Nginx, Postfix, Dovecot, and Rspamd integrated.
Community: Telegram group
- Webmail client: multiple mailbox switching, folders, reading and composing messages, drafts, scheduled sending, attachments, search, labels, stars, move/delete, read/unread status.
- Mailbox enhancements: contacts, signatures, inbox rules, sender blacklist, mail statistics, archive read messages, empty Trash/Spam.
- Multi-domain / multi-mailbox: domain management, DKIM key generation, DNS record display and checks, mailbox accounts, alias forwarding, catch-all toggle.
- Accounts and permissions: login/registration, session management, TOTP two-factor authentication, Cloudflare Turnstile, user self-service mailbox requests, permission groups/RBAC.
- Admin panel: overview checklist, user/permission group/domain/mailbox/alias/all-message management, system settings, mail templates, SMTP testing.
- Mail service stack: Postfix delivery, Dovecot IMAP/POP3, Rspamd anti-spam and DKIM signing, Maildir-to-SQLite sync.
- Deployment friendly: default all-in-one single container, plus a multi-container stack for debugging Postfix/Dovecot/Rspamd.
.
├── apps/api # Go API, SQLite schema, mail sync, and business logic
├── apps/web # React/Vite Webmail and admin panel
├── deploy # Docker Compose, image build, Postfix/Dovecot/Rspamd config
└── .github/workflows # Docker image release workflows
- Go 1.25+
- Node.js 20+
- pnpm 10.28.2 (can be enabled through corepack)
- Docker Engine
- Docker Compose v2
- A resolvable mail domain, plus available ports such as 25 / 465 / 587 / 993 / 995
Public email sending and receiving also requires correct MX, SPF, DKIM, and DMARC records, and you should confirm that your cloud provider does not block SMTP ports.
Backend:
cd apps/api
go mod download
go test ./...
go run ./cmd/serverFrontend (new terminal):
cd apps/web
corepack enable
corepack prepare pnpm@10.28.2 --activate
pnpm install
pnpm run devOpen:
- Web:
http://localhost:5173 - API:
http://localhost:8080
The default admin email is admin@lanqin.local. For development, explicitly set LANQIN_ADMIN_PASSWORD; if it is not set, the backend generates a random password on first startup and prints it to the logs.
A server only needs the Compose files and configuration under deploy/; building from source is not required:
cd deploy
cp .env.example .env
# Edit .env: domain, public URL, admin email, admin password, etc.
docker compose pull
docker compose up -dCommon commands:
# View logs
docker compose logs -f lanqin-email
# Pull the latest image and restart
docker compose pull
docker compose up -d
# Stop services
docker compose downTo build the image locally from the full source repository:
cd deploy
cp .env.example .env
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --buildSee deploy/README.md for more deployment details.
- Edit
deploy/.env: at minimum, changeLANQIN_PUBLIC_HOSTNAME,LANQIN_PUBLIC_BASE_URL,LANQIN_ADMIN_EMAIL, andLANQIN_ADMIN_PASSWORD. - In production, mount real TLS certificates and set
LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE. - Log in to the admin panel and add your mail domain.
- Copy and configure MX, SPF, DKIM, and DMARC records from domain management, then run the DNS check.
- Create mailbox accounts, alias forwarding, or permission groups; enable registration, 2FA, Turnstile, and self-service mailbox requests as needed.
- Use the admin SMTP test and Webmail send/receive tests to confirm the full path works.
See deploy/.env.example for the full configuration. Common variables:
| Variable | Description | Default / Example |
|---|---|---|
LANQIN_IMAGE |
All-in-one image | ghcr.io/lanqin996/lanqin-email:latest |
LANQIN_PUBLIC_HOSTNAME |
Mail server hostname; affects Postfix/DNS display/links | mail.example.com |
LANQIN_PUBLIC_BASE_URL |
Public Webmail URL | https://mail.example.com |
LANQIN_ADMIN_EMAIL |
Initial admin email | admin@example.com |
LANQIN_ADMIN_PASSWORD |
Initial admin password; must be changed in production | ChangeMe123! |
LANQIN_DB_PATH |
SQLite database path | /data/lanqin.db |
LANQIN_ALLOW_INSECURE_HTTP |
Allow non-HTTPS cookies; useful for local debugging | false |
LANQIN_OPEN_REGISTRATION |
Enable public registration | false |
LANQIN_TWO_FACTOR_ENABLED |
Global 2FA feature toggle | false |
LANQIN_TURNSTILE_ENABLED |
Enable Turnstile | false |
LANQIN_SMTP_HOST / LANQIN_SMTP_PORT |
Webmail outbound SMTP | 127.0.0.1 / 25 |
LANQIN_MAILDIR_ROOT |
Maildir root directory | /var/mail/vhosts |
LANQIN_CATCH_ALL_ENABLED |
Whether unregistered recipient addresses go into all messages | false |
LANQIN_USER_MAILBOX_APPLY_ENABLED |
Allow users to request mailboxes by themselves | false |
LANQIN_EXTERNAL_IMAP_ENABLED |
Enable external IMAP access; also configurable in Admin > System Settings > External IMAP | false |
LANQIN_EXTERNAL_IMAP_SECRET_KEY |
Encryption key for external IMAP passwords; required before enabling access; also configurable in admin | Random long string |
LANQIN_EXTERNAL_IMAP_SYNC_SECONDS |
Sync interval for external IMAP local-storage mode; also configurable in admin | 300 |
LANQIN_EXTERNAL_IMAP_ALLOW_PRIVATE_HOSTS |
Allow external IMAP to connect to private/localhost hosts; also configurable in admin | false |
LANQIN_EXTERNAL_IMAP_GMAIL_CLIENT_ID / LANQIN_EXTERNAL_IMAP_GMAIL_CLIENT_SECRET |
Gmail external IMAP OAuth2; callback is /api/external-imap-oauth/gmail/callback |
Empty |
LANQIN_EXTERNAL_IMAP_OUTLOOK_CLIENT_ID / LANQIN_EXTERNAL_IMAP_OUTLOOK_CLIENT_SECRET |
Microsoft 365 / Outlook external IMAP OAuth2; callback is /api/external-imap-oauth/outlook/callback |
Empty |
┌────────────────────────────────────────────────────────────┐
│ lanqin-email single container │
│ │
│ ┌─────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ Nginx │ ───▶ │ Go API │ ───▶ │ SQLite /data │ │
│ │ Web │ │ Webmail API│ └──────┬───────┘ │
│ │ static │ └─────┬──────┘ │ │
│ └─────────┘ │ Maildir sync │ maps │
│ ┌─────────┐ ┌─────▼──────┐ ┌──────▼───────┐ │
│ │ Rspamd │ ◀───▶ │ Postfix │ ───▶ │ Dovecot/LMTP │ │
│ │ DKIM/AS │ │ SMTP/MTA │ │ IMAP/POP3 │ │
│ └─────────┘ └────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────┘
Mail flow:
- Receiving: Postfix receives mail → Rspamd scores/marks it → Dovecot writes to Maildir → API worker syncs it into SQLite → Webmail displays it.
- Sending: Webmail calls the API → API builds MIME → SMTP submits to Postfix or an external SMTP server → mail is delivered to the destination.
- Local delivery: In development, internal mailboxes can send directly into the recipient Inbox; if
LANQIN_SMTP_HOSTis not configured, external recipients are not actually delivered. - Third-party clients: Connect with SMTP 465/587, IMAP 993, or POP3 995; in production, configure certificates that match
LANQIN_PUBLIC_HOSTNAME. - External mailbox access: Users can add external IMAP accounts in personal mailbox management. Local-storage mode syncs mail into the database; remote-direct mode reads from the remote server each time and does not write into local mail tables.
External integrations should use the versioned /api/open/v1 endpoints with scoped API Tokens. See the API guide and the machine-readable OpenAPI 3.1 contract. Sending supports idempotency keys; final delivery events can be ingested through a signed endpoint and all status changes can be pushed through the reliable signed webhook outbox.
# API tests
cd apps/api
go test ./...
# Web checks and build
cd apps/web
pnpm run check
# Single-container source build verification
cd deploy
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build- In production, always change the default admin password and protect
.env, the SQLite database, Maildir, and DKIM private keys. - The Web UI can sit behind host Nginx / aaPanel / an edge gateway, but SMTP/IMAP/POP3 certificates must be mounted separately for Postfix/Dovecot inside the container.
- Cloud providers often block port 25 by default; if public email does not send or receive, first check ports, security groups, firewalls, and reverse DNS.
- SQLite is suitable for single-node deployments; before multi-node deployment, migrate the database and adjust Postfix/Dovecot query configuration accordingly.
- Third-party client SMTP submission on
465/587is handled by the LanQin API process. - Before enabling SMTP submission, configure
LANQIN_TLS_CERT_FILE/LANQIN_TLS_KEY_FILE; the API will not expose 465/587 externally with a localhost self-signed certificate. - Postfix only keeps port
25for public inbound mail and internal/external relay. - Webmail/API and third-party client sends are first written into Sent, then enter the send queue.
- The send queue is relayed by a LanQin API background worker to
LANQIN_SMTP_HOST:LANQIN_SMTP_PORT; failures are audited and retried with backoff. - v1 supports sending from the user's own mailbox. For send-as, use an enabled alias forwarding source that points to the user's mailbox, or configure
send_as_grantsin the database. - If the client later writes its own Sent copy through IMAP APPEND, Maildir sync deduplicates by
Message-IDwithin the Sent folder.
Friends: LINUX DO — a new ideal community



