Architecture Overview¶
Understanding the design and structure of cdk8s-mailu deployments.
Introduction¶
cdk8s-mailu is designed around the principle of composable constructs - small, reusable building blocks that can be combined to create complex deployments. This architecture provides flexibility while maintaining production-grade defaults.
Component Architecture¶
Mailu is a modular mail server composed of multiple services working together:
graph TB
Ingress[Traefik<br/>TLS Termination] -->|Plaintext: 587, 465, 993, 995| FrontNginx[Front Nginx<br/>Auth Proxy]
Ingress -->|HTTP:80| Admin[Admin:8080<br/>Web UI + SSO]
Ingress -->|HTTP:80| Webmail[Webmail:80<br/>Roundcube]
Ingress -.MX:25.-> Postfix[Postfix:25<br/>SMTP]
FrontNginx -->|auth_http:8080/internal/auth/email| Admin
FrontNginx -->|Relay SMTP:25| Postfix
FrontNginx -->|Relay IMAP:143<br/>POP3:110| Dovecot[Dovecot<br/>IMAP/POP3]
Webmail -.SMTP:10025.-> DovecotSub[Dovecot<br/>Submission:10025]
DovecotSub -.Relay:25.-> Postfix
Postfix -->|Milter:11332| Rspamd[Rspamd<br/>Spam Filter]
Postfix -.Scan:3310.-> ClamAV[ClamAV<br/>Antivirus]
Postfix -->|LMTP:2525| Dovecot
style DovecotSub fill:#e1f5fe
style Webmail fill:#fff3e0
style Postfix fill:#c8e6c9
Core Components¶
Ingress (Traefik)
TLS termination for all protocols (HTTPS, SMTPS, IMAPS, POP3S)
Routes HTTP/HTTPS (80/443) directly to Admin and Webmail
Routes mail protocols (587, 465, 993, 995) to Front (Nginx)
Routes MX mail (port 25) directly to Postfix
LoadBalancer service with public IP
Front (Nginx)
Protocol routing for authenticated mail protocols (SMTP submission 587/465, IMAP 993, POP3 995)
Authentication proxy using Admin’s auth_http endpoint
Receives plain TCP from Traefik after TLS termination
Proxies to backend Kubernetes Services (Postfix, Dovecot)
Note: nginx proxies to Services, not directly to pods; if multiple backend replicas exist, the Service handles load balancing
Note: Port 25 (MX mail reception) and HTTP/HTTPS bypass Front entirely
Always required for authenticated mail protocols
Admin
Web-based administration interface (accessed via Ingress at port 80 internally)
User and domain management
Configuration interface
Authentication backend: Provides auth_http endpoint for Front (Nginx)
SSO service: Single sign-on for webmail and admin interface
Shared database: PostgreSQL database shared with Webmail
Always enabled by default
Webmail (Roundcube)
Browser-based email client (accessed via Ingress at port 80 internally)
Contact and calendar management
Uses Dovecot Submission service for sending mail
Shared database: Uses same PostgreSQL database as Admin
SSO integration: Authenticates via Admin’s SSO service
Enabled by default, can be disabled
Postfix
SMTP server for sending/receiving mail
Mail routing and relay
Spam/virus scanning integration
Mail delivery: Uses LMTP to deliver mail to Dovecot
Port 25 (MX): Receives direct routing from Traefik (bypasses Front/nginx)
Traefik InFlightConn middleware: Limits simultaneous connections per IP (15 default)
Postfix anvil rate limiting: Limits connections/min (60), messages/min (100), recipients/min (300)
Port 10025: Internal submission relay from Dovecot submission service
Always required
Dovecot
IMAP and POP3 server
Mail storage and retrieval
Mail reception: Receives mail from Postfix via LMTP
Authentication backend
Always required
Rspamd
Spam filtering
DKIM signing/verification
Header manipulation
Always required
Dovecot Submission Service
Dedicated service for webmail email sending
Uses official
dovecot/dovecot:2.3-latestimageListens on port 10025 for token authentication
Relays to Postfix:25 using
submission_relay_hostSolves configuration issues with bundled dovecot in front container
Always deployed (required for webmail functionality)
Optional Components¶
ClamAV
Antivirus scanning for attachments
Virus definition updates
Resource-intensive (requires ~1GB RAM)
Disabled by default
Webdav (Radicale)
CalDAV and CardDAV server
Calendar and contact synchronization
Disabled by default
Fetchmail
Fetch email from external POP3/IMAP servers
Consolidate multiple accounts
Disabled by default
Mail Delivery Flow Details¶
Understanding how mail flows through the system is crucial for troubleshooting and configuration.
Inbound Mail (Internet → Mailbox)¶
Complete flow from external sender to user mailbox:
External MTA → Traefik:25 - Internet mail server sends to your MX record
Traefik → Postfix:25 - Traefik routes directly to Postfix (bypasses Front/Nginx)
Postfix → Rspamd:11332 - Postfix calls Rspamd via milter protocol for spam scanning
Rspamd → Postfix - Rspamd returns scan results (accept/reject/add headers)
Postfix → Dovecot:2525 - Postfix delivers mail to Dovecot via LMTP protocol
Dovecot stores - Mail written to
/mailPVC in Maildir format
Key Points:
Rspamd scanning is inline (mail passes through), not parallel
Port 25 (MX) requires no authentication (standard SMTP behavior)
ClamAV scanning happens during Rspamd step if enabled
LMTP delivery ensures reliable handoff from Postfix to Dovecot
Rate Limiting:
Traefik InFlightConn middleware: Limits simultaneous connections per IP
Postfix anvil: Connection rate (60/min), message rate (100/min), recipient rate (300/min)
Webmail Sending (User → Internet via Webmail)¶
Complete flow when user sends email via webmail:
User → Browser → Webmail:80 - User composes email in Roundcube
Webmail authenticates - User already authenticated via Admin SSO
Webmail → Dovecot-Submission:10025 - Webmail sends via dedicated submission service
Dovecot-Submission accepts - Uses
nopassword=ytrust model (network isolation)Dovecot-Submission → Postfix:25 - Relays to Postfix without authentication
Postfix → Internet - Delivers to recipient’s MX server
Key Points:
“Token authentication” means webmail session trust, not actual token validation
Trust based on network isolation (only webmail pod can reach dovecot-submission:10025)
Dovecot-submission is a relay-only service (no mailbox access)
Bypasses Front/Nginx entirely for direct backend communication
Why separate submission service?
Webmail needs to send mail without user entering password again
Session-based trust is more secure than storing passwords
Isolates webmail submission from regular SMTP submission
Authenticated SMTP/IMAP (Mail Client → Server)¶
Complete flow for authenticated mail client access:
Mail client → Traefik:587/993/995 - Client connects (Thunderbird, Outlook, etc.)
Traefik TLS termination - Traefik decrypts TLS, forwards plaintext
Traefik → Front:587/993/995 - Nginx receives plaintext connection
Front → Admin:8080/internal/auth/email - Nginx auth_http check
Admin validates - Queries PostgreSQL for user credentials
Admin → Front - Returns HTTP 200 (success) or 403 (failure) with backend address
If authenticated:
SMTP (587/465): Front → Postfix:25
IMAP (993/143): Front → Dovecot:143
POP3 (995/110): Front → Dovecot:110
Key Points:
All TLS ports receive plaintext traffic (TLS_FLAVOR=notls, Traefik handles TLS)
Nginx auth_http protocol validates every connection
Front acts as authentication proxy, not just protocol router
Port 25 (MX) bypasses authentication (accepts from any sender)
Service Discovery and FRONT_ADDRESS¶
⚠️ IMPORTANT: FRONT_ADDRESS Naming Quirk
The environment variable
FRONT_ADDRESSis misleading - despite its name, it points to the Dovecot service, NOT the Front (Nginx) service.Why? This is a Mailu upstream naming convention. Mailu historically used the “front” container for LMTP delivery, so the variable is named
FRONT_ADDRESS. In cdk8s-mailu architecture with Traefik TLS termination, we point this variable to Dovecot directly for LMTP delivery from Postfix.Code reference:
src/mailu-chart.ts:384-388// FRONT_ADDRESS is used for LMTP delivery (postfix -> dovecot:2525) // Despite the name, it should point to dovecot, not the nginx front service if (this.dovecotConstruct?.service) { this.sharedConfigMap.addData('FRONT_ADDRESS', `${this.dovecotConstruct.service.name}.${namespace}.svc.cluster.local`); }Impact: When debugging, remember that
FRONT_ADDRESS= Dovecot service DNS name, not Front service.
CDK8S Design Patterns¶
Construct Hierarchy¶
MailuChart (extends Chart)
├── Namespace
├── SharedConfigMap (service discovery)
├── NginxPatchConfigMap (TLS_FLAVOR=notls wrapper)
├── WebmailPatchConfigMap (backend connection patches)
├── FrontConstruct
│ ├── Deployment (nginx with wrapper script)
│ └── Service (HTTP, SMTP, IMAP, POP3 ports)
├── AdminConstruct
│ ├── Deployment
│ ├── Service
│ └── PersistentVolumeClaim (5Gi)
├── PostfixConstruct
│ ├── Deployment
│ ├── Service (port 25, 10025)
│ └── PersistentVolumeClaim (5Gi)
├── DovecotConstruct
│ ├── Deployment
│ ├── Service
│ └── PersistentVolumeClaim (mailbox storage)
├── DovecotSubmissionConstruct
│ ├── Deployment (AMD64 nodeSelector)
│ ├── Service (port 10025)
│ └── ConfigMap (dovecot.conf, entrypoint.sh)
├── RspamdConstruct
│ ├── Deployment
│ ├── Service
│ └── PersistentVolumeClaim (5Gi)
└── Optional:
├── WebmailConstruct
├── ClamavConstruct
├── FetchmailConstruct
└── WebdavConstruct
Each construct is self-contained and manages:
Kubernetes resources (Deployment, Service, ConfigMap, etc.)
Resource requirements (CPU, memory)
Volume mounts and storage
Environment variables
Service discovery configuration
Configuration Flow¶
User provides MailuConfig - Type-safe configuration object
MailuChart validates config - Ensures required fields present
Shared resources created - Namespace, shared ConfigMap
Constructs instantiated conditionally - Based on component toggles
Resources synthesized - CDK8S generates Kubernetes YAML
Resource Management Philosophy¶
Defaults optimized for production:
Conservative resource requests (pods scheduled reliably)
Higher limits (allow bursting for traffic spikes)
Based on real-world usage patterns
Can be overridden per-component
Example: Admin component
Request: 100m CPU, 256Mi memory (guaranteed minimum)
Limit: 300m CPU, 512Mi memory (burstable maximum)
Storage Architecture¶
Persistent Volumes¶
Data Volume (/data)
Application data, SQLite database (if used)
Configuration files
Default: 10Gi
Mail Volume (/mail)
User mailboxes and messages
Largest storage requirement
Default: 50Gi, adjust based on users
Database Options¶
SQLite (Default)
Simple, zero-configuration
Suitable for small deployments (<100 users)
Stored in
/datavolume
PostgreSQL (Recommended for Production)
Better performance and reliability
Required for high-availability setups
Managed separately (CNPG, cloud database, etc.)
Network Architecture¶
Service Discovery¶
All components communicate via Kubernetes services:
{component-name}-service- Standard naming patternInternal DNS resolution
No external dependencies for inter-component communication
Ingress/TLS Configuration¶
Traefik TLS Termination (Supported & Tested)
Traefik handles TLS for all protocols (SMTP/IMAP/HTTP/HTTPS)
Nginx wrapper patches Front component for plaintext backend communication
Automatic certificate management via cert-manager
This is the only tested and supported configuration in cdk8s-mailu
Alternative Ingress Solutions
Theoretically, other ingress controllers could be used (e.g., nginx-ingress, HAProxy)
Would require custom configuration and testing
Not tested or documented - use at your own risk
Front Direct TLS (Not Recommended)
Mailu’s Front component can handle TLS directly (TLS_FLAVOR=cert, letsencrypt, mail, etc.)
This configuration is untested in cdk8s-mailu and likely requires significant modifications
Manual certificate management, no nginx patching applied
Not recommended: use Traefik termination instead
Design Decisions¶
Why CDK8S?¶
CDK8S was chosen over traditional YAML/Helm for cdk8s-mailu because it provides infrastructure as code benefits that are crucial for maintaining a complex, multi-component mail server deployment.
Type Safety Prevents Configuration Errors
Catch mistakes at compile time, not deploy time
IDE shows available options with autocomplete
Impossible to reference non-existent fields
Refactoring is safe and reliable
Real Example: When the dovecot submission service was added, TypeScript’s type system immediately caught everywhere that needed updates (service discovery, ConfigMap, construct exports). With YAML, these would have been runtime failures.
Programmatic Logic Simplifies Complex Configurations
Conditional resource creation based on config flags
Dynamic service name generation with hashing
Environment variable construction from multiple sources
Resource calculation (e.g., convert “256Mi” to bytes)
Testability Ensures Reliability
Unit tests for individual constructs
Integration tests for complete deployments
Test coverage >90% achieved
Snapshot testing for manifest stability
Advantages:
Type-safe configuration with compile-time validation
IDE autocomplete and inline documentation
Programmatic manifest generation with logic
Testable infrastructure code (>90% coverage)
Reusable constructs across projects
Why Modular Constructs?¶
Each Mailu component is implemented as a separate construct class (AdminConstruct, PostfixConstruct, etc.) rather than one monolithic MailuChart. This modular approach was essential for managing complexity.
Independent Testing and Development
Each construct has its own test file
Can develop and test components in isolation
Easier to debug when issues arise
Regression testing catches component-specific breaks
Flexible Configuration
Enable/disable components with simple flags
Override resources per-component
Customize storage per-service
Different image tags per component possible
Real Example: The dovecot submission service was added as a new DovecotSubmissionConstruct without touching existing constructs. If it had been one big class, the change would have affected every test and increased risk.
Clear Ownership and Documentation
Each construct file is self-documenting
Props interface shows required configuration
Construct methods are single-purpose
Easy to understand data flow
Benefits:
Component-level customization (resources, storage, images)
Easier testing (unit test each construct independently)
Clear separation of concerns (one construct = one service)
Maintainability (changes isolated to specific constructs)
Parallel development possible
Why Production Defaults?¶
cdk8s-mailu is designed with opinionated production-grade defaults rather than minimal examples that require extensive configuration. This reduces the “time to working deployment” and prevents common misconfigurations.
Resource Requests and Limits
All components have sensible CPU/memory defaults
Requests ensure reliable scheduling
Limits prevent resource exhaustion
Based on real production metrics
Example Defaults:
Admin: 100m CPU / 256Mi RAM (adequate for <1000 users)
Postfix: 100m CPU / 512Mi RAM (handles typical mail volume)
Dovecot: 200m CPU / 1Gi RAM (IMAP is memory-intensive)
Storage Sizes
Admin PVC: 5Gi (configuration and SQLite if used)
Postfix PVC: 5Gi (mail queue)
Dovecot PVC: 50Gi default (adjust based on users)
Rspamd PVC: 5Gi (spam filter data)
Security and Reliability
Non-root filesystem disabled where needed (mail services require privileges)
Health probes configured automatically
Service discovery via environment variables
PVC retention policies appropriate for mail data
Real-World Validation
Defaults tested on production clusters
Successfully deployed with AMD64/ARM64 mixed nodes
Handles real email traffic and webmail usage
Proven to work with Traefik TLS termination
Philosophy:
“Works out of the box” for most use cases (minimal configuration required)
Override only what you need (but you can customize everything)
Based on real-world production deployments
Fail-safe rather than fail-fast (conservative resources, not minimal)
See Also¶
Dovecot Submission Service - Detailed explanation of webmail email sending
CDK8S Patterns - Construct design patterns
Configuration Options - Complete API reference
Quick Start Tutorial - Deploy your first instance