# Lightweight Mail Stack for k3s GitOps Lab Complete deployment of **Stalwart Mail Server** + **SnappyMail** webmail using GitOps with ArgoCD. ## 📋 Overview This repository deploys a lightweight, modern mail stack designed for self-hosting and lab environments: - **Stalwart Mail Server**: All-in-one mail server (SMTP, IMAP, Admin UI) - **SnappyMail**: Modern, lightweight webmail client - **GitOps**: Managed by ArgoCD - **Storage**: NFS-based persistent volumes - **Ingress**: Traefik for web UI access ## 🏗️ Architecture ``` ┌─────────────────────────────────────────────────┐ │ Traefik Ingress │ │ mail.dvirlabs.com webmail.dvirlabs.com │ └───────────┬──────────────────────┬──────────────┘ │ │ │ │ ┌───────▼────────┐ ┌────────▼────────┐ │ Stalwart │◄───│ SnappyMail │ │ Mail Server │ │ Webmail │ │ │ │ │ │ • SMTP │ │ Connects via: │ │ • IMAP │ │ • IMAP:993 │ │ • Admin UI │ │ • SMTP:587 │ └────────┬───────┘ └─────────────────┘ │ │ ┌────────▼───────┐ │ NFS Storage │ │ Mail Data │ └────────────────┘ ``` ## 📁 Repository Structure ``` mail-services/ ├── argocd-apps/ │ ├── stalwart.yaml # ArgoCD Application for Stalwart │ └── snappymail.yaml # ArgoCD Application for SnappyMail ├── charts/ │ ├── stalwart/ # Local Helm chart for Stalwart │ │ ├── Chart.yaml │ │ ├── values.yaml # Default values │ │ └── templates/ │ │ ├── namespace.yaml │ │ ├── secret.yaml │ │ ├── statefulset.yaml │ │ ├── service.yaml │ │ └── ingress.yaml │ └── snappymail/ # Local Helm chart for SnappyMail │ ├── Chart.yaml │ ├── values.yaml # Default values │ └── templates/ │ ├── deployment.yaml │ ├── pvc.yaml │ ├── service.yaml │ ├── ingress.yaml │ └── configmap.yaml └── manifests/ ├── stalwart/ │ └── values.yaml # Custom values for dvirlabs.com └── snappymail/ └── values.yaml # Custom values for dvirlabs.com ``` ## 🚀 Quick Start ### Prerequisites - k3s cluster running - ArgoCD installed and configured - Traefik ingress controller - NFS storage class (`nfs-client`) - DNS records pointing to your cluster ### Step 1: Update Configuration 1. **Update ArgoCD Application manifests** with your Git repository URL: ```bash # Edit both files and replace YOUR_USERNAME with your actual repo vim argocd-apps/stalwart.yaml vim argocd-apps/snappymail.yaml ``` 2. **Change the Stalwart admin password**: ```bash # Edit and set a strong password vim manifests/stalwart/values.yaml ``` Find this section and change `CHANGE_ME_PLEASE_USE_STRONG_PASSWORD`: ```yaml secret: create: true name: stalwart-credentials adminPassword: "YOUR_STRONG_PASSWORD_HERE" ``` 3. **Update domain names** (if not using dvirlabs.com): ```bash # Update in both files vim manifests/stalwart/values.yaml vim manifests/snappymail/values.yaml ``` ### Step 2: Deploy with ArgoCD ```bash # Apply ArgoCD Applications kubectl apply -f argocd-apps/stalwart.yaml kubectl apply -f argocd-apps/snappymail.yaml # Check deployment status kubectl get applications -n argocd # Watch pods come up kubectl get pods -n mail -w ``` ### Step 3: Verify Deployment ```bash # Check all resources in mail namespace kubectl get all -n mail # Check PVCs kubectl get pvc -n mail # Check ingresses kubectl get ingress -n mail ``` Expected output: ``` NAME READY STATUS RESTARTS AGE pod/stalwart-0 1/1 Running 0 2m pod/snappymail-xxx-xxx 1/1 Running 0 2m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) service/stalwart ClusterIP 10.43.x.x 8080/TCP,25/TCP,587/TCP,993/TCP service/snappymail ClusterIP 10.43.x.x 8888/TCP NAME CLASS HOSTS ingress.networking.k8s.io/stalwart traefik mail.dvirlabs.com ingress.networking.k8s.io/snappymail traefik webmail.dvirlabs.com ``` ## 🌐 Access the Services ### Stalwart Admin UI URL: `https://mail.dvirlabs.com` Default credentials: - Username: `admin@dvirlabs.com` - Password: (the one you set in manifests/stalwart/values.yaml) ### SnappyMail Webmail URL: `https://webmail.dvirlabs.com` First-time setup: 1. Access the admin panel: `https://webmail.dvirlabs.com/?admin` 2. Default admin password: `12345` (change immediately!) 3. Configure mail server connection: - **IMAP Server**: `stalwart.mail.svc.cluster.local` - **IMAP Port**: `993` - **IMAP Security**: SSL/TLS - **SMTP Server**: `stalwart.mail.svc.cluster.local` - **SMTP Port**: `587` - **SMTP Security**: STARTTLS ## 📧 Configuring Real Mail Service ### Important: Cloudflare Tunnel Limitations ⚠️ **WARNING**: While Cloudflare Tunnel works fine for web UIs (admin panel and webmail), it **CANNOT** be used for actual email protocols (SMTP/IMAP). **What works through Cloudflare Tunnel:** - ✅ Stalwart admin UI (HTTPS) - ✅ SnappyMail webmail (HTTPS) **What does NOT work through Cloudflare Tunnel:** - ❌ Receiving mail from other servers (SMTP port 25) - ❌ Sending mail to other servers (SMTP port 25) - ❌ External email clients (IMAP/SMTP) ### Required for Real Email To receive and send real email, you need: #### 1. DNS Records ```dns ; MX Record (Mail Exchange) @ IN MX 10 mail.dvirlabs.com. ; A Record (pointing to your public IP - NOT Cloudflare Tunnel) mail IN A YOUR_PUBLIC_IP ; SPF Record (Sender Policy Framework) @ IN TXT "v=spf1 mx ~all" ; DMARC Record _dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:admin@dvirlabs.com" ; DKIM Record (generated by Stalwart) ; Get this from Stalwart admin UI after setup default._domainkey IN TXT "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY_HERE" ``` #### 2. Port Forwarding You need to expose these ports directly (NOT through Cloudflare): ``` Port 25 (SMTP) - Required for receiving mail from other servers Port 587 (SMTP) - Required for sending mail (submission) Port 465 (SMTPS) - Optional, secure SMTP submission Port 993 (IMAPS) - Required for IMAP access Port 143 (IMAP) - Optional, plaintext IMAP ``` **Option A: NodePort Service** ```bash kubectl apply -f - < mail.dvirlabs.com ``` This is **critical** for email deliverability. Without it, many servers will reject your mail. ## 🔧 Configuration Management ### Using External Secrets (Recommended for Production) Instead of storing passwords in Git, use External Secrets Operator: 1. Install External Secrets Operator 2. Create a secret in your secret backend (Vault, AWS Secrets Manager, etc.) 3. Update manifests/stalwart/values.yaml: ```yaml secret: create: false # Don't create the secret name: stalwart-credentials # Reference external secret ``` 4. Create an ExternalSecret: ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: stalwart-credentials namespace: mail spec: refreshInterval: 1h secretStoreRef: name: your-secret-store kind: SecretStore target: name: stalwart-credentials data: - secretKey: STALWART_ADMIN_PASSWORD remoteRef: key: mail/stalwart/admin-password ``` ## 🛠️ Maintenance ### View Stalwart Logs ```bash kubectl logs -n mail stalwart-0 -f ``` ### View SnappyMail Logs ```bash kubectl logs -n mail -l app.kubernetes.io/name=snappymail -f ``` ### Access Stalwart Shell ```bash kubectl exec -it -n mail stalwart-0 -- /bin/sh ``` ### Backup Mail Data ```bash # Backup Stalwart data kubectl exec -n mail stalwart-0 -- tar czf /tmp/mail-backup.tar.gz /opt/stalwart-mail kubectl cp mail/stalwart-0:/tmp/mail-backup.tar.gz ./mail-backup-$(date +%Y%m%d).tar.gz # Backup SnappyMail config kubectl exec -n mail -l app.kubernetes.io/name=snappymail -- tar czf /tmp/snappymail-backup.tar.gz /var/lib/snappymail kubectl cp mail/snappymail-xxx:/tmp/snappymail-backup.tar.gz ./snappymail-backup-$(date +%Y%m%d).tar.gz ``` ### Restore from Backup ```bash # Restore Stalwart kubectl cp ./mail-backup.tar.gz mail/stalwart-0:/tmp/mail-backup.tar.gz kubectl exec -n mail stalwart-0 -- tar xzf /tmp/mail-backup.tar.gz -C / kubectl rollout restart statefulset -n mail stalwart ``` ## 🔍 Troubleshooting ### Pods Not Starting ```bash # Check pod events kubectl describe pod -n mail stalwart-0 kubectl describe pod -n mail -l app.kubernetes.io/name=snappymail # Check PVC status kubectl get pvc -n mail ``` ### Ingress Not Working ```bash # Check ingress status kubectl describe ingress -n mail # Check Traefik logs kubectl logs -n kube-system -l app.kubernetes.io/name=traefik # Test internal connectivity kubectl run -it --rm debug --image=busybox -n mail -- wget -O- http://stalwart:8080 ``` ### SnappyMail Can't Connect to Stalwart ```bash # Test IMAP connectivity from SnappyMail pod kubectl exec -it -n mail -l app.kubernetes.io/name=snappymail -- nc -zv stalwart.mail.svc.cluster.local 993 # Check Stalwart service kubectl get svc -n mail stalwart ``` ### Email Not Being Delivered Common issues: 1. **No PTR record**: Check reverse DNS 2. **Port 25 blocked**: Many ISPs block outbound port 25 3. **Missing SPF/DKIM/DMARC**: Check DNS records 4. **IP on blacklist**: Check https://mxtoolbox.com/blacklists.aspx ## 📊 Monitoring ### Check Mail Queue ```bash # Access Stalwart admin UI # https://mail.dvirlabs.com # Navigate to Queue section ``` ### Resource Usage ```bash # Check pod resource usage kubectl top pods -n mail # Check PVC usage kubectl exec -n mail stalwart-0 -- df -h /opt/stalwart-mail ``` ## 🔐 Security Hardening ### Recommended Post-Deployment Steps 1. **Change SnappyMail admin password** immediately 2. **Enable fail2ban** for brute force protection 3. **Set up TLS certificates** with cert-manager (if not using Cloudflare) 4. **Enable DKIM signing** in Stalwart 5. **Configure rate limiting** in Stalwart 6. **Regular backups** of mail data 7. **Monitor logs** for suspicious activity ### TLS Certificates with cert-manager If you want Let's Encrypt certificates instead of Cloudflare: ```yaml # Add to ingress annotations cert-manager.io/cluster-issuer: letsencrypt-prod ``` And configure TLS: ```yaml tls: - hosts: - mail.dvirlabs.com secretName: stalwart-tls ``` ## 📚 Additional Resources - [Stalwart Documentation](https://stalw.art/docs) - [SnappyMail Documentation](https://snappymail.eu/) - [Email Deliverability Best Practices](https://www.mail-tester.com/) - [DKIM Setup Guide](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/) ## ⚙️ Customization ### Adjust Storage Size Edit `manifests/stalwart/values.yaml` or `manifests/snappymail/values.yaml`: ```yaml persistence: size: 50Gi # Increase as needed ``` ### Change Resource Limits Edit `manifests/*/values.yaml`: ```yaml resources: requests: memory: "2Gi" cpu: "1000m" limits: memory: "8Gi" cpu: "4000m" ``` ### Add Multiple Domains Configure in Stalwart admin UI after deployment. ## 🤝 Contributing This is a personal lab setup, but feel free to fork and adapt for your needs! ## 📝 License MIT License - Use at your own risk ## ⚠️ Disclaimer This setup is designed for lab/self-hosting environments. For production use: - Use External Secrets for credentials - Set up proper TLS certificates - Configure backup automation - Enable monitoring and alerting - Review security best practices - Test email deliverability thoroughly