Mailu Configuration Reference¶
This document describes the server-side configuration options for the Mailu deployment at mail.kup6s.com.
Overview¶
Mailu configuration is managed through:
CDK8S Configuration (dp-infra/mailu/config.yaml) - Deployment-level settings
Environment Variables - Runtime configuration via ConfigMap and Secrets
Admin UI - Web-based user/domain management at https://mail.kup6s.com/admin
CDK8S Configuration File¶
Location: dp-infra/mailu/config.yaml
This YAML file controls the CDK8S chart that generates Kubernetes manifests.
Basic Settings¶
name: mailu # Kubernetes resource name prefix
namespace: mailu # Kubernetes namespace
domain: kup6s.com # Primary mail domain
hostname: mail.kup6s.com # Public hostname for mail services
Image Configuration¶
images:
registry: ghcr.io/mailu # Container registry
tag: '2024.06' # Mailu version
Available versions: See Mailu releases
TLS Configuration¶
tls:
flavor: traefik # TLS termination mode
certSecretName: mail-kup6s-com-tls # cert-manager certificate secret
TLS Flavor Options:
traefik- External TLS termination (current setup)cert- Mailu manages certificates (not used)notls- No TLS (not recommended)letsencrypt- Mailu requests Let’s Encrypt (not compatible with Traefik)
Note: traefik flavor uses nginx wrapper script to enable mail port listeners. See Traefik TLS Termination Pattern.
Database Configuration¶
database:
type: postgresql # Database backend (postgresql or sqlite)
host: mailu-postgres-pooler # PostgreSQL service name
port: 5432 # PostgreSQL port
name: mailu # Database name
user: mailu # Database user
existingSecret: mailu-postgres-app # Secret with password
passwordKey: password # Key in secret
Database Backends:
postgresql- Recommended for production (current setup with CNPG)sqlite- Simple but not recommended for production
Secrets¶
secrets:
mailuSecretKey: mailu-secret-key # Secret for SECRET_KEY env var
databasePassword: mailu-postgres-app # PostgreSQL credentials
Creating Secrets:
# Generate SECRET_KEY (required for encryption)
SECRET_KEY=$(openssl rand -base64 32)
# Create secret
kubectl create secret generic mailu-secret-key \
-n mailu \
--from-literal=secret-key="$SECRET_KEY"
Storage Configuration¶
storage:
storageClass: longhorn # Storage class for PVCs
dataSize: 20Gi # Mail data volume size
redisSize: 1Gi # Redis cache volume size
Storage Classes (see Storage Tiers):
longhorn- Default (2 replicas)longhorn-redundant-app- Single replica (not recommended for mail data)longhorn-ha- High availability (3 replicas)
Sizing Recommendations:
dataSize: Allocate based on expected mailbox usage (20Gi handles ~40 users with 500MB each)redisSize: 1Gi sufficient for most deployments
Resource Limits¶
resources:
front:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
admin:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# ... similar for imap, smtp, antispam, redis, webmail
Resource Tuning:
Increase
memorylimits if pods are OOMKilledIncrease
cpurequests if experiencing slow performanceSee actual usage:
kubectl top pods -n mailu
Ingress Configuration¶
ingress:
enabled: true # Enable Traefik ingress resources
type: "traefik" # Ingress controller type
traefik:
hostname: "mail.kup6s.com" # Public hostname
certIssuer: "letsencrypt-cluster-issuer" # cert-manager issuer
enableTcp: true # Enable TCP routes (SMTP, IMAP, POP3)
smtpConnectionLimit: 15 # Max concurrent SMTP connections per IP
enableSmtp: false # Enable SMTP port 25 (default: false)
CRITICAL - enableSmtp Security:
Default:
false- Port 25 blocked for securitySet to
trueONLY if you need to receive email from external mail servers (MX delivery)REQUIRES
postfix-master-overrideConfigMap for PROXY protocol
Without PROXY protocol, enabling enableSmtp: true creates an open relay vulnerability!
Ingress Options:
enableTcp: true- Creates IngressRouteTCP for mail protocols (SMTPS:465, Submission:587, IMAPS:993, POP3S:995)smtpConnectionLimit: 15- Traefik rate limit (max concurrent connections per source IP)enableSmtp: false- Security best practice (only enable if needed)
LoadBalancer Configuration¶
loadbalancer:
enabled: true # Enable Hetzner LoadBalancer
annotations:
load-balancer.hetzner.cloud/name: mailu-lb
load-balancer.hetzner.cloud/location: fsn1
load-balancer.hetzner.cloud/type: lb11
LoadBalancer Types (Hetzner):
lb11- €5.83/month, 20TB trafficlb21- €14.17/month, 20TB traffic, higher performancelb31- €29.17/month, 20TB traffic, highest performance
Replicas¶
replicas:
front: 1 # Nginx frontend
admin: 1 # Admin UI
imap: 1 # Dovecot IMAP server
smtp: 1 # Postfix SMTP server
antispam: 1 # Rspamd antispam
redis: 1 # Redis cache (not clustered)
webmail: 1 # Roundcube webmail
Scaling Considerations:
redis: Not designed for multiple replicas (shared cache)smtp,imap: Can scale to multiple replicas if neededfront: Can scale with LoadBalancer distributionStorage: PVCs are ReadWriteOnce (single pod access)
Environment Variables¶
These are set in the generated ConfigMap and Secrets:
Core Settings¶
Variable |
Value |
Description |
|---|---|---|
|
(secret) |
Encryption key for cookies/sessions |
|
|
Primary mail domain |
|
|
Public hostname |
|
|
Kubernetes pod network CIDR |
Database Settings¶
Variable |
Value |
Description |
|---|---|---|
|
|
Database type |
|
|
PostgreSQL service |
|
|
PostgreSQL port |
|
|
Database name |
|
|
Database user |
|
(secret) |
Database password |
Mail Protocol Settings¶
Variable |
Value |
Description |
|---|---|---|
|
|
Max email size (50MB) |
|
(empty) |
Trusted networks for relay |
|
(empty) |
External SMTP relay |
|
|
Fetchmail poll interval (seconds) |
|
|
Plus addressing delimiter |
TLS Settings¶
Variable |
Value |
Description |
|---|---|---|
|
|
TLS termination mode |
|
|
Require TLS for inbound SMTP (port 25) |
Note: INBOUND_TLS_ENFORCE=false allows external servers to deliver mail via port 25 without TLS (standard practice).
Security Settings¶
Variable |
Value |
Description |
|---|---|---|
|
(empty) |
Trusted networks for relay (empty = no external relaying) |
|
|
Max failed auth per IP |
|
|
Max failed auth per user |
|
|
Max messages per user per day |
|
|
Send anonymous usage stats |
Critical - Relay Security:
RELAYNETS=""- Prevents unauthorized SMTP relay (configured in cdk8s-mailu)Only authenticated users can send mail via ports 465/587
Port 25 accepts mail ONLY for local domains (@kup6s.com)
PROXY protocol required for relay restrictions to work (see below)
SMTP PROXY Protocol (Port 25 Security)¶
CRITICAL - Required ConfigMap:
If enableSmtp: true in config.yaml, you MUST create the postfix-master-override ConfigMap:
# Create postfix.master override file
cat > /tmp/postfix.master <<'EOF'
smtp/inet=smtp inet n - n - - smtpd -o smtpd_upstream_proxy_protocol=haproxy
EOF
# Create ConfigMap
kubectl create configmap postfix-master-override \
-n mailu \
--from-file=postfix.master=/tmp/postfix.master
What this does:
Configures Postfix to accept PROXY protocol v2 from Traefik
Preserves real client IP addresses (not Traefik pod IP)
Enables relay restrictions to work correctly
Prevents open relay vulnerability
Without this ConfigMap: Port 25 will either:
Return “bad syntax” errors (PROXY protocol sent but not accepted)
Allow unauthorized relay (if fallback to no PROXY protocol)
See: Enable SMTP Port 25 Securely for complete guide
Webmail Settings¶
Variable |
Value |
Description |
|---|---|---|
|
|
Webmail interface |
|
|
Admin UI path |
|
|
Webmail path |
Antispam Settings (Rspamd)¶
Variable |
Value |
Description |
|---|---|---|
|
|
Antivirus engine (ClamAV not enabled) |
|
|
Rspamd milter |
Applying Configuration Changes¶
1. Update CDK8S Configuration¶
cd dp-infra/mailu
# Edit config.yaml
vim config.yaml
# Regenerate manifests
npm run build
# Review changes
git diff manifests/mailu.k8s.yaml
# Commit and push
git add config.yaml manifests/
git commit -m "Update Mailu configuration"
git push
2. Sync with ArgoCD¶
Automatic sync (if enabled):
# ArgoCD detects git changes and syncs automatically
argocd app get mailu
Manual sync:
argocd app sync mailu
3. Restart Pods (if needed)¶
Some changes require pod restart:
# Restart all Mailu pods
kubectl rollout restart deployment -n mailu
# Or restart specific component
kubectl rollout restart deployment -n mailu mailu-front-deployment-c8...
4. Verify Changes¶
# Check pod status
kubectl get pods -n mailu
# Check updated environment variables
kubectl exec -n mailu deploy/mailu-admin-deployment-c8... -- env | grep DOMAIN
# Check logs for errors
kubectl logs -n mailu -l 'app.kubernetes.io/component=admin' --tail=50
Admin UI Configuration¶
Web-based configuration at https://mail.kup6s.com/admin
User Management¶
Create User:
Login to admin UI
Navigate to Mail → Users
Click Create User
Set email, password, quota, and features
Set User Quota:
Edit user
Set Quota (in bytes, e.g., 1073741824 for 1GB)
Save
Enable/Disable Features:
IMAP access - Allow IMAP retrieval
POP3 access - Allow POP3 retrieval
Global admin - Grant admin UI access
Domain Management¶
Add Domain:
Navigate to Mail → Domains
Click Create Domain
Enter domain name (e.g.,
example.com)Set max users, max aliases, max quota
Configure Domain Settings:
Max users: Limit number of users in domain
Max aliases: Limit number of aliases
Max quota: Total storage limit for domain
Alias Management¶
Create Alias:
Navigate to Mail → Aliases
Click Create Alias
Enter alias email (e.g.,
info@kup6s.com)Select destination user(s)
Wildcard Aliases:
Use
*@kup6s.comto catch all emails to domainForward to a single user or discard
Relay Domains¶
For routing mail to external servers:
Navigate to Mail → Relays
Click Create Relay
Enter relay domain and remote SMTP host
DNS Configuration¶
Required DNS records for mail delivery:
MX Record (Mail Delivery)¶
kup6s.com. IN MX 10 mail.kup6s.com.
A/AAAA Records (Hostname Resolution)¶
mail.kup6s.com. IN A 167.233.14.203
mail.kup6s.com. IN AAAA 2a01:4f8:1c1f:6562::1
SPF Record (Sender Policy Framework)¶
kup6s.com. IN TXT "v=spf1 mx ~all"
Explanation:
mx- Allow servers listed in MX records to send mail~all- Soft fail for others (not recommended to reject)
DKIM Record (DomainKeys Identified Mail)¶
Generate DKIM key in Mailu admin UI:
Navigate to Mail → Domains → Regenerate keys
Copy DNS record provided
Add to DNS:
dkim._domainkey.kup6s.com. IN TXT "v=DKIM1; k=rsa; p=MIIBI..."
DMARC Record (Domain-based Message Authentication)¶
_dmarc.kup6s.com. IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@kup6s.com"
Policy Options:
p=none- Monitor only (recommended for testing)p=quarantine- Mark as spam if failsp=reject- Reject if fails (strictest)
Reverse DNS (PTR Record)¶
Configure via Hetzner Cloud:
167.233.14.203 → mail.kup6s.com
Required for mail delivery - many servers reject mail without valid PTR record.
Backup and Restore¶
Backup Configuration¶
Backups managed by CNPG (PostgreSQL) and Longhorn (mail data):
PostgreSQL Backup (CNPG ScheduledBackup):
# Check backup status
kubectl get backup -n mailu
# Manual backup
kubectl create -f - <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Backup
metadata:
name: mailu-postgres-manual-backup
namespace: mailu
spec:
cluster:
name: mailu-postgres
EOF
Mail Data Backup (Longhorn):
Automatic snapshots via Longhorn RecurringJob
Backed up to Hetzner Storage Box (CIFS)
Restore¶
Restore PostgreSQL:
# Restore from CNPG backup
kubectl apply -f - <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: mailu-postgres-restored
namespace: mailu
spec:
bootstrap:
recovery:
backup:
name: mailu-postgres-backup-timestamp
EOF
Restore Mail Data:
# Restore Longhorn volume from backup
# Use Longhorn UI: https://longhorn.ops.kup6s.net
# Select backup, click "Restore", create new volume
Monitoring¶
Metrics¶
Mailu components expose metrics for Prometheus scraping:
Admin container:
Endpoint:
http://mailu-admin-service:80/metricsMetrics: Request count, latency, user count
Postfix (SMTP):
Logs parsed by monitoring stack
Queue size, delivery status, errors
Logs¶
View component logs:
# All Mailu logs
kubectl logs -n mailu -l 'app.kubernetes.io/part-of=mailu' --tail=100
# Specific component
kubectl logs -n mailu -l 'app.kubernetes.io/component=smtp' --tail=50
Log aggregation: Logs collected by Loki, viewable in Grafana:
Grafana: https://grafana.ops.kup6s.net
Explore → Loki →
{namespace="mailu"}
Health Checks¶
# Check pod health
kubectl get pods -n mailu
# Check service endpoints
kubectl get endpoints -n mailu
# Test webmail health
curl -I https://mail.kup6s.com
Security Hardening¶
Rate Limiting¶
Configured via environment variables:
AUTH_RATELIMIT_IP: "10/hour" # Max 10 failed auth per IP per hour
AUTH_RATELIMIT_USER: "100/day" # Max 100 failed auth per user per day
Adjust in config.yaml and rebuild manifests.
TLS Security¶
Enforced TLS Settings (configured in Traefik IngressRouteTCP):
TLS 1.2 minimum
Modern cipher suites only
Perfect Forward Secrecy (PFS)
Network Policies¶
Recommended (not currently enforced):
# Allow only necessary pod-to-pod communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mailu-network-policy
namespace: mailu
spec:
podSelector:
matchLabels:
app.kubernetes.io/part-of: mailu
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: mailu
- podSelector: {}
egress:
- to:
- namespaceSelector:
matchLabels:
name: mailu
- podSelector: {}
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
Troubleshooting¶
For common issues and diagnostics:
Troubleshoot SMTP Errors - Connection, authentication, delivery issues
Configure Mail Client - Client setup instructions
Traefik TLS Termination Pattern - Architecture explanation