# mailgosend Self-hosted email server and webmail client in a single Go binary. ## What it does - **SMTP** — receives inbound mail (port 25), accepts authenticated submission (587 STARTTLS, 465 TLS) - **IMAP** — serves mail to desktop clients (143 STARTTLS, 993 TLS) - **Webmail** — full browser client: read, compose, reply, forward, trash, search (port 8080) - **Admin panel** — manage domains, users, queue, IP bans, security events (port 8081, localhost-only by default) - **CalDAV** — calendar sync compatible with Apple Calendar, Thunderbird, DAVx5 (port 8080 at `/caldav/`) - **CardDAV** — contacts sync compatible with Apple Contacts, Thunderbird, DAVx5 (port 8080 at `/carddav/`) - **DKIM** — signs outbound mail, verifies inbound signatures - **SPF / DMARC** — validates inbound mail policy - **Spam filtering** — DNSBL checks + header heuristics with configurable score threshold - **Encryption at rest** — all email bodies, attachments, contacts, and calendar events encrypted AES-256-GCM - **TOTP / MFA** — per-user two-factor authentication with backup recovery codes - **Auto TLS** — ACME (Let's Encrypt) via DNS-01 or HTTP-01; or bring your own certs - **Multi-domain** — serve multiple mail domains from one instance --- ## Requirements - Go 1.26.3+ - Linux / macOS (Windows: SMTP port 25 typically blocked) - Ports 25, 587, 465 open inbound for mail reception - A real FQDN with MX, A/AAAA, and SPF DNS records --- ## Build ```bash git clone https://ghb.freebede.com/nahakubuilder/mailgosend cd mailgosend go build -o mailgosend ./cmd/mailgosend/ ``` Produces a single ~35 MB binary with all web assets embedded. --- ## First run ```bash ./mailgosend ``` On first run with no `app_config.conf` present, the binary: 1. Generates `app_config.conf` with secure random `ENCRYPTION_KEY` and `SESSION_SECRET` 2. Creates `./data/mail.db` (SQLite) and applies the schema 3. Starts all configured services and prints listening addresses **Back up `app_config.conf` immediately** — the encryption key is required to read stored mail. Losing it means losing access to all stored messages, contacts, and calendar data. --- ## Configuration All settings live in `app_config.conf` (INI-style `KEY = VALUE`). The file is auto-generated with commented defaults on first run. The most important settings: ### Identity | Key | Default | Description | |---|---|---| | `HOSTNAME` | `mail.example.com` | Server FQDN — used in SMTP HELO and TLS SNI | | `DEFAULT_DOMAIN` | `example.com` | Primary mail domain | ### Ports | Key | Default | Description | |---|---|---| | `SMTP_PORT` | `25` | Inbound SMTP | | `SUBMIT_PORT` | `587` | SMTP submission (STARTTLS) | | `SMTPS_PORT` | `465` | SMTP submission (implicit TLS) | | `IMAP_PORT` | `143` | IMAP (STARTTLS) | | `IMAPS_PORT` | `993` | IMAP (implicit TLS) | | `WEBCLIENT_PORT` | `8080` | Webmail + CalDAV + CardDAV | | `WEBADMIN_PORT` | `8081` | Admin panel (binds `127.0.0.1` by default) | Disable any service by setting its `_ENABLED = false` or `_PORT = 0`. ### TLS | Key | Default | Description | |---|---|---| | `TLS_MODE` | `dns01` | `dns01` \| `http01` \| `file` \| `off` | | `ACME_EMAIL` | _(empty)_ | Required for ACME modes | | `ACME_DNS_PROVIDER` | `cloudflare` | `cloudflare` \| `route53` \| `digitalocean` \| `hetzner` | | `TLS_CERT` / `TLS_KEY` | `./certs/` | Certificate paths for `TLS_MODE=file` | For `dns01` (recommended for wildcard certs), set the provider credentials: ```ini TLS_MODE = dns01 ACME_EMAIL = admin@example.com ACME_DNS_PROVIDER = cloudflare CF_DNS_API_TOKEN = your-cloudflare-api-token ``` For manual / existing certs: ```ini TLS_MODE = file TLS_CERT = /etc/letsencrypt/live/mail.example.com/fullchain.pem TLS_KEY = /etc/letsencrypt/live/mail.example.com/privkey.pem ``` ### Storage | Key | Default | Description | |---|---|---| | `DB_DRIVER` | `sqlite` | `sqlite` (embedded, no deps) | | `DB_PATH` | `./data/mail.db` | SQLite database file | | `STORAGE_BACKEND` | `db` | `db` (blobs in SQLite) \| `fs` (files on disk) | | `STORAGE_FS_PATH` | `./data/messages` | Directory for `fs` backend | | `MAX_MESSAGE_SIZE` | `52428800` | Max message size in bytes (50 MB) | ### Security | Key | Default | Description | |---|---|---| | `BRUTE_MAX_TRIES` | `5` | Failed attempts before IP ban | | `BRUTE_WINDOW_MIN` | `30` | Rolling window for attempt counting (minutes) | | `BRUTE_BAN_HOURS` | `24` | How long a banned IP stays banned | | `SECURE_COOKIE` | `false` | Set `true` when serving over HTTPS (marks cookies `Secure`) | | `SESSION_MAX_AGE` | `604800` | Session lifetime in seconds (7 days) | ### Spam | Key | Default | Description | |---|---|---| | `SPAM_THRESHOLD` | `10` | Score at which mail is marked spam | | `SPAM_DNSBL` | `zen.spamhaus.org,...` | Comma-separated DNSBL hosts | | `SPAM_CHECK_SPF` | `true` | Validate SPF on inbound | | `SPAM_CHECK_DKIM` | `true` | Validate DKIM on inbound | | `SPAM_CHECK_DMARC` | `true` | Validate DMARC on inbound | ### Queue / Delivery | Key | Default | Description | |---|---|---| | `QUEUE_MAX_AGE_HOURS` | `72` | Hours before undeliverable mail bounces | | `QUEUE_RETRY_MINS` | `5,15,60,240,480` | Retry backoff schedule (minutes) | | `DNS_PRIMARY` | `1.1.1.1` | Primary resolver for MX lookups | --- ## DNS records (required) Replace `example.com` and `203.0.113.1` with your domain and server IP. ``` ; MX example.com. MX 10 mail.example.com. ; A record for the mail host mail.example.com. A 203.0.113.1 ; SPF — only this server sends mail for example.com example.com. TXT "v=spf1 mx ~all" ; DMARC _dmarc.example.com. TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com" ; PTR (set at your hosting provider — must match HOSTNAME) 1.113.0.203.in-addr.arpa. PTR mail.example.com. ``` DKIM keys are generated per-domain from the admin panel. After generating, copy the displayed TXT record into DNS. --- ## Initial setup workflow 1. Start the binary, open the admin panel at `http://127.0.0.1:8081/admin/` 2. On first run there is no admin account. Create one: ```bash # The binary exposes no separate CLI; create the first admin via the # setup endpoint that appears when zero users exist, or insert directly: ./mailgosend --create-admin admin@example.com ``` *(If no `--create-admin` flag exists yet, use the DB directly or the admin panel bootstrap page.)* 3. Add your domain: Admin → Domains → New Domain 4. Generate DKIM keys: Domain detail page → Generate DKIM 5. Copy the DKIM TXT record into your DNS provider 6. Create users: Admin → Users → New User 7. Point your mail client at the server (IMAP + SMTP credentials = email + password) --- ## Connecting a mail client | Setting | Value | |---|---| | IMAP server | `mail.example.com` | | IMAP port | `993` (TLS) or `143` (STARTTLS) | | SMTP server | `mail.example.com` | | SMTP port | `465` (TLS) or `587` (STARTTLS) | | Username | Full email address (`user@example.com`) | | Password | Account password | | Authentication | Normal password | --- ## CalDAV / CardDAV Clients discover the service via well-known URLs: | URL | Service | |---|---| | `https://mail.example.com/.well-known/caldav` | Calendar discovery | | `https://mail.example.com/.well-known/carddav` | Contacts discovery | Authentication: HTTP Basic Auth (email + password). Compatible with Apple Calendar, Apple Contacts, Thunderbird (with TbSync), and DAVx5 on Android. --- ## Two-factor authentication (TOTP) Users enable MFA from the webmail Settings page. The flow: 1. Settings → Set Up Two-Factor Auth → scan QR code with authenticator app 2. Enter the 6-digit code to confirm → MFA enabled 3. 10 single-use recovery codes are generated and encrypted; displayed once at enrollment time On next login, users enter password then a 6-digit TOTP code (or 8-char recovery code). Admin accounts support the same MFA flow via the admin panel login. --- ## Reloading TLS certificates Send `SIGHUP` to reload certificates without downtime: ```bash kill -HUP $(pidof mailgosend) ``` --- ## Graceful shutdown `SIGTERM` or `SIGINT` (Ctrl+C) triggers graceful shutdown: HTTP servers drain active connections (10-second deadline), queue worker stops, database closes. --- ## Security model - **Sessions**: raw token in `HttpOnly` cookie; SHA-256 hash stored in DB. Cookie theft without DB access yields nothing. - **Passwords**: bcrypt (cost 12). - **Encryption**: AES-256-GCM with HKDF-derived per-user per-purpose keys. Master key in `app_config.conf`. - **CSRF**: stateless HMAC-SHA256 token bound to session + clock hour. No DB storage. - **Brute force**: failed attempts tracked per IP; configurable lockout threshold and duration. - **Rate limiting**: token-bucket per IP — 60 req/min (webmail), 10 req/min (admin). - **Security headers**: CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy on all HTTP responses. - **HTML emails**: rendered in `