Docs

What gets installed, how the server is wired, every config key, and honest security notes — so you know exactly what you are running.

▶ On this page

What is DesertEmail

#

DesertEmail is a one-binary, pure-Rust email server written from scratch: mail, IMAP, webmail, DKIM, DNS, and HTTP are hand-rolled pure std. The only dependency is rustls (plus rustls-pemfile and webpki-roots) for TLS — deliberately, because you should never roll your own crypto. A release build lands around ~1.2 MB stripped with rustls+ring.

It implements SMTP (inbound + authenticated submission), IMAP, webmail/admin over HTTP(S), DKIM signing, STARTTLS / implicit TLS, and an outbound MTA with MX lookup, opportunistic STARTTLS, and a disk retry queue — all with plain threads, not async.

Who it is for:

  • Self-hosters who want a readable, rsync-friendly Maildir stack
  • Tiny hardware: Pi Zero, 128 MB VPS, old netbooks, Termux on a phone
  • Learning email protocols by reading a small codebase

It is deliberately minimal. See Security notes before putting it on the public internet.

What gets installed on your machine

#

Default prefix is ~/.desertemail (override with DESERTEMAIL_PREFIX). On Windows: %USERPROFILE%\.desertemail.

  • Binary$PREFIX/bin/desertemail (Windows: desertemail.exe)
  • Config$PREFIX/config.toml (mode 0600 when the OS allows)
  • Optional DKIM key$PREFIX/dkim.pem if you enable DKIM during the wizard
  • Mail data — Maildirs under $PREFIX/data by default (or DESERTEMAIL_DATA_DIR)
  • PATH block — one block appended to your shell rc (zsh/bash/fish/.profile). Markers are literal:
# >>> desertemail PATH >>>
export PATH="$HOME/.desertemail/bin:$PATH"
# <<< desertemail PATH <<<

Fish uses set -gx PATH … between the same markers. Windows adds the bin directory to the user PATH via the registry (no shell-rc markers).

  • Optional systemd unit/etc/systemd/system/desertemail.service only if you opt in (Linux with systemd). The installer uses sudo only when installing that unit and you are not root.

Safety guarantees of the installer:

  • Downloads from this site’s /bin/ and verifies SHA-256 against /bin/SHA256SUMS when present (mismatch aborts; missing sums file warns and continues)
  • Never runs sudo without a reason (systemd unit only) and, interactively, only after you ask for the unit
  • Re-runs never overwrite an existing config.toml without asking (non-interactive re-runs keep the existing config)

How to uninstall

#
  1. Stop the process (or systemctl stop desertemail if you installed the unit).
  2. Delete the prefix directory: rm -rf ~/.desertemail (or your DESERTEMAIL_PREFIX).
  3. Remove the PATH block between the markers in your shell rc (or remove the user PATH entry on Windows).
  4. If installed: sudo systemctl disable --now desertemail and sudo rm /etc/systemd/system/desertemail.service, then sudo systemctl daemon-reload.

That is a complete removal. There are no other system packages, launch agents, or hidden services.

Architecture

#

One process starts cooperating listeners (threads, shared config). Plaintext ports always bind; TLS extras bind only when tls_cert_file + tls_key_file load successfully and the listen address is non-empty:

  1. SMTP inbound — default high port 0.0.0.0:2525 (real-world: 25); STARTTLS (RFC 3207) advertised when TLS is configured
  2. SMTP submission — authenticated clients, default 0.0.0.0:2587 (real: 587); STARTTLS when TLS is configured
  3. IMAP — default 0.0.0.0:2143 (real: 143); STARTTLS (RFC 2595) when TLS is configured
  4. Webmail + admin — HTTP/1.1 on 0.0.0.0:8080 (empty web_listen disables it)
  5. Optional implicit TLS — SMTPS (smtps_listen, e.g. 465), IMAPS (imaps_listen, e.g. 993), HTTPS webmail (web_tls_listen, e.g. 8443; Secure session cookies)
  6. Outbound queue worker — disk queue under data_dir/queue, MX/A lookup over UDP (or optional smarthost), opportunistic STARTTLS to remote MX with webpki-roots validation (falls back to plaintext on failure so delivery is never blocked), exponential backoff 1m → 5m → 15m → 1h → 4h, bounce after 24h, optional DKIM signing
 Internet / peers          Clients (MUA / browser)
        |                           |
        v                           v
   [ SMTP :25 ]            [ submission :587 ]
   (+ STARTTLS)            [ IMAP :143 ] (+ STARTTLS)
        |                  [ SMTPS :465 / IMAPS :993 ]
        |                  [ web :8080 / HTTPS :8443 ]
        v                           |
   +-----------+                    |
   |  Maildir  | <------------------+
   |  storage  |
   +-----------+
        |
        v
   [ queue worker ] --MX/smarthost--> remote MTAs
        |              (opportunistic STARTTLS)
     DKIM sign (optional)

Storage is classic Maildir (cur/ new/ tmp/) per mailbox under data_dir. Easy to back up or rsync. STARTTLS upgrade resets protocol state and discards prior AUTH (RFC 3207 / RFC 2595).

Configuration reference

#

Config is a hand-parsed TOML-like file (key = "value", simple lists, [users] section). Defaults below match src/config.rs Default.

Keys

#
KeyTypeDefaultMeaning
domainslist of strings["localhost"]Domains this server accepts mail for
data_dirstring"./data"Maildir + queue root (absolute path recommended)
smtp_listenstring"0.0.0.0:2525"Inbound SMTP bind address
submission_listenstring"0.0.0.0:2587"Authenticated submission bind
imap_listenstring"0.0.0.0:2143"IMAP bind address
web_listenstring"0.0.0.0:8080"Webmail/admin HTTP bind; empty string disables
admin_userstring / unsetunsetLogin name allowed on /admin; empty/unset disables admin
smarthostoptional stringnoneRelay host:port when direct MX/port 25 is unavailable
smarthost_useroptional stringnoneAUTH user for smarthost
smarthost_passoptional stringnoneAUTH password for smarthost
catch_allbooltrueAccept any local-part@domain and auto-create mailbox
default_passwordstring"changeme"Password for catch-all / auto-created mailboxes
dkim_selectorstring"mail"DKIM selector; DNS name is <selector>._domainkey.<domain>
dkim_key_fileoptional stringnonePath to PEM RSA private key for signing
tls_cert_fileoptional stringnonePEM certificate chain; both this and tls_key_file required to enable TLS
tls_key_fileoptional stringnonePEM private key (PKCS#8 or RSA)
smtps_listenstring"" (disabled)Implicit SMTPS bind (e.g. "0.0.0.0:465"); only bound when TLS cert/key loaded and non-empty
imaps_listenstring"" (disabled)Implicit IMAPS bind (e.g. "0.0.0.0:993"); only bound when TLS loaded and non-empty
web_tls_listenstring"" (disabled)HTTPS webmail bind (e.g. "0.0.0.0:8443"); Secure session cookies when used
require_tls_for_authboolfalseIf true, reject SMTP AUTH on plaintext with 538
[users]mapempty"local" or "user@domain" = plaintext password

Annotated example

#
# Domains this server accepts (MX should point here)
domains = ["example.com", "mail.example.com"]

data_dir = "/home/you/.desertemail/data"

# High ports = no root. Real mail uses 25/587/143 (+ STARTTLS when TLS configured).
smtp_listen = "0.0.0.0:2525"
submission_listen = "0.0.0.0:2587"
imap_listen = "0.0.0.0:2143"

web_listen = "0.0.0.0:8080"
admin_user = "postmaster"

# Optional: when outbound port 25 is blocked
# smarthost = "smtp.example.com:587"
# smarthost_user = "you@example.com"
# smarthost_pass = "app-password"

catch_all = true
default_password = "changeme"   # change this!

# openssl genrsa -out dkim.pem 2048
# desertemail --dkim-dns example.com
# dkim_selector = "mail"
# dkim_key_file = "dkim.pem"

# --- TLS (optional; both cert + key required to enable) ---
# openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
#   -subj "/CN=mail.example.com" -keyout tls.key -out tls.crt
# tls_cert_file = "tls.crt"
# tls_key_file = "tls.key"
# smtps_listen = "0.0.0.0:465"      # implicit SMTPS
# imaps_listen = "0.0.0.0:993"      # implicit IMAPS
# web_tls_listen = "0.0.0.0:8443"   # HTTPS webmail
# require_tls_for_auth = false      # true => AUTH only over TLS (538 on plain)

[users]
"alice" = "alicepass"
"bob" = "bobpass"
"postmaster" = "adminpass"

CLI: desertemail --config path/to/config.toml (or -c). Help: --help / -h. DKIM DNS helper: desertemail --dkim-dns <domain> [--config path].

DNS setup

#
  1. A / AAAA — hostname of your mail server (e.g. mail.example.com) → public IP (DynDNS is fine for home).
  2. MXexample.commail.example.com (priority 10 is typical).
  3. SPF (TXT) — e.g. v=spf1 mx a:mail.example.com ~all.
  4. DKIM (TXT) — generate a key, set dkim_key_file / dkim_selector, then:
    desertemail --dkim-dns example.com --config ~/.desertemail/config.toml
    Publish the printed TXT at <selector>._domainkey.example.com.

rDNS / PTR: many receivers check that your sending IP’s reverse DNS matches a sensible hostname. On a VPS, set PTR to your mail hostname when the provider allows it. Home ISPs rarely give you useful PTR.

Port 25: residential ISPs often block outbound 25. Receiving may still work with port-forward; for sending use a smarthost in config or host on a VPS with open egress on 25.

TLS / encryption setup

#

TLS is built in via rustls (ring crypto provider). There is no async runtime and no other crates for mail protocol code — only TLS uses a library. You supply a certificate and private key; DesertEmail does not talk to ACME / Let’s Encrypt for you.

Quick self-signed cert (testing)

#
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -subj "/CN=mail.example.com" -keyout tls.key -out tls.crt

Then set in config.toml:

tls_cert_file = "tls.crt"
tls_key_file = "tls.key"

Both paths must be set and load successfully. If either is missing or invalid, the server logs a warning and runs plaintext only (fine for LAN/localhost/behind VPN).

Which ports do what

#
ListenerTypical portMode
smtp_listen25 (or 2525)Plaintext; advertises STARTTLS (RFC 3207) when TLS configured
submission_listen587 (or 2587)Same — STARTTLS upgrade; AUTH required for submission
imap_listen143 (or 2143)Plaintext; advertises STARTTLS (RFC 2595) when TLS configured
smtps_listen465Implicit TLS before banner (submission semantics); only if non-empty + TLS loaded
imaps_listen993Implicit TLS before IMAP greeting
web_listen8080HTTP webmail (no TLS on this socket)
web_tls_listen8443HTTPS webmail; session cookie gets the Secure flag

After STARTTLS, the server resets protocol state and discards any prior AUTH (you must re-authenticate under TLS).

Real certificates (Let’s Encrypt via external tools)

#

Obtain a cert with certbot, acme.sh, or your host’s panel, then point DesertEmail at the PEM files:

# Example paths after certbot (adjust for your domain / layout)
tls_cert_file = "/etc/letsencrypt/live/mail.example.com/fullchain.pem"
tls_key_file  = "/etc/letsencrypt/live/mail.example.com/privkey.pem"

smtps_listen = "0.0.0.0:465"
imaps_listen = "0.0.0.0:993"
web_tls_listen = "0.0.0.0:8443"

You own renewal (certbot timer, acme.sh cron, etc.). Restart or re-run DesertEmail after the files on disk change — there is no built-in reload/ACME client.

Force encrypted logins

#
require_tls_for_auth = true

When true, SMTP AUTH on a plaintext connection is rejected with 538 Encryption required for requested authentication mechanism. Default is false so local demos still work without certs.

What still isn’t covered

#
  • No automatic certificate issuance or renewal (no ACME / Let’s Encrypt inside the binary)
  • Outbound TLS is opportunistic — remote STARTTLS with full cert validation; on failure the queue reconnects plaintext so delivery is not blocked
  • Passwords remain plaintext in config.toml; TLS only protects the wire
  • You may still put a reverse proxy in front for HTTP(S) if you prefer terminating TLS elsewhere

The installers

#

Platform installers are generated from installers/template.sh (and a PowerShell / build-from-source sibling). There is no platform auto-detection and no GitHub Releases API in the install path — you pick a button; the script downloads /bin/desertemail-<rust-triple> from this site.

What each run does

#
  1. Download the binary for the fixed target triple baked into that script
  2. Verify SHA-256 against /bin/SHA256SUMS when available
  3. Install under $PREFIX/bin and append the PATH block (idempotent markers)
  4. Run the wizard (or env defaults in non-interactive mode)
  5. Write config.toml if missing / if you confirm overwrite
  6. Optionally install systemd unit (Linux only)
  7. Print start command and DNS/DKIM hints

Wizard questions

#
  • Primary domain
  • Admin username
  • Admin password (hidden input; Enter generates a random one)
  • Data directory
  • Enable webmail? (Y/N)
  • Port set: high (2525/2587/2143) or privileged (25/587/143)
  • Enable DKIM signing? (needs openssl for keygen)
  • Overwrite existing config? (only if present)
  • Install systemd unit? (Linux with systemd)

Env vars (headless / CI)

#

Use DESERTEMAIL_NONINTERACTIVE=1 with optional overrides:

VariablePurpose
DESERTEMAIL_NONINTERACTIVE=1Skip prompts; use defaults / other env vars
DESERTEMAIL_PREFIXInstall root (default ~/.desertemail)
DESERTEMAIL_DOMAINPrimary domain (default localhost)
DESERTEMAIL_ADMIN_USERAdmin / first user (default admin)
DESERTEMAIL_ADMIN_PASSWORDAdmin password (else random)
DESERTEMAIL_DATA_DIRMaildir root (default $PREFIX/data)
DESERTEMAIL_WEBMAIL1/0 — enable web UI (default on)
DESERTEMAIL_PORTShigh or privileged
DESERTEMAIL_DKIM1/0 — generate/use DKIM key
DESERTEMAIL_SYSTEMD1/0 — install unit (POSIX only; Windows has no systemd)
curl -fsSL https://YOUR-SITE/install-linux-x86_64.sh \
  | DESERTEMAIL_NONINTERACTIVE=1 DESERTEMAIL_DOMAIN=example.com sh

Platforms, Windows, Termux, source

#
  • Linux x86_64 / arm64 / armv7 / armv6 (musl static) — curl … | sh
  • macOS Apple Silicon & Intel — same curl form
  • Windowsirm https://YOUR-SITE/install-windows.ps1 | iex (or Bypass if execution policy blocks). Installs desertemail.exe, verifies with Get-FileHash, user PATH entry
  • Android (Termux) — use the Linux ARM64 installer after pkg install curl openssl (F-Droid Termux). High ports need no root
  • Build from source — needs git + Rust toolchain (cargo build --release); use when no prebuilt matches your machine

Security notes & current limitations

#

DesertEmail is an educational / personal-use MVP — not a hardened production MTA. Be explicit about what it does and does not do.

  • TLS is built in (rustls): STARTTLS on SMTP (RFC 3207) and IMAP (RFC 2595), optional implicit SMTPS/IMAPS, HTTPS webmail, and opportunistic outbound STARTTLS with webpki-roots validation. Supply tls_cert_file + tls_key_file to enable. Without them the server runs plaintext — fine for LAN/localhost/VPN; for any internet-facing deploy, configure TLS and consider require_tls_for_auth = true.
  • No automatic cert renewal. Bring your own certificate (self-signed for testing, Let’s Encrypt via certbot/acme.sh externally, or a TLS-terminating reverse proxy). See TLS / encryption setup.
  • Passwords are plaintext in config.toml. Keep the file chmod 600 and treat the prefix as secret. Hashing is planned, not present. TLS protects the wire, not the file on disk.
  • Catch-all + default_password: with catch_all = true, any local-part authenticates with the default password. That is convenient for demos and dangerous if left as changeme on the internet.
  • Webmail sessions: cookie is HttpOnly, SameSite=Lax; on HTTPS (web_tls_listen) the cookie also gets Secure. Tokens are derived from OS CSPRNG material (not just timestamp/PID).
  • DKIM: pure-std RSA-SHA256, relaxed/relaxed; signing path verified against dkimpy in project tests.
  • Missing for “real MTA” use: no spam filter, limited IMAP surface, no rate limiting, no ACME / auto-cert inside the binary.

If you need a production mail gateway today, use Postfix/OpenSMTPD/etc. Use DesertEmail when you want ownership, tiny footprint, and a codebase you can actually finish reading.

FAQ

#
Does it support TLS / encryption?
Yes — built in via rustls. Set tls_cert_file and tls_key_file (both required). You get STARTTLS on SMTP/IMAP, optional implicit ports (465/993), HTTPS webmail, and opportunistic outbound STARTTLS. There is no ACME auto-cert; bring your own files (self-signed, certbot, acme.sh, or a reverse proxy). See TLS / encryption setup.
Will Gmail accept my mail?
Maybe. SPF + DKIM + sensible rDNS help a lot. New or residential IPs still have low reputation; expect spam folders or rejects until reputation builds — or send via a smarthost with good standing.
Can I run it on my phone?
Yes on Termux (Android): Linux ARM64 musl binary, high ports, no root required for that port set.
My ISP blocks port 25 — can I still send?
Set smarthost / smarthost_user / smarthost_pass, or host on a VPS with open egress. Inbound may still work with port-forward if you have a public IP.
Multiple domains?
Yes — list them in domains = ["a.com", "b.com"]. Users can be local-parts or full addresses.
Where is mail stored?
Maildir trees under data_dir (default ~/.desertemail/data). Rsync-friendly; queue lives in data_dir/queue.
How do I update?
Re-run the same installer. Binary is replaced; existing config is kept unless you explicitly overwrite.
Is Windows supported?
Yes: MSVC .exe + PowerShell installer. Maildir uses NTFS-safe naming (!2, instead of :2, in filenames).
Does the installer call GitHub?
No. Binaries and checksums come from this site under /bin/. The source is open source by request — text @bitfent on Telegram.
Is it still “zero dependencies”?
No. Mail/IMAP/webmail/DKIM/DNS/HTTP remain pure std (no async runtime). The only crates are rustls (ring), rustls-pemfile, and webpki-roots for TLS — the one thing you should never hand-roll. Binary size is ~1.2 MB with TLS.

Open source by request — text @bitfent on Telegram for more info.

Binaries are served from this site under /bin/.

“We can’t expect God to do all the work.”

— Joshua Graham