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:

  1. CDK8S Configuration (dp-infra/mailu/config.yaml) - Deployment-level settings

  2. Environment Variables - Runtime configuration via ConfigMap and Secrets

  3. 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 memory limits if pods are OOMKilled

  • Increase cpu requests if experiencing slow performance

  • See 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 security

  • Set to true ONLY if you need to receive email from external mail servers (MX delivery)

  • REQUIRES postfix-master-override ConfigMap for PROXY protocol

  • See: Enable SMTP Port 25 Securely

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 traffic

  • lb21 - €14.17/month, 20TB traffic, higher performance

  • lb31 - €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 needed

  • front: Can scale with LoadBalancer distribution

  • Storage: PVCs are ReadWriteOnce (single pod access)

Environment Variables

These are set in the generated ConfigMap and Secrets:

Core Settings

Variable

Value

Description

SECRET_KEY

(secret)

Encryption key for cookies/sessions

DOMAIN

kup6s.com

Primary mail domain

HOSTNAMES

mail.kup6s.com

Public hostname

SUBNET

10.42.0.0/16

Kubernetes pod network CIDR

Database Settings

Variable

Value

Description

DB_FLAVOR

postgresql

Database type

DB_HOST

mailu-postgres-pooler

PostgreSQL service

DB_PORT

5432

PostgreSQL port

DB_NAME

mailu

Database name

DB_USER

mailu

Database user

DB_PW

(secret)

Database password

Mail Protocol Settings

Variable

Value

Description

MESSAGE_SIZE_LIMIT

50000000

Max email size (50MB)

RELAYNETS

(empty)

Trusted networks for relay

RELAYHOST

(empty)

External SMTP relay

FETCHMAIL_DELAY

600

Fetchmail poll interval (seconds)

RECIPIENT_DELIMITER

+

Plus addressing delimiter

TLS Settings

Variable

Value

Description

TLS_FLAVOR

traefik

TLS termination mode

INBOUND_TLS_ENFORCE

false

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

RELAYNETS

(empty)

Trusted networks for relay (empty = no external relaying)

AUTH_RATELIMIT_IP

60/hour

Max failed auth per IP

AUTH_RATELIMIT_USER

100/day

Max failed auth per user

MESSAGE_RATELIMIT

200/day

Max messages per user per day

DISABLE_STATISTICS

false

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:

  1. Return “bad syntax” errors (PROXY protocol sent but not accepted)

  2. Allow unauthorized relay (if fallback to no PROXY protocol)

See: Enable SMTP Port 25 Securely for complete guide

Webmail Settings

Variable

Value

Description

WEBMAIL

roundcube

Webmail interface

WEB_ADMIN

/admin

Admin UI path

WEB_WEBMAIL

/

Webmail path

Antispam Settings (Rspamd)

Variable

Value

Description

ANTIVIRUS

none

Antivirus engine (ClamAV not enabled)

ANTISPAM_MILTER_ADDRESS

mailu-antispam-service:11332

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:

  1. Login to admin UI

  2. Navigate to MailUsers

  3. Click Create User

  4. Set email, password, quota, and features

Set User Quota:

  1. Edit user

  2. Set Quota (in bytes, e.g., 1073741824 for 1GB)

  3. Save

Enable/Disable Features:

  • IMAP access - Allow IMAP retrieval

  • POP3 access - Allow POP3 retrieval

  • Global admin - Grant admin UI access

Domain Management

Add Domain:

  1. Navigate to MailDomains

  2. Click Create Domain

  3. Enter domain name (e.g., example.com)

  4. 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:

  1. Navigate to MailAliases

  2. Click Create Alias

  3. Enter alias email (e.g., info@kup6s.com)

  4. Select destination user(s)

Wildcard Aliases:

  • Use *@kup6s.com to catch all emails to domain

  • Forward to a single user or discard

Relay Domains

For routing mail to external servers:

  1. Navigate to MailRelays

  2. Click Create Relay

  3. 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:

  1. Navigate to MailDomainsRegenerate keys

  2. Copy DNS record provided

  3. 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 fails

  • p=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):

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/metrics

  • Metrics: 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)

See Traefik TLS Configuration.

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: