What Is an SMTP Relay Server?
An SMTP relay accepts outbound email from your applications and delivers it to the final destination mail servers (Gmail, Outlook, Yahoo, etc.). Unlike a local MTA that handles both inbound and outbound, a relay focuses purely on outbound delivery optimization.
Typical architecture:
Application → SMTP Relay → Internet → Recipient Mail Server
(KumoMTA) (TLS)
The relay handles:
- Message queuing and retry logic
- TLS encryption with certificate management
- Rate limiting and traffic shaping
- DKIM signing and SPF passthrough
- Bounce processing and delivery tracking
Self-Hosted vs Cloud SMTP Relay
| Factor | Self-Hosted (KumoMTA) | Cloud SMTP (SendGrid, Mailgun) |
|---|---|---|
| Control | Full (server, config, logs) | Limited (API only) |
| Cost at 10M/month | ~$1,500 (infra) | ~$1,200 (paid plan) |
| Cost at 100M/month | ~$5,000 (infra) | ~$15,000+ |
| Throughput ceiling | Unlimited (scale infra) | Shared, tier-limited |
| Compliance | Full GDPR/CAN-SPAM control | Shared responsibility |
| Setup complexity | Medium | Low |
| AI optimization | Yes (KumoMTA native) | Limited |
| Custom bounce processing | Full Lua control | API/webhook only |
Choose self-hosted if: You send > 5M emails/month, have engineering capacity, need full compliance control, or want to eliminate per-email pricing.
Choose cloud SMTP if: You're under 1M emails/month, have no infra engineering, or need rapid setup without infrastructure management.
KumoMTA as SMTP Relay: Production Configuration
Core Relay Configuration
-- /etc/kumomta/relay.conf
-- Production SMTP relay configuration
-- SMTP Listener (accepts from internal apps)
kumo.start_smtp_listener {
listen = "[::]:2525", -- Internal relay port
name = "relay-in",
relay_hosts = { "10.0.0.0/8", "172.16.0.0/12" }, -- Internal networks only
auth_require_tls = true, -- Require AUTH from application servers
}
-- HTTP API for application injection
kumo.start_http_listener {
listen = "[::]:8080",
trusted_hosts = { "10.0.0.0/8", "127.0.0.1" },
}
-- DKIM signing for all outbound
kumo.configure_dkim_signing {
domain = "example.com",
selector = "mail",
key_file = "/etc/kumomta/keys/mail._domainkey.example.com.pem",
headers = { "From", "To", "Subject", "Date", "Message-ID" },
}
-- TLS for outbound delivery
kumo.configure_tls {
min_tls_version = "1.3",
ciphers = "ECDHE-RSA-AES256-GCM-SHA384",
}
-- Prometheus metrics
kumo.start_http_listener {
listen = "[::]:2000",
trusted_hosts = { "127.0.0.1" },
}
Application Integration via HTTP API
Applications inject mail via KumoMTA's HTTP API:
curl -X POST http://kumomta-relay:8080/v1/message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"from": "orders@example.com",
"to": ["customer@gmail.com", "customer2@yahoo.com"],
"subject": "Your Order #12345 Has Shipped",
"headers": {
"X-Order-ID": "12345",
"X-Campaign": "shipping-confirmation"
},
"html": "<h1>Your order is on its way!</h1>",
"text": "Your order #12345 has shipped. Track at: https://track.example.com/12345"
}'
Multi-Tenant Relay Configuration
For SaaS platforms sending on behalf of customers:
-- Per-customer relay configuration
kumo.on("smtp_server_greeting", function(domain, meta)
local tenant = meta.tenant
if tenant then
-- Apply per-tenant rate limits
local limit = get_tenant_rate_limit(tenant)
kumo.limit_sending(tenant, limit, { per = "minute" })
-- Set tenant-specific DKIM
local dkim = get_tenant_dkim(tenant)
if dkim then
kumo.sign_dkim(dkim.domain, dkim.selector, dkim.key_file)
end
end
end)
Security: TLS, AUTH, and IP Allowlisting
TLS Configuration (Required for 2026)
-- Enforce TLS 1.3 for all delivery
kumo.configure_tls {
min_tls_version = "1.3",
prefer_server_ciphers = true,
}
-- For submission ports (SMTPS)
kumo.start_smtp_listener {
listen = "[::]:465",
name = "smtps",
tls = {
cert_file = "/etc/kumomta/tls/server.crt",
key_file = "/etc/kumomta/tls/server.key",
},
}
Generate self-signed certificates for testing:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=mail.example.com"
SMTP AUTH Configuration
-- Require AUTH for submission port
kumo.start_smtp_listener {
listen = "[::]:587",
name = "submission",
auth_require_tls = true,
-- Supported mechanisms
auth_mechanisms = { "PLAIN", "LOGIN", "CRAM-MD5" },
}
Application authentication (for HTTP API):
-- API key validation
kumo.on("http_request", function(request)
local token = request.headers["Authorization"]:gsub("Bearer ", "")
if not validate_api_key(token) then
return { status = 401, body = "Unauthorized" }
end
end)
IP Allowlisting
-- Only accept connections from trusted IPs
kumo.start_smtp_listener {
listen = "[::]:2525",
name = "relay-in",
relay_hosts = { "10.0.0.0/8", "172.16.0.0/12" },
-- Deny all other IPs
}
For additional security with cloud applications:
# Firewall: only allow specific application server IPs
sudo ufw allow from 10.0.1.0/24 to any port 2525
sudo ufw allow from 10.0.2.0/24 to any port 2525
Performance Tuning for High-Volume Relay
Connection and Thread Tuning
-- /etc/kumomta/tuning.conf
-- Delivery threads (scale with CPU cores)
kumo.configure_delivery {
threads = 16, -- 2x CPU cores
max_connections_per_domain = 10,
max_message_rate = 1000, -- per minute per domain
}
-- Queue management
kumo.configure_queue {
max_queue_depth = 100000,
retry_interval = "5m",
max_retry_age = "72h",
dead_letter_after = "7d",
}
Monitoring Metrics
Key metrics to track:
| Metric | Target | Alert |
|---|---|---|
kumomta_queue_depth |
< 10,000 | > 50,000 |
kumomta_delivery_latency_p95 |
< 30s | > 60s |
kumomta_smtp_errors_total |
< 0.1% | > 1% |
kumomta_tls_failures_total |
< 0.5% | > 2% |
| Connections per domain | < 10 | > 20 |
SMTP Relay for Specific Use Cases
Transactional Email (Order Confirmations, Password Resets)
Requirements:
- Immediate delivery (< 30 seconds end-to-end)
- High reliability (99.9% success rate)
- DKIM signing required
- Low volume per user, high total volume
-- Transactional: low latency priority
kumo.on("smtp_message_received", function(domain, meta)
local headers = meta.headers or {}
if headers["X-Type"] == "transactional" then
-- Elevate priority
kumo.set_queue_priority("high")
end
end)
Bulk/Marketing Email
Requirements:
- High throughput (100K+ emails/hour)
- Per-campaign tracking
- Bounce processing integration
- List unsubscribe handling (RFC 8058)
-- Bulk: throughput priority
kumo.on("smtp_message_received", function(domain, meta)
local headers = meta.headers or {}
local campaign = headers["X-Campaign"]
if campaign then
-- Track campaign metrics
increment_campaign_metric(campaign, "queued")
end
end)
-- List-Unsubscribe header (RFC 8058 - required for bulk)
kumo.on("smtp_message_received", function(domain, meta)
if is_bulk_campaign(meta.headers) then
kumo.add_header("List-Unsubscribe", "<mailto:unsubscribe@example.com?subject=unsubscribe>")
kumo.add_header("List-Unsubscribe-Post", "List-Unsubscribe=One-Click")
end
end)
Troubleshooting Common SMTP Relay Issues
Issue: Connection Timeout / Slow Delivery
Cause: Network latency, remote MX throttling, or TLS negotiation failure
Fix:
# Test SMTP connectivity
telnet gmail-smtp-in.l.google.com 25
# Check TLS
openssl s_client -connect gmail-smtp-in.l.google.com:25 -starttls smtp
# Check KumoMTA queue
curl http://localhost:2000/metrics | grep queue_depth
Issue: 421/429 Throttling from Gmail/MS
Cause: ISP is throttling your IP — you're sending too fast
Fix: Implement adaptive throttling in your relay policy:
kumo.on("smtp_delivery_result", function(result, meta)
if result.code == 421 or result.code == 429 then
-- Reduce rate by 30%
adjust_domain_rate(meta.domain, -0.3)
log_warn("Throttled by " .. meta.domain .. ", reducing rate")
end
end)
Issue: TLS Handshake Failures
Cause: Outdated TLS config or untrusted certificate
Fix: Ensure min_tls_version = "1.2" minimum (1.3 preferred). Check certificate expiration:
openssl x509 -in /etc/kumomta/tls/server.crt -noout -dates
FAQ
Q: What's the difference between SMTP port 25, 465, and 587?
A: Port 25 is the standard MX port (server-to-server delivery). Port 465 (SMTPS) is for legacy submission with implicit TLS. Port 587 (Submission) is the modern standard for application-to-relay submission with explicit STARTTLS. Use 587 for your application relay integration.
Q: Can KumoMTA relay from Postfix?
A: Yes. Configure Postfix to relay to KumoMTA on an internal port:
relayhost = [kumomta.internal]:2525
Q: How do I handle SMTP relay for WordPress/WooCommerce?
A: Install WP Mail SMTP plugin, configure it to point to your KumoMTA relay at kumomta.example.com:587 with AUTH credentials. KumoMTA will DKIM-sign and deliver all WordPress mail.
Q: Is port 25 blocked by cloud providers — what do I do?
A: Most cloud providers (AWS, GCP, Azure) block port 25 by default. Solutions: (1) Use port 587 submission instead, (2) request port 25 unblock from your provider, (3) use a dedicated SMTP relay service for the "last mile" if needed.
Q: How does KumoMTA compare to Postfix for relay use?
A: KumoMTA has superior multi-tenant traffic shaping, built-in DKIM, and Prometheus metrics. Postfix requires external tools (OpenDKIM, policyd) for equivalent functionality. For high-volume relay, KumoMTA is significantly easier to operate.
Get Help With SMTP Relay Setup
PostMTA provides:
- Production KumoMTA relay configuration and deployment
- Secure TLS and AUTH setup
- Multi-tenant relay for SaaS platforms
- Integration with existing applications (WordPress, Shopify, custom)
- 24/7 relay monitoring and alerting
For related guides, see KumoMTA Setup Guide, Email Authentication Guide, and Open Source Email Infrastructure.
References: RFC 5321 (SMTP) | RFC 8460 (SMTP TLS Reporting)
Top comments (0)