Self-Hosted Installation Guide
Deploy MailDesk on your own infrastructure. All data stays on your servers.
Prerequisites
| Requirement | Minimum |
|---|---|
| Docker Engine 24+ | |
| Docker Compose v2.20+ | |
| 4 GB RAM | |
| 20 GB disk (+ attachment storage) | |
| Any Linux with Docker support | |
| License key from maildesk.cloud | |
1. Extract and load
Extract the package:
tar xzf maildesk-selfhosted.tar.gz
cd maildeskLoad the Docker images:
docker load < maildesk-images.tar.gz2. Configure environment
./setup.shRun ./setup.sh to auto-generate all secrets. Then edit .env to set your APP_URL and license key.
Open .env and set the following values:
Required variables
| Variable | Description |
|---|---|
| APP_URL | Public URL of your instance (no trailing slash) |
| MAILDESK_LICENSE_KEY | Your Ed25519-signed JWT license key |
Auto-generated by setup.sh
These are generated automatically when you run setup.sh. Do not change unless migrating data.
| Variable | Description |
|---|---|
| BETTER_AUTH_SECRET | Session signing key, min 32 chars (auto-generated) |
| CREDENTIAL_ENCRYPTION_KEY | AES-256-GCM key for IMAP/SMTP passwords (auto-generated) |
| POSTGRES_PASSWORD | PostgreSQL database password (auto-generated) |
| REDIS_PASSWORD | Redis cache password (auto-generated) |
| MAILDESK_MODE | Set to selfhosted (auto-set by setup.sh) |
Optional variables
| Variable | Description |
|---|---|
| MAILDESK_LICENSE_SERVER_URL | License server URL for online verification (default: offline-only) |
| REGISTRATION_ENABLED | true (default) or false to disable self-signup |
| ALLOWED_REGISTER_IPS | Comma-separated IPs allowed to access /register |
| ENABLE_HSTS | true to send HSTS header (enable when behind TLS) |
| LOG_LEVEL | trace, debug, info (default), warn, error, fatal |
| METRICS_AUTH_TOKEN | Bearer token for /api/metrics endpoint (hidden if unset) |
| OPENAI_EMBEDDING_KEY | OpenAI API key for KB semantic search (optional) |
| MAX_ATTACHMENT_SIZE_MB | Max attachment size in MB (default: 25) |
Example .env for self-hosted
# Only these 2 lines need manual editing after running ./setup.sh:
APP_URL=https://tickets.example.com
MAILDESK_LICENSE_KEY=eyJhbGciOiJFZERTQSIs...your-key-here
# Everything else is auto-generated by ./setup.sh3. Start
docker compose up -dThis starts 6 services:
| Service | Purpose |
|---|---|
| migrate | Runs database migrations, then exits |
| app | Next.js web application (port 3000) |
| worker | Background worker (IMAP/SMTP, email processing, jobs) |
| postgres | PostgreSQL 17 with pgvector extension |
| redis | Redis 7 for job queues and pub/sub |
| clamav | ClamAV antivirus for attachment scanning |
The migrate service runs automatically before app and worker start. It creates all tables idempotently — safe to run on both fresh and existing databases.
4. Verify the deployment
# Check all services are healthy
docker compose ps
# Check app logs
docker compose logs app
# Check worker logs
docker compose logs worker
# Health check endpoint
curl http://localhost:3000/api/healthOnce healthy, open your APP_URL in a browser. You should see the login/registration page.
5. Initial setup
- 1Register the first admin account at your-domain.com/register
- 2Create an organization — this is your workspace
- 3Add a mailbox under Settings > Mailboxes (IMAP/SMTP credentials)
- 4Incoming emails will automatically create tickets
Email Verification (Optional)
Configure SMTP to enable email verification for new users. Without SMTP, new users are auto-verified and can log in immediately.
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM=MailDesk <noreply@yourdomain.com>Reverse Proxy Setup
MailDesk requires a reverse proxy for TLS termination. Here are examples for common setups:
tickets.example.com {
handle /ws* {
reverse_proxy localhost:3002
}
handle {
reverse_proxy localhost:3000
}
}License Key
Your license key is an Ed25519-signed JWT that contains your plan, limits, and feature flags.
What the key controls:
- Plan tier (Free, Pro, Business)
- Limits: max agents, mailboxes, tickets/month, storage
- Features: AI reply, knowledge base, auto-translation
Validation:
- The key is validated offline by default using the embedded Ed25519 public key — no external calls needed
- If MAILDESK_LICENSE_SERVER_URL is set, MailDesk will periodically verify the key online (optional)
- Validated license data is cached in Redis for 24 hours
- If Redis is down, the JWT is verified directly (Ed25519 needs no server)
Expired or invalid key:
- The app enters read-only mode — existing data remains accessible
- Creating tickets, sending replies, and AI features are disabled
- A banner shows "License expired" with a renewal link
Updating
# Load new images
docker load < maildesk-images.tar.gz
# Restart (migrations run automatically)
docker compose up -dMigrations run automatically on every start — no manual migration step needed.
Backup & Restore
Backup
Database
docker compose exec postgres pg_dump -U maildesk maildesk > backup_$(date +%Y%m%d).sqlAttachments
docker compose cp worker:/data/attachments ./attachments-backupRedis (optional — only caches, can be rebuilt)
docker compose exec redis redis-cli BGSAVERestore
Database
docker compose exec -T postgres psql -U maildesk maildesk < backup_20260317.sqlAttachments
docker compose cp ./attachments-backup/. worker:/data/attachmentsTroubleshooting
App won't start
docker compose logs app
docker compose logs migrateCommon causes:
- Database not ready — The migrate service waits for Postgres health check. Check docker compose ps for unhealthy services.
- Invalid DATABASE_URL — Ensure the connection string matches the Postgres credentials.
- Port conflict — If port 3000 is taken, change the port mapping in docker-compose.yml.
License errors
docker compose logs app | grep -i license- "License invalid" — Verify MAILDESK_LICENSE_KEY is set correctly in .env (no line breaks, no quotes around the JWT).
- "License expired" — Renew your license at maildesk.cloud.
ClamAV slow to start
ClamAV downloads virus definitions on first start, which can take 3–5 minutes.
docker compose logs clamavWorker not processing emails
docker compose logs worker- Verify IMAP credentials are correct in the mailbox settings
- Check that the worker service is healthy
- Ensure Redis is reachable
Architecture Overview
┌─────────────┐
│ Reverse │
│ Proxy │
│ (TLS) │
└──────┬──────┘
│ :3000
┌──────▼──────┐
│ App │
│ (Next.js) │
└──┬──────┬───┘
│ │
┌────────▼─┐ ┌─▼────────┐
│ Postgres │ │ Redis │
│ (pgvec) │ │ (queue) │
└────────▲─┘ └─▲────────┘
│ │
┌──┴──────┴───┐
│ Worker │
│ (IMAP/SMTP) │
└──────┬──────┘
│
┌──────▼──────┐
│ ClamAV │
│ (antivirus) │
└─────────────┘- App — App serves the web UI and API routes
- Worker — Worker connects to mailboxes via IMAP, sends replies via SMTP, processes background jobs
- Postgres — Postgres stores all application data (with pgvector for AI embeddings)
- Redis — Redis handles job queues (BullMQ) and real-time pub/sub (WebSocket)
- ClamAV — ClamAV scans email attachments for malware