first commit
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
# GoMail
|
||||
|
||||
A complete self-hosted email server in a single Go binary — replacing
|
||||
postfix + dovecot + roundcube + spamassassin + radicale with one process,
|
||||
hand-rolled almost entirely on the Go standard library (SMTP, IMAP, POP3,
|
||||
JMAP, CalDAV/CardDAV, ManageSieve, DKIM, SPF/DMARC verification, OAuth2,
|
||||
ACME, JWT — all implemented from scratch, not wrapped around third-party
|
||||
protocol libraries).
|
||||
|
||||
## What's included
|
||||
|
||||
- **Mail transport**: SMTP (inbound MTA + authenticated submission), full
|
||||
security pipeline (SPF, DKIM, DMARC, header/URL heuristics, optional
|
||||
ClamAV/Rspamd/LLM stages), outbound queue with retry and bounce handling
|
||||
- **Mail access**: IMAP4rev1, POP3 (off by default), JMAP (RFC 8620/8621
|
||||
subset)
|
||||
- **Filtering**: Sieve scripts (RFC 5228 subset) managed via ManageSieve
|
||||
(RFC 5804)
|
||||
- **Calendar & contacts**: CalDAV/CardDAV, per-user and per-tenant
|
||||
- **Webmail**: built-in dark-themed web client — folders, compose, search,
|
||||
quarantine review, MFA/app-password/recovery settings
|
||||
- **Admin portal**: tenants, domains (with DKIM key generation/rotation),
|
||||
users, list rules, outbound queue management, global quarantine
|
||||
- **Multi-account**: link external Gmail/Microsoft 365/generic IMAP
|
||||
accounts via OAuth2
|
||||
- **Security**: TOTP MFA with backup codes, app passwords, TLS via real
|
||||
ACME (Let's Encrypt-compatible) certificate issuance and auto-renewal,
|
||||
per-IP rate limiting on every listener
|
||||
- **Everything encrypted at rest**: messages, contacts, calendar events,
|
||||
DKIM/TLS/OAuth2 credentials — AES-256-GCM with a per-record key derived
|
||||
via HKDF from a single master key
|
||||
|
||||
See `gomail-action-plan-v4.md` (if included alongside this README) for a
|
||||
detailed phase-by-phase account of what's built, what's tested, and what's
|
||||
intentionally deferred.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# 1. Generate a master key (required — GoMail refuses to start without one)
|
||||
gomail -gen-master-key
|
||||
# prints: GOMAIL_MASTER_KEY=<64 hex chars>
|
||||
|
||||
# 2. Set required environment variables
|
||||
export GOMAIL_MASTER_KEY=<paste from step 1>
|
||||
export GOMAIL_JWT_SECRET=$(openssl rand -hex 32)
|
||||
export GOMAIL_ADMIN_INIT_PASSWORD='choose-a-strong-password'
|
||||
|
||||
# 3. First run auto-generates a default config at the path you specify
|
||||
gomail -config /etc/gomail/gomail.yaml
|
||||
|
||||
# 4. Edit /etc/gomail/gomail.yaml before running for real:
|
||||
# - server.hostname: your actual mail server hostname
|
||||
# - tls.mode: "acme" with tls.acme_domains set (see below), or "file"
|
||||
# - tls.acme_email: an address you actually monitor (Let's Encrypt
|
||||
# account contact, used for expiry warnings)
|
||||
```
|
||||
|
||||
For a full system install (dedicated user, systemd unit, directory
|
||||
permissions), use `install.sh` instead of running the binary directly —
|
||||
see that script's header comment for exactly what it does and doesn't do.
|
||||
|
||||
## TLS / ACME
|
||||
|
||||
GoMail obtains real Let's Encrypt-compatible certificates automatically
|
||||
when `tls.mode: acme` and `tls.acme_domains` are set:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
mode: acme
|
||||
acme_email: postmaster@yourdomain.com
|
||||
acme_domains: ["mail.yourdomain.com"]
|
||||
# acme_directory_url defaults to real Let's Encrypt production —
|
||||
# override to Let's Encrypt staging while testing (see below)
|
||||
```
|
||||
|
||||
This requires port 80 to be reachable from the internet for HTTP-01
|
||||
challenge validation — GoMail runs its own minimal HTTP server on `:80`
|
||||
for this, it doesn't need a separate web server in front of it for the
|
||||
challenge itself.
|
||||
|
||||
**Before pointing this at Let's Encrypt production, test against
|
||||
staging** — set `tls.acme_directory_url` to
|
||||
`https://acme-staging-v02.api.letsencrypt.org/directory` — to avoid
|
||||
hitting production rate limits while testing. Staging certificates aren't
|
||||
trusted by real browsers/clients, so switch back to the production
|
||||
directory (or just remove the override) once you've confirmed issuance
|
||||
works.
|
||||
|
||||
Without `tls.acme_domains` set, GoMail falls back to a self-signed
|
||||
certificate — fine for local testing, **not suitable for production**.
|
||||
|
||||
## DNS setup
|
||||
|
||||
Once GoMail is running, publish these DNS records for `yourdomain.com`
|
||||
(adjust names/values to match your actual domain and what GoMail logs at
|
||||
bootstrap — the exact DKIM public key is generated per-installation and
|
||||
logged once at first startup, and also viewable via the admin portal's
|
||||
Domains page).
|
||||
|
||||
### MX record
|
||||
Point mail delivery at your GoMail server:
|
||||
|
||||
| Type | Name | Value | Priority |
|
||||
|---|---|---|---|
|
||||
| MX | `yourdomain.com` | `mail.yourdomain.com` | 10 |
|
||||
|
||||
Plus an A/AAAA record for `mail.yourdomain.com` pointing at the server's
|
||||
IP.
|
||||
|
||||
### SPF record
|
||||
Authorizes your server to send mail for the domain:
|
||||
|
||||
| Type | Name | Value |
|
||||
|---|---|---|
|
||||
| TXT | `yourdomain.com` | `v=spf1 mx ~all` |
|
||||
|
||||
`mx` covers "any host listed in this domain's MX records may send mail" —
|
||||
sufficient for a single GoMail instance handling its own outbound
|
||||
delivery. `~all` (soft fail) is a reasonable starting point; tighten to
|
||||
`-all` (hard fail) once you're confident no other source legitimately
|
||||
sends mail as this domain.
|
||||
|
||||
### DKIM record
|
||||
GoMail generates a DKIM key pair automatically for each domain at
|
||||
bootstrap (or when created via the admin portal) and logs the exact TXT
|
||||
record to publish — it looks like this:
|
||||
|
||||
| Type | Name | Value |
|
||||
|---|---|---|
|
||||
| TXT | `mail._domainkey.yourdomain.com` | `v=DKIM1; k=rsa; p=<base64 public key>` |
|
||||
|
||||
The selector (`mail` by default) and the actual key value are
|
||||
domain-specific — copy them from the startup log or the admin portal
|
||||
rather than this example. Rotating the key (also available in the admin
|
||||
portal) requires updating this record to match the new key.
|
||||
|
||||
### DMARC record
|
||||
Tells receiving servers what to do with mail that fails SPF/DKIM
|
||||
alignment, and where to send aggregate reports:
|
||||
|
||||
| Type | Name | Value |
|
||||
|---|---|---|
|
||||
| TXT | `_dmarc.yourdomain.com` | `v=DMARC1; p=quarantine; rua=mailto:postmaster@yourdomain.com` |
|
||||
|
||||
Start with `p=quarantine` (suspicious mail gets flagged, not silently
|
||||
dropped) rather than `p=reject` until you've confirmed legitimate mail
|
||||
flows aren't being caught by DMARC alignment failures.
|
||||
|
||||
### MTA-STS and TLS-RPT
|
||||
**Not yet implemented** by GoMail (see the action plan's Phase 13
|
||||
deferred-items list) — no records to publish for these yet. This section
|
||||
will be filled in once that work lands.
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Full config lives in `gomail.yaml` (auto-generated with defaults on first
|
||||
run) plus a small number of required environment variables for secrets
|
||||
(`GOMAIL_MASTER_KEY`, `GOMAIL_JWT_SECRET`, optionally
|
||||
`GOMAIL_ADMIN_INIT_PASSWORD`) — secrets are deliberately never stored in
|
||||
the YAML file itself. See `internal/config/config.go` for the full set of
|
||||
available fields and their defaults; every field has a sensible default
|
||||
except the two required secrets above.
|
||||
|
||||
## Building from source
|
||||
|
||||
```bash
|
||||
go build -o gomail ./cmd/gomail
|
||||
```
|
||||
|
||||
Requires CGO (for `mattn/go-sqlite3`) — a working C compiler must be
|
||||
available. No other build-time dependencies beyond what `go mod
|
||||
download` fetches.
|
||||
|
||||
## License
|
||||
|
||||
(Not yet specified — add your chosen license here before public release.)
|
||||
Reference in New Issue
Block a user