A self-hosted mail server written from scratch in Go. SMTP (inbound MTA + authenticated submission), IMAP4rev1, POP3, an outbound delivery queue with DKIM signing and DSN bounces, SPF/DMARC checking, greylisting and a built-in web admin panel + webmail — all in a single static binary with the mail protocols implemented by hand on top of the Go standard library.
Corvus is built as a learning-grade, portfolio reference implementation: every wire protocol (RFC 5321, 3501, 1939) and authentication mechanism (DKIM RFC 6376, SPF RFC 7208, DMARC RFC 7489) is implemented in this repository — no third-party mail libraries.
| Area | What's implemented |
|---|---|
| SMTP inbound (MTA) | RFC 5321 state machine, ESMTP (STARTTLS, SIZE, 8BITMIME, SMTPUTF8, PIPELINING, ENHANCEDSTATUSCODES), open-relay prevention (mail accepted only for local domains), greylisting, SPF check, DKIM verify, DMARC policy, spam scoring → INBOX / Junk |
| SMTP submission | Ports 587 (STARTTLS) & 465 (implicit TLS), AUTH PLAIN/LOGIN over TLS only, DKIM-signs outbound, queues external recipients |
| Outbound delivery | Persistent queue, MX lookup, from-scratch SMTP client, opportunistic STARTTLS, exponential-backoff retry, DSN bounces |
| IMAP4rev1 | LOGIN/AUTHENTICATE, LIST, SELECT/EXAMINE, STATUS, CREATE/DELETE/RENAME, APPEND, FETCH (FLAGS, ENVELOPE, BODY[…], BODYSTRUCTURE, UID), STORE, SEARCH, COPY, EXPUNGE, CLOSE, IDLE, UID variants |
| POP3 | RFC 1939: USER/PASS, STAT, LIST, UIDL, RETR, TOP, DELE, RSET, STLS |
| Auth & crypto | argon2id password hashing, DKIM RSA-2048 sign/verify (relaxed/relaxed canonicalization), AES-256-GCM vault for DKIM private keys |
| TLS | STARTTLS + implicit TLS on every protocol; auto-generated self-signed cert (or bring your own) |
| Web | Admin panel (domains, mailboxes, aliases, queue, DKIM/DNS wizard, live log monitor) + minimal webmail (read, compose, send) |
| Storage | SQLite (pure-Go, no CGO) for metadata + Maildir-style tree on disk for bodies |
| Layer | Technology |
|---|---|
| Language | Go 1.26 — standard library (net, crypto/tls, crypto/rand) |
| Storage | SQLite (pure-Go modernc.org/sqlite, WAL) + Maildir on disk |
| Email auth | DKIM RSA-2048 (RFC 6376), SPF (RFC 7208), DMARC (RFC 7489) — from scratch |
| Crypto | argon2id (passwords), AES-256-GCM (DKIM key vault) |
| Protocols | SMTP (RFC 5321) · IMAP4rev1 (RFC 3501) · POP3 (RFC 1939) |
| Web | html/template + embed — no build step |
| Deploy | Docker · systemd · single static binary |
Only two direct module dependencies (
golang.org/x/cryptofor argon2,modernc.org/sqlitefor pure-Go storage); every mail protocol and DKIM/SPF/ DMARC check is hand-written on the standard library.
Requires Go 1.26+. No CGO, no external services.
git clone https://github.com/vugarfamiloglu/corvus
cd corvus
# build the single binary
CGO_ENABLED=0 go build -o corvus ./cmd/corvus
# create the demo domain (corvus.local) + accounts and a welcome message
./corvus seed
# run all listeners
./corvus serveThen open the web panel at http://localhost:8025 and sign in:
| Account | Password | Role |
|---|---|---|
postmaster@corvus.local |
Corvus2026! |
admin (full panel + webmail) |
alice@corvus.local |
Corvus2026! |
user (webmail) |
bob@corvus.local |
Corvus2026! |
user (webmail) |
Ports default to unprivileged values so Corvus runs without root. Map them to
the standard ports in production via the COR_* env vars.
| Service | Dev | Standard |
|---|---|---|
| SMTP inbound | 2525 | 25 |
| Submission (STARTTLS) | 5870 | 587 |
| Submission (implicit TLS) | 4650 | 465 |
| IMAP (STARTTLS) | 1430 | 143 |
| IMAP (implicit TLS) | 9930 | 993 |
| POP3 (STLS) | 1100 | 110 |
| POP3 (implicit TLS) | 9950 | 995 |
| Web / webmail | 8025 | 8025 |
Submit a message (authenticated) with swaks:
swaks --server localhost:5870 --auth LOGIN \
--auth-user alice@corvus.local --auth-password 'Corvus2026!' \
--from alice@corvus.local --to bob@corvus.local \
--header "Subject: Hello from Corvus" --body "It works."Read it over POP3:
openssl s_client -connect localhost:9950 -quiet
USER bob@corvus.local
PASS Corvus2026!
STAT
RETR 1
QUITConnect a real IMAP client (Thunderbird, K-9, Apple Mail): IMAP host
localhost, port 9930, SSL/TLS, allow the self-signed certificate; SMTP host
localhost, port 5870, STARTTLS. You can read, flag, search, move and send
mail end-to-end.
The web log monitor (/logs) streams every SMTP/IMAP/POP3/delivery event
live so you can watch the protocols at work.
All settings come from COR_* environment variables (see .env.example):
COR_HOSTNAME=mail.example.com
COR_SMTP_ADDR=:25
COR_SUBMISSION_ADDR=:587
COR_IMAP_TLS_ADDR=:993
COR_TLS_CERT=/etc/corvus/fullchain.pem
COR_TLS_KEY=/etc/corvus/privkey.pem
COR_ADMIN_EMAIL=postmaster@example.com
COR_ADMIN_PASSWORD=change-mecorvus serve # run all listeners + delivery worker
corvus seed # create demo domain + accounts
corvus dkim-gen <domain> # print the DKIM DNS record for a domain
corvus passwd <address> <pass> # set a mailbox password
corvus version
Corvus's admin panel generates the exact records to publish. For example.com
with mail host mail.example.com:
mail.example.com. A <server-ip>
example.com. MX 10 mail.example.com.
example.com. TXT "v=spf1 mx -all"
cv1._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=<generated>"
_dmarc.example.com. TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com"
Plus a PTR / reverse-DNS record for the server IP (set at your VPS provider). Real-world deliverability additionally needs: a static IP with outbound port 25 open, a clean (non-blacklisted) IP, and IP warming — see the caveats below.
⚠️ Reality check. Most ISPs and cloud providers block outbound port 25, and a brand-new IP/domain will land in spam until it builds reputation. This repository is a from-scratch reference implementation for learning and portfolio use; running it as your primary production MX requires a dedicated static IP with PTR control and reputation warming.
cmd/corvus/ entrypoint + subcommands (serve/seed/dkim-gen/passwd)
internal/
config/ COR_* configuration
logx/ structured logging + in-memory ring buffer (web log monitor)
store/ SQLite metadata + Maildir storage + AES vault + DKIM keys
auth/ argon2id hashing + SASL PLAIN/LOGIN
mailmsg/ RFC 5322 header parsing + IMAP ENVELOPE + Received headers
dkim/ DKIM RSA-SHA256 sign/verify (relaxed/relaxed)
spf/ SPF policy evaluator
dmarc/ DMARC alignment + policy
spam/ signal-based spam scoring + greylisting
tlsx/ TLS config + self-signed certificate generation
smtp/ SMTP server (inbound MTA + submission)
delivery/ outbound queue worker + SMTP client + DSN bounces
imap/ IMAP4rev1 server
pop3/ POP3 server
web/ admin panel + webmail (html/template, embedded, no build)
Implemented: the full vertical slice above. Some advanced features are
deliberately out of scope for this reference build and noted here for honesty:
Sieve filtering, ARC, MTA-STS/DANE enforcement, IMAP CONDSTORE/QRESYNC,
JMAP, a trained Bayesian classifier, and HA/clustering. Spam scoring uses
authentication signals + heuristics rather than a learned model.
go test ./...Unit tests cover DKIM sign/verify round-trips, argon2 hashing, SASL decoding, the store lifecycle (domains, mailboxes, delivery, recipient resolution, greylisting), IMAP sequence-set/item parsing, SPF CIDR matching, message parsing and delivery backoff.
Apache License 2.0 — see LICENSE.