Profile
Back to NewsBack
GitHub Trending 14 min
Reader Mode
chenyuwebwawa/OpenMail: A self-hosted, open-source mail server suite in a single Node.js process — full-stack email with SMTP / IMAP / POP3, a modern three-pane webmail, an admin dashboard, multi-tenancy, DKIM, anti-spam and 2FA. Zero native dependen

chenyuwebwawa/OpenMail: A self-hosted, open-source mail server suite in a single Node.js process — full-stack email with SMTP / IMAP / POP3, a modern three-pane webmail, an admin dashboard, multi-tenancy, DKIM, anti-spam and 2FA. Zero native dependen

13 hours ago

OpenMail

A self-hosted, open-source mail server suite in a single Node.js process — full-stack email with SMTP / IMAP / POP3, a modern three-pane webmail, an admin dashboard, multi-tenancy, DKIM, anti-spam and 2FA. Zero native dependencies, one SQLite file, up and running in minutes.

OpenMail webmail OpenMail admin dashboard

Compose window DNS configuration helper

简体中文文档


Why OpenMail

Most self-hosted mail stacks glue together a dozen daemons (Postfix, Dovecot, Rspamd, Redis, MySQL…). OpenMail is a deliberately simple, single-binary-style mail platform written entirely in JavaScript:

  • One process runs SMTP (MX + Submission + SMTPS), IMAP, IMAPS, POP3, POP3S, the HTTP API and the webmail UI.
  • Zero native modules — node:sqlite (built into Node.js 22+), pure-JS crypto. Installs in seconds on Windows, Linux, macOS, ARM64.
  • Real protocols — Thunderbird / Apple Mail / Outlook can connect via IMAP/SMTP out of the box.
  • Sane defaults — DKIM keys auto-generated per domain, DNS records rendered in the admin UI, spam scoring built in.
It is ideal for personal mail, small teams, homelabs, and as a readable codebase to learn how email actually works.

Feature matrix

Mail

| Feature | Details | | --- | --- | | Send & receive | Inbound SMTP (port 25 MX), authenticated Submission (587), implicit-TLS SMTPS (465); multiple accounts per instance | | Operations | Read / write / reply / reply-all / forward, batch multi-select move, delete, star, mark read | | Drafts & scheduling | Server-side autosaving drafts every 12 s, scheduled send with a delivery scheduler | | Attachments | Upload, download, preview; drag & drop; paste screenshots directly into the composer; inline images via CID | | Search | Full-text across sender, recipients, subject, body; per-folder or global | | Organization | Folders (system + custom), stars, unread filters, drag & drop messages between folders | | Threading | Conversations grouped by References / In-Reply-To / normalized subject | | Hygiene | One-click archive of read inbox mail, empty trash / junk | | AI assistant | Bring your own OpenAI-compatible API (Base URL + model + key, AES-encrypted server-side): AI draft writing with tone control, translate emails to 12 languages, analyze mail (summary / key points / action items / reply hint) | | Email templates | Save recurring mail as templates, import HTML templates (.html file or paste) or generate them with AI; one-click insert in the composer |

Contacts

Add / edit / delete, groups, search, vCard & CSV import/export, recipient autocomplete from contacts + directory, compose from contact.

Accounts & security

| Feature | Details | | --- | --- | | Auth | Email + password (bcrypt), optional open registration with admin code | | 2FA | TOTP (RFC 6238) with QR code enrollment — Google Authenticator compatible | | Sessions | Multi-device sessions list, per-session and bulk revocation, configurable TTL | | RBAC | admin / user / temp roles enforced server-side on every route | | Audit log | Logins, failed logins, sends, admin actions, deliveries — all recorded with IP |

Domains & multi-tenancy

Multiple domains per instance · per-mailbox storage quotas · aliases with forwarding · Catch-all per domain · automatic 2048-bit DKIM key generation with copy-paste DNS records.

Admin dashboard

Live stats & 14-day send/receive chart · user CRUD / ban / quota / password reset · domain & DKIM management · alias manager · outbound queue inspector · global blacklist · audit log browser · SMTP test · one-click SQLite backup.

Anti-spam & authentication

  • Inbound authentication via SPF, DKIM and DMARC verification (mailauth)
  • Rule-based spam scoring (auth failures, keywords, HELO checks…) → auto junk folder, optional SMTP-level rejection
  • Outbound mail is DKIM-signed automatically using each domain's key
  • Per-user and global sender blacklists
  • Rate limiting per IP on all SMTP ports

Protocols

| Protocol | Dev port | Production port | Purpose | | --- | --- | --- | --- | | SMTP (MX) | 2525 | 25 | Server-to-server delivery | | Submission | 2587 | 587 | User sending with SASL auth (STARTTLS) | | SMTPS | 2546 | 465 | Implicit TLS sending | | IMAP | 1143 | 143 | Mail pull | | IMAPS | 1993 | 993 | Implicit TLS mail pull | | POP3 | 1110 | 110 | Mail download | | POP3S | 1995 | 995 | Implicit TLS download | | HTTP | 3000 | reverse-proxy 443 | Webmail / admin / REST API |
The IMAP/POP3 servers are intentionally minimal (LOGIN, LIST, SELECT, FETCH, STORE, SEARCH, APPEND, IDLE, MOVE…) — enough for everyday client use, not a full RFC 3501 implementation.

Installation

OpenMail ships four install paths — pick one:

| Method | Command / Entry | Best for | | --- | --- | --- | | One-click script | curl -fsSL https://raw.githubusercontent.com/chenyuwebwawa/OpenMail/main/install.sh \| bash | Fresh Linux VPS (installs Node 22, source, .env, systemd, language packs) | | Docker | docker compose up -d --build (source) · ghcr.io/chenyuwebwawa/openmail:latest / chenyutech/openmail:latest (prebuilt) | Any server with Docker / 1Panel | | Manual server | git clone && npm install && npm start | Development, Windows (see start-windows.bat), custom setups | | Hosting panels | BT Panel (宝塔) guide · 1Panel guide | Existing panel-managed servers |

1. One-click (Linux server)

# Interactive: prompts for domain, generates .env, registers a systemd service,

installs all 10 UI language packs and prints the admin password.

curl -fsSL https://raw.githubusercontent.com/chenyuwebwawa/OpenMail/main/install.sh | bash

Behind a network that blocks raw.githubusercontent (e.g. mainland China)? Any of:

curl -fsSL https://cdn.jsdelivr.net/gh/chenyuwebwawa/OpenMail@main/install.sh | bash git clone https://github.com/chenyuwebwawa/OpenMail.git /opt/openmail && cd /opt/openmail && bash install.sh

Non-interactive with options:

bash install.sh --domain example.com --standard-ports --dir /opt/openmail

Updating later — one command on the server (backs up data, snapshots runtime files, pulls the latest code, installs deps, restarts):

cd /opt/openmail && bash scripts/update.sh      # add --check to preview new commits

Flags: --domain (mail domain), --standard-ports (use 25/587/… instead of dev ports), --port (HTTP), --dir (install dir). The server runs as a non-root user via systemd (systemctl status openmail).

2. Docker

OpenMail runs as a single container — SQLite and attachments live in bind mounts, no sidecars. Two ways to get the image:

Option A — build from source (recommended, always works):

git clone https://github.com/chenyuwebwawa/OpenMail.git && cd OpenMail
cp .env.example .env                # set domain / base URL / relay / TLS
docker compose up -d --build

docker-compose.yml reads every OM_* value from your .env (via ${VAR:-default}) instead of hardcoding it; the database, the auto-generated secret.key and admin-credentials.txt persist in ./data, attachments in ./files. The admin password is printed on first launch (also written to data/admin-credentials.txt).

Option B — prebuilt image (published automatically to GHCR and Docker Hub):

The Docker Image workflow builds multi-arch images (linux/amd64, linux/arm64) on every push to main, on v* tags and on manual dispatch, and publishes the same tags to both registries:

  • GHCR — ghcr.io/chenyuwebwawa/openmail:latest (uses the built-in GITHUB_TOKEN, no secrets required)
  • Docker Hub — chenyutech/openmail:latest (public, anonymous pull works; pushed via the maintainer's DOCKERHUB_USERNAME / DOCKERHUB_TOKEN repo secrets)
# if the package is private, log in first with a PAT that has read:packages:

docker login ghcr.io -u <github-user>

docker pull ghcr.io/chenyuwebwawa/openmail:latest

or from Docker Hub (public, anonymous pull works):

docker pull chenyutech/openmail:latest

docker run -d --name openmail --restart unless-stopped \ -p 3000:3000 -p 25:25 -p 587:587 -p 465:465 \ -p 143:143 -p 993:993 -p 110:110 -p 995:995 \ -v openmail-data:/app/data -v openmail-files:/app/files \ -e OM_PRIMARY_DOMAIN=example.com -e OM_BASE_URL=https://mail.example.com \ ghcr.io/chenyuwebwawa/openmail:latest # or: chenyutech/openmail:latest

OM_SECRET is optional: leave it unset and the first boot generates a key, persisted to /app/data/secret.key inside the data volume (so recreating the container keeps saved AI keys working). If you set it explicitly, keep it fixed — changing it invalidates every saved AI API key.

Or use the prebuilt image through Compose (no local build):

# use either registry — both expose the same tags
echo "OPENMAIL_IMAGE=ghcr.io/chenyuwebwawa/openmail:latest" >> .env

echo "OPENMAIL_IMAGE=chenyutech/openmail:latest" >> .env

docker compose up -d
Image not available? Images are built by GitHub Actions and published to GHCR, and — when the repo secrets DOCKERHUB_USERNAME + DOCKERHUB_TOKEN are configured (as they are for this repository) — to Docker Hub as docker.io/chenyutech/openmail (public, multi-arch, anonymous pull). On a brand-new fork the packages may not exist yet: use Option A, or run Actions → Docker Image → Run workflow once; for anonymous GHCR pulls, set the package visibility to Public under repo → Packages → Package settings.
> Healthcheck: the image probes the public route GET / (HTTP 200) every 30 s, so docker compose ps shows healthy once the app is up. Upgrades: docker compose pull && docker compose up -d for the prebuilt image, or git pull && docker compose up -d --build for source builds (source installs use scripts/update.sh instead).
> Full Docker guide — GHCR/Docker Hub publishing, ports & firewall, reverse-proxy TLS, backups, multi-arch and troubleshooting: docs/DOCKER.md.

3. Manual

Requirements: Node.js ≥ 22.5 (no native build tools needed).

git clone https://github.com/chenyuwebwawa/OpenMail.git
cd OpenMail
npm install
npm start

First launch prints the auto-created admin credentials and writes them to data/admin-credentials.txt. On Windows, run start-windows.bat for a double-click launcher.

4. Hosting panels

  • BT Panel (宝塔面板) — docs/panels/BAOTA.md: full walkthrough including the 宝塔邮局 (BT Mail plugin) adaptation — auto-detects port conflicts with Postfix/Dovecot, can take over mail services or co-exist on dev ports, reverse-proxy site setup, SSL reuse, and migration steps from BT Mail to OpenMail. One-shot adapter script: scripts/baota-adapt.sh.
  • 1Panel — docs/panels/1PANEL.md: Docker-Compose orchestration, OpenResty reverse-proxy site, ACME certificates and firewall rules.
Both panel guides cover domain + website access: DNS records → panel site → reverse proxy → HTTPS → mail ports in the panel firewall and cloud security group.

Production checklist

  1. DNS — add A, MX, SPF, DKIM, DMARC records (the admin UI renders them for you; details in docs/DNS-Guide.md).
  2. TLS — point OM_TLS_CERT / OM_TLS_KEY at a Let's Encrypt certificate (or terminate TLS at Nginx/Caddy). Self-signed certs are generated automatically for development.
  3. Outbound relay — most clouds block outbound port 25. Configure OM_RELAY_* to route external mail through an upstream smarthost, or use a provider with port 25 open.
  4. rDNS (PTR) — set a reverse DNS record on your server IP pointing to your mail hostname.
  5. Backups — download SQLite snapshots from the admin UI and back up files/; see docs/DEPLOYMENT.md.

Interface languages (10)

The webmail and admin console ship with English and 简体中文 built in, plus 8 installable language packs (provided as separate content in langpacks/):

🇬🇧 English · 🇨🇳 简体中文 · 🇫🇷 Français · 🇪🇸 Español · 🇵🇹 Português · 🇷🇺 Русский · 🇸🇦 العربية · 🇮🇳 हिन्दी · 🇧🇩 বাংলা · 🇵🇰 اردو

Arabic and Urdu render the whole UI right-to-left automatically. Switch anytime in Settings → Language.

Language settings Arabic RTL webmail

Install packs (any one of):

node scripts/install-langpacks.mjs --all        # CLI: install every pack
node scripts/install-langpacks.mjs fr ar ur     # CLI: specific packs
npm run langpacks                               # show status

or in the admin web UI (Settings → Language → Install), or by copying langpacks/.json → public/locales/. Missing keys fall back to English; see langpacks/README.md to write your own pack.

AI assistant (OpenAI-compatible)

The AI features (write email, translate, analyze, generate HTML templates) run server-side and call any OpenAI-compatible POST {base}/chat/completions endpoint. Configure them per user in Settings → AI assistant: choose a provider preset (OpenAI / DeepSeek / Moonshot / Zhipu GLM / Ollama / custom), fill in Base URL, model and API key, then press Test connection. The key is encrypted with AES-256-GCM in the SQLite database and is never returned in clear text.

  • Base URL formats — either the service root / …/v1 (https://api.deepseek.com, https://api.openai.com/v1) or the full endpoint copied from the provider docs (https://api.deepseek.com/chat/completions). It is normalized on save, and the server calls {base}/chat/completions with Authorization: Bearer — the exact OpenAI chat-completions request shape (stream: false).
  • DeepSeek example — select the DeepSeek preset (Base URL https://api.deepseek.com, model deepseek-chat, or deepseek-reasoner for thinking), paste the API key and save. No manual URL editing is needed.
  • Model list — Fetch models queries the upstream GET {base}/models and lets you pick from a dropdown; manual input still works.
  • Enable / disable — the switch defaults to on for the first setup. While disabled, generation endpoints return 403 AI_DISABLED with a hint, but Test connection still works for diagnostics.
  • Provider differences — reasoning/thinking models are supported: if the provider rejects max_tokens, it is retried as max_completion_tokens; unsupported temperature / response_format are dropped and retried; SSE streams, content-part arrays and reasoning_content responses are handled. Errors include the resolved endpoint, HTTP status and the upstream message.
  • Secrets — saved API keys are encrypted with OM_SECRET; when unset, a key is generated once and persisted to data/secret.key (back it up with data/, and never change it or saved keys stop decrypting). Keys stored by older builds that used a per-boot random secret can no longer be decrypted — re-enter the API key once after upgrading.

Configuration

Everything is configured via environment variables (or a .env file) — see .env.example. Highlights:

| Variable | Default | Description | | --- | --- | --- | | OM_PRIMARY_DOMAIN | localhost | Domain created on first boot (with DKIM keys) | | OM_BASE_URL | http://localhost:3000 | Public URL, used in reset links | | OM_SECRET | auto-generated | Server-side key used to encrypt saved AI API keys. When unset, a random value is generated and persisted to data/secret.key; changing it invalidates stored AI keys (they must be re-entered) — set a fixed long random value in production | | OM_REGISTRATION | true | Allow self-service registration | | OM_RELAY_HOST/PORT/USER/PASS | – | Upstream SMTP relay for outbound mail | | OM_TLS_CERT / OM_TLS_KEY | – | PEM certificate for STARTTLS / implicit TLS | | OM_ANTISPAM, OM_SPAM_SCORE | true, 5 | Junk threshold |

CLI maintenance tool:

node server/setup.js                  # show help
node server/setup.js reset-admin [email protected] NewPass123
node server/setup.js add-domain example.org
node server/setup.js list-users

Architecture

┌────────────────────────── one Node.js process ──────────────────────────┐
│  HTTP :3000          SMTP :25        Submission :587/465   IMAP :143/993│
│  ┌───────────┐      ┌─────────┐     ┌────────────┐       ┌──────────┐   │
│  │ Webmail   │      │ MX pipe │     │ SASL auth  │       │ Minimal  │   │
│  │ Admin UI  │      │ ▼       │     │ ▼          │       │ IMAP/POP3│   │
│  │ REST API  │      │ mailauth(SPF/DKIM/DMARC) + spam score          │   │
│  └─────┬─────┘      └────┬────┘     └─────┬──────┘       └────┬─────┘   │
│        │                 └───────┬────────┘                   │         │
│        ▼                         ▼                            ▼         │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                mailstore (folders, threads, quota, filters)      │  │
│  ├──────────────────────────────────────────────────────────────────┤  │
│  │   node:sqlite (WAL)             files/ attachments (per-user)    │  │
│  └──────────────────────────────────────────────────────────────────┘  │
│  scheduler: scheduled send · outbound queue retry · bounce notices     │
└─────────────────────────────────────────────────────────────────────────┘
openmail/
├── .github/workflows/      # CI: build & publish multi-arch images to GHCR + Docker Hub
├── install.sh              # one-click installer (Node 22 + systemd + langpacks)
├── start-windows.bat       # Windows double-click launcher
├── scripts/                # update.sh (one-click updater) · doctor.sh (self-diagnosis) · install-langpacks.mjs · baota-adapt.sh (BT panel)
├── server/
│   ├── index.js            # entry: HTTP + all services + first-run init
│   ├── smtp.js             # MX / Submission / SMTPS servers
│   ├── imap.js             # minimal IMAP4rev1 server
│   ├── pop3.js             # POP3/POP3S server
│   ├── db.js               # node:sqlite schema + helpers
│   ├── config.js           # env config
│   ├── setup.js            # CLI maintenance tool
│   ├── util/               # TOTP, DKIM keys, TLS, sessions, RBAC
│   ├── mail/               # mailstore, delivery pipeline, outbound, scheduler
│   └── routes/             # auth / mail / contacts / settings / admin / langs APIs
├── public/                 # webmail + admin SPA (vanilla ES modules, no build)
│   ├── js/i18n.js          # i18n runtime (10 languages, RTL aware)
│   └── locales/            # installed language files (en + zh built in)
├── langpacks/              # extra language packs + installer manifest (8 languages)
├── scripts/
│   ├── install-langpacks.mjs
│   ├── update.sh — 一键更新
│   └── baota-adapt.sh      # BT Panel (宝塔) adapter incl. BT Mail plugin handling
├── tests/e2e.mjs           # 119-check offline e2e suite (API, SMTP, IMAP, POP3, i18n, mock-AI)
├── docs/                   # DNS guide, deployment guide, Docker guide (docs/DOCKER.md), panel guides, screenshots
├── Dockerfile · docker-compose.yml · .env.example
Open http://localhost:3000/ for the interactive product site; the webmail lives at /app/.

Stack: Express 5 · nodemailer (MIME/DKIM) · smtp-server · mailparser · mailauth (SPF/DKIM/DMARC) · bcryptjs · node:sqlite. Frontend is a dependency-free vanilla-JS SPA with light/dark themes.

Comparison with other self-hosted solutions

| Solution | Style | Best for | Resources | | --- | --- | --- | --- | | OpenMail | Single Node.js process, batteries included | Personal / small teams, learning, fast deploys | 512 MB RAM | | mailcow | Docker full stack, most complete | SMEs wanting everything | 6 GB RAM + 20 GB disk | | Mailu | Lightweight Docker | Resource-constrained setups | 1–3 GB RAM | | iRedMail | Traditional installer | Bare-metal preference | 2 GB+ RAM | | Modoboa | Panel-centric, modular | Mail hosting providers | 2 GB+ RAM | | Stalwart | Rust, all-in-one, JMAP | Performance-focused | low (512 MB) | | Mox / Maddy | Go, minimal ops | Personal, enthusiasts | 512 MB– | | Postfix+Dovecot+Roundcube | Classic combo | Deep customization | à la carte |

Hardware & network requirements

| Tier | CPU | RAM | Disk | | --- | --- | --- | --- | | Personal (< 10 users) | 1 GHz dual-core | 512 MB – 1 GB | 20 GB SSD | | Team (10–50 users) | 2–4 cores | 2 GB | 100 GB+ SSD |

Network: a static public IPv4, outbound port 25 (or a relay), reverse DNS (PTR) on your IP, firewall limited to the mail ports. Mainland-China cloud providers usually block outbound 25 — use OM_RELAY_* or an overseas VPS.

Development & testing

npm test            # 119-check offline e2e suite against a running server (mock AI upstream included)
npm run test:secret # verify the auto-generated OM_SECRET survives restarts
npm run dev         # node --watch auto-restart

The suite covers registration/login, internal & scheduled sending, attachments, external delivery over raw SMTP, SASL submission, IMAP (fetch/store/search/append/list), POP3, folders, filters, blacklists, contacts import/export, 2FA, and every admin API including DKIM/DNS records.

Security model & known limitations

  • Passwords bcrypt-hashed; sessions are random 256-bit tokens stored hashed; cookies are HttpOnly/SameSite.
  • Mail HTML is sanitized (script/style/iframes/event handlers stripped) and links get rel=noopener`.
  • SMTP open-relay protection: port 25 only accepts recipients on hosted domains; Submission requires auth.
  • Known limitations (roadmap): IMAP is minimal (no CONDSTORE/QRESYNC), no JMAP yet, anti-spam is rule-based rather than Bayesian, no built-in CalDAV/CardDAV, per-message quota accounting is approximate on shared attachments.
  • This project ships real mail-server functionality — run it on a properly configured host with TLS, and keep backups.

License

MIT — contributions welcome!

NeshDevTech

NeshDevTech is a professional technology firm passionate about creating innovative, high-performance web and mobile solutions.

Core Services
  • Custom Laravel Apps
  • SPA (React & Vue.js)
  • Flutter Mobile Apps
  • SEO & Performance Opt
Get in Touch
Available for Projects

Need a high-performing system? Let's discuss your project.


© 2026 NeshDevTech. All rights reserved.

Built with

Chat with me