Building a Self-Hosted Email Server: Complete Overview
Google Workspace increased prices 17-22% in 2025. At $168/year per user, a 10-person team pays $1,680 annually. Self-hosting? Just $72/year for the server. That’s 95% cost savings.
But cost isn’t the only reason. With corporate surveillance, data mining for AI training, and the constant threat of account suspension, taking control of your email infrastructure has never been more compelling.
Over 22 days in December 2025 and January 2026, I built a complete email server from scratch in Go. This article chronicles the architecture, performance metrics, deployment strategies, and unique features that emerged from 87 commits.
Why Self-Host Email in 2026?
Cost Comparison
| Solution | Annual Cost (10 users) | Per-User Cost | Privacy | Control |
|---|---|---|---|---|
| Google Workspace | $1,680 | $168 | Low | None |
| Microsoft 365 | $1,500 | $150 | Low | None |
| ProtonMail Business | $900 | $90 | High | Limited |
| Self-Hosted | $72 | $7.20 | Maximum | Complete |
Beyond Cost: Privacy and Control
Modern email providers scan every message. Gmail uses email content for ad targeting and AI training. ProtonMail encrypts in transit but requires proprietary clients. Self-hosting gives you:
- Zero corporate surveillance: Your emails never leave your server
- Unlimited users: $72/year covers everyone, forever
- No account suspension: You own your data, no one can revoke access
- Standards compliance: Full IMAP/SMTP/CalDAV/CardDAV support
- Custom features: Add whatever you need (we built unique features like Screener and Send Later)
Architecture Overview
The server follows a layered architecture with clear separation of concerns:
┌───────────────────────────────────────────────────────────────────────┐
│ Mail Server │
├───────────┬───────────┬───────────┬───────────┬───────────┬───────────┤
│ SMTP(25) │ Sub(587) │ IMAP(993) │ DAV(8443) │Admin(8080)│ AutoDisc │
├───────────┴───────────┴───────────┴───────────┴───────────┴───────────┤
│ Security Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Rate Limit │ │ Greylisting │ │ Audit │ │ TLS/ACME │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
├───────────────────────────────────────────────────────────────────────┤
│ Authentication Layer │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Argon2id │ │ User Quotas │ │ Multi-Domain │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
├───────────────────────────────────────────────────────────────────────┤
│ Delivery Engine │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Redis │ │ Circuit │ │ Retry w/ │ │ TLS │ │
│ │ Queue │ │ Breakers │ │ Backoff │ │ Fallback │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │
├───────────────────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ SQLite │ │ Maildir │ │ DKIM │ │
│ │ (metadata) │ │ (emails) │ │ (signing) │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
├───────────────────────────────────────────────────────────────────────┤
│ Observability │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Prometheus │ │ Audit │ │ Health │ │
│ │ Metrics │ │ Logs │ │ Checks │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
Core Components
The codebase is organized into 21 internal packages:
internal/
├── admin/ # Web interface and HTTP handlers
├── api/ # RESTful API endpoints
├── audit/ # Security event logging
├── auth/ # Authentication with Argon2id
├── autodiscover/ # Outlook/Apple Mail auto-configuration
├── config/ # Configuration management
├── dav/ # CalDAV/CardDAV server
├── dns/ # DNS verification and validation
├── greylist/ # Greylisting for spam prevention
├── imap/ # IMAP server with IDLE support
├── logging/ # Structured logging
├── metrics/ # Prometheus metrics
├── queue/ # Redis-backed message queue
├── resilience/ # Circuit breakers and retry logic
├── security/ # TLS, rate limiting, encryption
├── setup/ # Setup wizard and preflight checks
├── sieve/ # Email filtering engine
├── smtp/ # SMTP server and delivery engine
├── storage/ # SQLite database and Maildir storage
├── validation/ # Input validation
└── features/ # Unique features (Screener, Send Later, etc.)
Tech Stack Rationale
Why Go?
Go emerged as the ideal choice for several reasons:
- Concurrency primitives: Goroutines make handling concurrent IMAP connections and SMTP sessions trivial
- Static binary: Single executable deployment—no runtime dependencies
- Performance: Compiled to native code with excellent memory management
- Standard library: Robust HTTP server, crypto, and database packages
- Ecosystem: Excellent IMAP/SMTP libraries from the emersion project
Storage Choices
# SQLite for metadata
database_path: /var/lib/mailserver/mail.db
# Lightweight, embedded, no external database server needed
# Perfect for user accounts, domains, DKIM keys
# Maildir for email storage
maildir_path: /var/lib/mailserver/maildir
# Standard format, easy to backup, works with standard tools
# File-based, one file per email, O(1) access
# Redis for message queue
redis:
address: localhost:6379
db: 0
# In-memory, persistent when configured
# Perfect for delivery retries and scheduled emails
Why SQLite over PostgreSQL/MySQL?
For an email server:
- Zero admin overhead: No database server to monitor or patch
- Atomic backups: Single file = simple snapshots
- Sufficient scale: Handles hundreds of domains and thousands of users
- Fast: In-memory caching makes reads instant
For the message queue, Redis provides:
- Ordered operations: Critical for delivery retries
- Expiration: Auto-cleanup of old queue entries
- Atomic operations: Prevent race conditions
Performance Metrics
After comprehensive optimizations (commits e7b2314, 4beac86), the server achieves remarkable efficiency:
Memory Footprint
| Metric | Before | After | Improvement |
|---|---|---|---|
| Current Memory | 149.65 MB | 9.39 MB | 94% reduction |
| Peak Memory | 459.05 MB | 71.09 MB | 85% reduction |
| Resident Memory | 282 MB | 27.4 MB | 90% reduction |
Current Usage (Post-Optimization)
| Metric | Value | % of Limit |
|---|---|---|
| Current Memory (cgroup) | 9.39 MB | 1.8% |
| Peak Memory | 71.09 MB | 14% |
| Binary Size | 32 MB | - |
CPU Usage
| Metric | Value |
|---|---|
| Current CPU | 0.1% |
| Threads | 6 |
| Load Average | 0.07 (1m), 0.03 (5m), 0.00 (15m) |
Capacity Estimates
| Metric | Current | Estimated Max |
|---|---|---|
| Concurrent IMAP | 6 | ~500-1000 |
| Emails/hour | ~1 | ~2000-5000 |
| Mailboxes | 8 | ~500-1000 |
Assessment: The server runs at 10-15% capacity and can handle 10x growth without hardware changes.
Optimization Techniques Applied
- Memory leak fix (commit
e7b2314): Fixed UserRateLimiter with periodic cleanup - Pre-compiled regex: Avoided compilation on every request
- IMAP session pre-allocation: Reduced GC pressure from map growth
- String allocation fixes: Replaced unnecessary conversions
- Goroutine limits: Prevented unbounded spawn under load
Feature Matrix
Core Email Protocols
| Feature | Implementation | Status |
|---|---|---|
| IMAP Server | go-imap/v2 with native IDLE support | ✓ |
| IDLE Push Notifications | Real-time email delivery | ✓ |
| POP3 Support | Legacy compatibility (removed in 7d093d3) | ✗ |
| SMTP Server | go-smtp with delivery engine | ✓ |
| SMTPS (465) | SSL/TLS | ✓ |
| Submission (587) | STARTTLS | ✓ |
| DKIM Signing | Per-domain signing keys | ✓ |
| SPF Verification | Inbound authentication | ✓ |
| DMARC Verification | Policy enforcement | ✓ |
| Relay Host | Smart outbound delivery | ✓ |
Calendar & Contacts
| Feature | Implementation | Status |
|---|---|---|
| CalDAV Server | Full WebDAV + CalDAV protocol | ✓ |
| CardDAV Server | vCard standard support | ✓ |
| Apple Calendar | Tested and working | ✓ |
| Thunderbird | Full compatibility | ✓ |
Security Features
| Feature | Implementation | Status |
|---|---|---|
| TLS/ACME | Automatic Let’s Encrypt certificates | ✓ |
| Argon2id | OWASP-recommended password hashing | ✓ |
| Greylisting | Configurable delay and age | ✓ |
| Rate Limiting | Per-user limits with cleanup | ✓ |
| Audit Logging | All security events tracked | ✓ |
| TLS Fallback | Graceful degradation for misconfigured servers | ✓ |
Administration
| Feature | Implementation | Status |
|---|---|---|
| Web Admin Panel | Hacker News-style UI | ✓ |
| Prometheus Metrics | /metrics endpoint | ✓ |
| Health Endpoints | /health and /health/detailed | ✓ |
| Auto-discovery | Outlook and Apple Mail support | ✓ |
| Setup Wizard | Guided installation | ✓ |
| Preflight Checks | Pre-deployment validation | ✓ |
| Doctor Command | Diagnostic troubleshooting | ✓ |
Unique Features
| Feature | Implementation | Commit |
|---|---|---|
| Screener | Hey.com-style first-contact filtering | dc3201e |
| Email Aliases | Disposable/masked addresses with tracking | dc3201e |
| Send Later | Scheduled email delivery | dc3201e, 6e487f7 |
| Undo Send | Configurable 0-30s delay | dc3201e |
| Email Snooze | Hide and reappear later | dc3201e, e2859ef |
| VIP Contacts | Priority sender management | dc3201e, cb873d7 |
| Two-Factor Auth | Admin panel security | 7cf5afb |
Development Timeline
The project evolved rapidly over 22 days, from December 19, 2025 to January 10, 2026:
Phase 1: Foundation (Dec 19-20, 2025)
Initial implementation of personal email server(Dec 19)Add CLI entry point with serve, migrate, domain, and user commands(Dec 19)Add CalDAV/CardDAV support, DKIM signing, and deployment files(Dec 19)Add comprehensive tests and documentation(Dec 19)Add detailed Linode + Cloudflare deployment guide(Dec 19)
Key accomplishments: Core IMAP/SMTP servers, CLI, basic CalDAV/CardDAV, deployment guides.
Phase 2: Production Readiness (Dec 20-21, 2025)
Fix IMAP SELECT crash by always setting status fields(Dec 20)Fix IMAP crash when client requests BODYSTRUCTURE(Dec 20)Add instant email notifications via IMAP IDLE(Dec 20)Performance: instant email notifications with async delivery and O(1) file access(Dec 20)Fix critical IDLE bug: remove run() goroutine stealing updates(Dec 20)Upgrade to go-imap v2 for native IDLE support(Dec 20)Add Sieve filtering, admin panel, and DNS tools(Dec 20)Fix admin panel SQL queries for users table(Dec 20)Wire up Sieve executor and admin server in serve command(Dec 20)
Key accomplishments: IMAP IDLE support, Sieve filtering, web admin panel, stability fixes.
Phase 3: Reliability & Auto-discovery (Dec 21-30, 2025)
Deep reliability audit: IMAP, Sieve, Queue, DNS, DAV, Delivery(Dec 21)Major reliability improvements: Apple-quality production code(Dec 21)Fix Apple mobileconfig: use SMTPS port 465 with SSL(Dec 21)Add email client autodiscover for easy setup(Dec 21)Add queue monitoring to admin UI and extend session duration(Dec 21)Add security, reliability, and usability improvements(Dec 21)Fix: Start DAV server for CalDAV/CardDAV support(Dec 30)Improve CalDAV/CardDAV compatibility for Apple clients(Dec 30)Improve DAV security and fix SMTP authentication(Dec 30)
Key accomplishments: Auto-discovery for Outlook/Apple Mail, CalDAV/CardDAV fixes, reliability audit.
Phase 4: Security & Monitoring (Dec 31, 2025 - Jan 2, 2026)
Add metrics, audit logging, greylisting, and fix TLS delivery fallback(Dec 31, 2025)Clarify encryption and privacy claims(Jan 2, 2026)Use /tmp for internal files(Jan 2, 2026)
Key accomplishments: Prometheus metrics, audit logging, greylisting, TLS delivery fallback.
Phase 5: Multi-Domain & DKIM (Jan 4-8, 2026)
Add bind_address config option for multi-homed servers(Jan 4)Add transactional email API with SendGrid-like functionality(Jan 5)Major server improvements: security, reliability, and logging(Jan 5)Add database migration for DKIM key management(Jan 7)Add DKIM key storage abstraction layer(Jan 7)Add CLI commands for DKIM management and backup/restore(Jan 7)Add DKIM management to admin dashboard domains page(Jan 7)Add system management page with backup/restore to admin(Jan 7)Add DNS verification feature for domains(Jan 8)Add domain setup wizard with DNS verification flow(Jan 8)
Key accomplishments: Full multi-domain support, DKIM key management, DNS verification wizard, transactional email API.
Phase 6: UI Improvements & 2FA (Jan 9-10, 2026)
Fix migration 006 to record version in schema_migrations(Jan 9)Add two-factor authentication for admin panel(Jan 9)Fix QR code generation and DNS wizard CSRF issues(Jan 9)Simplify domain add form to single step(Jan 9)Fix autodiscover to support multiple domains dynamically(Jan 9)Fix multi-domain support throughout the codebase(Jan 9)Add server performance report with live metrics(Jan 9)Performance optimizations for memory and CPU(Jan 9)Refactor admin UI to HN style with shared CSS file(Jan 9)Add breadcrumbs navigation to all admin templates(Jan 9)Optimize UI and fix critical security vulnerabilities(Jan 9)Add admin dashboard improvements: phases 2-5(Jan 9)Improve mobile responsiveness in admin interface(Jan 10)
Key accomplishments: 2FA security, mobile responsive UI, performance optimizations, HN-style admin design.
Phase 7: Unique Features (Jan 10-12, 2026)
Add unique features: Screener, Aliases, Send Later, Undo Send, Snooze(Jan 12)Refactor features to use admin dashboard pattern(Jan 12)Initialize features store and add navigation link(Jan 12)Add comprehensive tests for unique features(Jan 12)Make all feature cards clickable(Jan 12)Standardize features UI to match admin design system(Jan 12)Add Send Later and Snooze features UI(Jan 12)Start feature scheduler for Send Later, Snooze, Undo Send(Jan 12)Add MessageMover for snooze wake-ups(Jan 12)Use delivery queue for scheduled email sending(Jan 12)Add email sender for scheduled email delivery(Jan 12)Add VIP checking and smart zone detection(Jan 12)
Key accomplishments: Hey.com-style Screener, email aliases, Send Later, Undo Send, Snooze, VIP contacts.
Code Examples
IMAP Server with IDLE Support
The IMAP server uses the go-imap/v2 library with native IDLE support:
// @filename: main.go
// internal/imap/server.go
func (s *Server) handleIDLE(c *server.Conn, cmd *imap.Command) error {
// Create IDLE handler
handler := &idle.Handler{
NewCallback: func() {
// Send EXISTS update when new mail arrives
c.SendUpdates(&imap.StatusUpdate{
Mailbox: c.Mailbox(),
Updates: []imap.StatusItem{imap.StatusMessages},
})
},
}
// Start IDLE
return handler.Handle(cmd)
}
This implementation provides instant push notifications when new emails arrive, eliminating the need for polling.
DKIM Signing
DKIM signing is performed per-domain with automatically managed keys:
// @filename: main.go
// internal/smtp/dkim.go
func (b *Backend) SignMessage(msg *message.Message, domain string) error {
// Get DKIM key for domain
key, err := b.dkimStore.GetKey(domain)
if err != nil {
return err
}
// Create DKIM signer
signer, err := dkim.NewSigner(b.hostname, "mail", domain, key)
if err != nil {
return err
}
// Add DKIM-Signature header
return signer.Sign(msg)
}
Delivery Engine with Retry Logic
The delivery engine uses exponential backoff with per-domain circuit breakers:
// @filename: main.go
// internal/smtp/delivery/worker.go
func (w *Worker) deliver(msg *queue.Message) error {
// Check circuit breaker
if w.circuit.IsOpen(msg.ToDomain) {
return fmt.Errorf("circuit breaker open for %s", msg.ToDomain)
}
// Attempt delivery
err := w.smtpClient.Deliver(msg)
if err != nil {
// Record failure
w.circuit.RecordFailure(msg.ToDomain)
// Calculate backoff
delay := w.calculateBackoff(msg.Attempts)
// Requeue with delay
return w.queue.Requeue(msg.ID, delay)
}
// Success - close circuit
w.circuit.RecordSuccess(msg.ToDomain)
return nil
}
func (w *Worker) calculateBackoff(attempt int) time.Duration {
// Exponential backoff: 5m, 15m, 30m, 1h, 2h, 4h, 8h, 16h, 24h (capped)
backoffs := []time.Duration{
5 * time.Minute,
15 * time.Minute,
30 * time.Minute,
1 * time.Hour,
2 * time.Hour,
4 * time.Hour,
8 * time.Hour,
16 * time.Hour,
24 * time.Hour,
}
idx := min(attempt, len(backoffs)-1)
// Add ±10% jitter
jitter := time.Duration(float64(backoffs[idx]) * 0.1 * (rand.Float64()*2 - 1))
return backoffs[idx] + jitter
}
Greylisting Implementation
Greylisting rejects first-time senders temporarily:
// @filename: main.go
// internal/greylist/store.go
func (s *Store) ShouldGreylist(ip, from, to string) (bool, time.Duration, error) {
key := s.buildKey(ip, from, to)
// Check if we've seen this triplet before
entry, err := s.get(key)
if err != nil {
if err == ErrNotFound {
// First time - greylist
return true, s.minDelay, nil
}
return false, 0, err
}
// Check if we've passed the minimum delay
if time.Since(entry.SeenAt) < s.minDelay {
// Too soon - still greylisting
return true, s.minDelay - time.Since(entry.SeenAt), nil
}
// Passed delay - remember and allow
entry.SeenAt = time.Now()
entry.ExpiresAt = time.Now().Add(s.maxAge)
s.set(key, entry)
return false, 0, nil
}
Deployment Options
Docker Deployment
# docker-compose.yml
version: '3.8'
services:
mailserver:
build: .
ports:
- '25:25'
- '587:587'
- '465:465'
- '143:143'
- '993:993'
- '8443:8443'
- '8080:8080'
volumes:
- ./config:/etc/mailserver
- maildata:/var/lib/mailserver
depends_on:
- redis
environment:
- TZ=UTC
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
maildata:
redisdata:
Systemd Deployment (Production)
# @filename: .env
# /etc/systemd/system/mailserver.service
[Unit]
Description=Mail Server
After=network.target redis.service
Requires=redis.service
[Service]
Type=simple
User=mailserver
Group=mailserver
ExecStart=/usr/local/bin/mailserver serve --config /etc/mailserver/config.yaml
Restart=always
RestartSec=5s
StandardOutput=journal
StandardError=journal
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/mailserver
[Install]
WantedBy=multi-user.target
Enable and start:
# @filename: script.sh
sudo systemctl daemon-reload
sudo systemctl enable mailserver
sudo systemctl start mailserver
sudo journalctl -u mailserver -f
Bare Metal Deployment
For maximum performance and minimal overhead:
# @filename: script.sh
# 1. Build from source
git clone https://github.com/fenilsonani/email-server.git
cd email-server
go build -o mailserver ./cmd/mailserver
# 2. Copy to system location
sudo cp mailserver /usr/local/bin/
sudo chmod +x /usr/local/bin/mailserver
# 3. Create directories
sudo mkdir -p /etc/mailserver /var/lib/mailserver/maildir
sudo chown mailserver:mailserver /var/lib/mailserver
# 4. Initialize
sudo -u mailserver mailserver setup
sudo -u mailserver mailserver migrate
Quick Start Wizard
The built-in setup wizard handles the entire initial configuration:
# @filename: script.sh
# Run preflight checks
./mailserver preflight
# Interactive setup wizard
./mailserver setup
# Diagnose any issues
./mailserver doctor
The wizard checks:
- Port availability (25, 587, 465, 143, 993, 8080, 8443)
- DNS configuration (MX, TXT, SRV records)
- Redis connectivity
- Directory permissions
- TLS certificate status
Monitoring and Observability
Prometheus Metrics
Metrics are exposed at /metrics on the admin port:
curl http://localhost:8080/metrics
Available metrics:
| Metric | Type | Description |
|---|---|---|
mailserver_messages_received_total | Counter | Total inbound messages |
mailserver_messages_sent_total | Counter | Successful deliveries |
mailserver_messages_rejected_total | Counter | Rejected messages (by reason) |
mailserver_messages_bounced_total | Counter | Bounced messages |
mailserver_delivery_duration_seconds | Histogram | Delivery latency |
mailserver_queue_depth | Gauge | Current queue size |
mailserver_auth_attempts_total | Counter | Auth attempts (by result/protocol) |
mailserver_quota_exceeded_total | Counter | Quota rejections |
mailserver_active_connections | Gauge | Active connections (by protocol) |
Health Endpoints
# @filename: script.sh
# Basic health check
curl http://localhost:8080/health
# {"status":"ok"}
# Detailed health with component status
curl http://localhost:8080/health/detailed
# {"status":"ok","components":{"database":"ok","redis":"ok","storage":"ok"}}
Grafana Dashboard
Import the included dashboard from deploy/grafana-dashboard.json for visualization of all metrics.
Unique Features Deep Dive
The Screener
Inspired by Hey.com, the Screener holds emails from first-time senders until you approve them:
// @filename: main.go
// internal/features/screener.go
func (s *Screener) CheckMessage(from, to string) (Action, error) {
// Check if sender is approved
approved, err := s.isApproved(from, to)
if err != nil {
return Reject, err
}
if approved {
return Accept, nil
}
// First time - move to Screener mailbox
return Screen, nil
}
Benefits:
- Zero spam from unknown senders
- One-click approval for legitimate contacts
- Automatic whitelist for approved senders
Email Aliases
Create disposable or masked email addresses with usage tracking:
// @filename: main.go
// internal/features/aliases.go
func (a *AliasManager) CreateAlias(user, domain string) (string, error) {
// Generate random alias
alias := fmt.Sprintf("alias-%s@%s", generateToken(), domain)
// Store mapping
err := a.store.Set(alias, user)
if err != nil {
return "", err
}
return alias, nil
}
func (a *AliasManager) DeliverToRealRecipient(alias string) (string, error) {
return a.store.Get(alias)
}
Use cases:
- Sign up for services without exposing your real email
- Track which company leaked your email
- Disable compromised aliases instantly
Send Later
Schedule emails for future delivery:
// @filename: main.go
// internal/features/scheduler.go
func (s *Scheduler) ScheduleEmail(msg *Email, sendAt time.Time) error {
// Calculate delay
delay := time.Until(sendAt)
// Add to delivery queue with scheduled time
return s.queue.AddWithDelay(msg.ID, delay)
}
Benefits:
- Work across time zones without waking people up
- Batch send emails at optimal times
- Never forget to send important emails
Undo Send
Configurable delay before sending:
features:
undo_send:
enabled: true
delay_seconds: 10 # 0-30 seconds
Email Snooze
Hide emails and have them reappear later:
// @filename: main.go
// internal/features/snooze.go
func (s *Snoozer) SnoozeEmail(msgID, mailbox string, until time.Time) error {
// Move to Snoozed mailbox
err := s.moveMail(msgID, "Snoozed")
if err != nil {
return err
}
// Schedule wake-up
return s.scheduler.Schedule(func() {
s.moveMail(msgID, mailbox)
}, until)
}
VIP Contacts
Priority handling for important senders:
// @filename: main.go
// internal/features/vip.go
func (v *VIPManager) IsVIP(user, sender string) bool {
// Check VIP list
return v.store.Has(user, sender)
}
Security Considerations
Encryption
- In transit: TLS enforced via ACME (Let’s Encrypt)
- At rest: Unencrypted in Maildir (standard)
- Recommendation: Use full-disk encryption (LUKS, FileVault)
Password Security
// @filename: handlers.go
// internal/auth/password.go
func HashPassword(password string) (string, error) {
// Argon2id with OWASP parameters
params := argon2.DefaultParams()
params.Time = 3
params.Memory = 64 * 1024 // 64 MB
params.Threads = 4
params.KeyLen = 32
params.SaltLen = 16
return argon2.CreateHash(password, params)
}
Rate Limiting
Per-user rate limits with automatic cleanup:
// @filename: main.go
// internal/security/ratelimit.go
func (r *RateLimiter) Check(userID, action string) (bool, time.Duration) {
key := fmt.Sprintf("%s:%s", userID, action)
count, err := r.redis.Get(key)
if err != nil && err != redis.Nil {
return false, 0
}
if count >= r.limits[action] {
// Rate limited - return reset time
ttl, _ := r.redis.TTL(key)
return false, ttl
}
// Increment counter
r.redis.Incr(key)
r.redis.Expire(key, r.window)
return true, 0
}
Firewall Recommendations
# @filename: script.sh
# Only open required ports
sudo ufw allow 25/tcp # SMTP
sudo ufw allow 587/tcp # Submission
sudo ufw allow 465/tcp # SMTPS
sudo ufw allow 143/tcp # IMAP
sudo ufw allow 993/tcp # IMAPS
sudo ufw allow 8443/tcp # CalDAV/CardDAV
# Restrict admin panel to localhost or VPN
sudo ufw deny 8080/tcp
Troubleshooting
Preflight Checks
./mailserver preflight
This checks:
- Port availability
- DNS configuration
- Redis connectivity
- Directory permissions
- TLS certificate status
Doctor Command
./mailserver doctor
Diagnoses common issues:
- Service connectivity
- DNS record validation
- Deliverability testing
- Storage health
Common Issues
Connection refused:
sudo ss -tlnp | grep mailserver
sudo ufw status
TLS certificate issues:
ls -la /var/lib/mailserver/acme/
openssl s_client -connect mail.yourdomain.com:993 -servername mail.yourdomain.com
Email not delivered:
# @filename: script.sh
dig MX yourdomain.com
dig TXT yourdomain.com
dig TXT mail._domainkey.yourdomain.com
dig TXT _dmarc.yourdomain.com
Conclusion
Building a self-hosted email server in 2026 is not only feasible—it’s practical. With 87 commits over 22 days, we achieved:
- 95% cost savings compared to Google Workspace
- 9.4 MB memory footprint running at 10-15% capacity
- Full standards compliance with IMAP/SMTP/CalDAV/CardDAV
- Production-grade reliability with circuit breakers and retry logic
- Unique features like Screener, Send Later, and Email Aliases
The architecture leverages Go’s strengths: concurrency, static binaries, and excellent libraries. The tech stack—Go, SQLite, Redis, Maildir—provides simplicity without sacrificing power.
For individuals and small teams seeking email independence, self-hosting is no longer a complex endeavor. It’s a viable, cost-effective alternative that puts you in complete control of your communications.
