A full-featured SMTP server with multi-user support, local delivery, and authenticated relay.
-
Dual-Port Operation
- Port 25 (SMTP): Receives mail from other servers (no authentication required)
- Port 587 (Submission): Accepts mail from clients (requires authentication)
Note that 25 is the no auth service and could be configured to run on any port such as 2025. Same goes for 587, e.g. 2587. Why? because if you run this at home on a DMZ, your internet provider probably blocks 25, but they can't block all the ports or Alexa would not be able to keep tabs on you. ;-}
-
Multi-User Support
- Multiple mailboxes with individual authentication
- Secure password hashing with bcrypt
- Per-user email storage
-
Multi-Domain Support
- Host multiple domains on one server
- Configure which domains are local vs relayed
-
Smart Routing
- Local domains → Delivered to user mailboxes
- External domains → Relayed through upstream SMTP server
- Automatic routing based on recipient domain
-
Authenticated Relay
- Support for both Let's Encrypt and self-signed certificates
- STARTTLS support for secure connections
- Configurable TLS settings
-
Install dependencies:
go get github.com/joho/godotenv go get golang.org/x/crypto/bcrypt go get github.com/emersion/go-smtp go get modernc.org/sqlite
-
Create configuration:
cp .env.example .env # Edit .env with your settings
# Server Ports
SMTP_ADDR=0.0.0.0
SMTP_PORT=25 # Incoming mail from other servers
SUBMISSION_ADDR=0.0.0.0
SUBMISSION_PORT=587 # Outgoing mail from your clients
# Your domain
SMTP_DOMAIN=mail.example.com
# Database
DB_PATH=./stevemail.db
# Relay for external domains
RELAY_HOST=smtp.gmail.com
RELAY_PORT=587
RELAY_USERNAME=[email protected]
RELAY_PASSWORD=your-app-password
RELAY_USE_TLS=true
RELAY_USE_LETSENCRYPT=trueAdd a local domain:
go run cmd/mailctl/main.go domain:add example.com trueAdd users:
go run cmd/mailctl/main.go user:add [email protected] password123
go run cmd/mailctl/main.go user:add [email protected] password456List configuration:
go run cmd/mailctl/main.go domain:list
go run cmd/mailctl/main.go user:listgo run cmd/smtpserver/main.goThe server will listen on:
- Port 25 - For receiving mail from other mail servers (no auth)
- Port 587 - For sending mail from your email client (requires auth)
Configure your email client (Thunderbird, Outlook, etc.):
Outgoing Mail (SMTP):
- Server: localhost (or your server IP)
- Port: 587
- Security: STARTTLS (or None for testing)
- Authentication: Normal password
- Username: [email protected]
- Password: password123
-
User sends email via port 587:
- Client authenticates as
[email protected] - Email is accepted
- Client authenticates as
-
Server routes the email:
- To
[email protected]→ Local delivery to Bob's mailbox - To
[email protected]→ Relayed through configured SMTP server
- To
-
Other servers send to port 25:
- No authentication required
- Email delivered to local mailboxes if domain exists
- Otherwise rejected or relayed (based on configuration)
┌─────────────────────┐
│ Email Clients │
│ (Thunderbird, etc) │
└──────────┬──────────┘
│
Port 587 │ (Auth Required)
↓
┌─────────────────────┐
│ SteveMail Server │
│ │
│ ┌───────────────┐ │
│ │ Port 25 (SMTP)│ │←── Other Mail Servers
│ │ No Auth │ │ (No Auth)
│ └───────────────┘ │
│ │
│ ┌───────────────┐ │
│ │Port 587 (Sub) │ │
│ │ Auth Required│ │
│ └───────────────┘ │
│ │
│ ┌───────────────┐ │
│ │ Local DB │ │
│ │ (Mailboxes) │ │
│ └───────────────┘ │
└──────────┬──────────┘
│
↓
┌─────────────────────┐
│ Upstream SMTP │
│ (Gmail, etc) │
│ For External Mail │
└─────────────────────┘
Users Table:
- Email, username, domain, password_hash
- Each user has their own mailbox
Domains Table:
- Domain name, is_local flag
- Determines routing (local vs relay)
Emails Table:
- Email content, headers, raw data
- Linked to mailbox_id for per-user storage
For Production:
- Use TLS certificates (Let's Encrypt)
- Enable STARTTLS on port 587
- Consider SPF, DKIM, and DMARC records
- Firewall port 25 from untrusted sources
- Use strong passwords for users
- Regular database backups
For Testing:
- Ports 2525/2587 don't require admin/root
- Can run without TLS for local testing
Port 25 requires root/admin:
# Option 1: Use higher ports for testing
SMTP_PORT=2525
SUBMISSION_PORT=2587
# Option 2: Run as admin/root (production)
sudo go run cmd/smtpserver/main.goAuthentication failing:
- Check user exists:
go run cmd/mailctl/main.go user:list - Verify password is correct
- Check client is connecting to port 587
Mail not being relayed:
- Verify RELAY_HOST is set in .env
- Check relay credentials are correct
- Test relay server is accessible
telnet localhost 587
EHLO localhost
AUTH PLAIN
# Base64 encoded: \[email protected]\0password123
AGFsaWNlQGV4YW1wbGUuY29tAHBhc3N3b3JkMTIz
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Subject: Test
From: [email protected]
To: [email protected]
Test message body.
.
QUITsqlite3 stevemail.db "SELECT id, from_address, to_addresses, subject, received_at FROM emails ORDER BY received_at DESC LIMIT 10;"Project Structure:
stevemail/
├── cmd/
│ ├── smtpserver/ # Main SMTP server
│ └── mailctl/ # Management CLI
├── internal/
│ ├── config/ # Configuration
│ ├── models/ # Data models
│ ├── smtp/ # SMTP implementation
│ └── storage/ # Database layer
└── .env # Configuration file
CC BY-NC-ND License SEE LICENSE FILE