280 lines
9.3 KiB
Markdown
280 lines
9.3 KiB
Markdown
# 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 `<iframe sandbox="allow-same-origin">` — scripts cannot execute.
|
|
- **Admin panel**: binds `127.0.0.1` by default; not exposed to the internet.
|
|
|
|
---
|
|
|
|
## Logging
|
|
|
|
Logs go to stdout by default. Set `LOG_FILE = ./logs/mail.log` to write to a file. Rotate with `logrotate` + `SIGHUP`.
|
|
|
|
---
|
|
|
|
## Upgrading
|
|
|
|
Replace the binary and restart. Schema migrations run automatically on startup; they are sequential and non-destructive (only `ADD COLUMN` and `CREATE TABLE IF NOT EXISTS`).
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
See [LICENSE](LICENSE).
|