Configuration
Security
Cleanmails implements multiple layers of security to protect your data and infrastructure.
Encryption
- Credentials at rest: All SMTP/IMAP passwords, integration tokens, and API keys are encrypted with AES-256-GCM using your
ENCRYPTION_KEY(64-char hex = 32 bytes) - User passwords: Hashed with bcrypt
- API keys: Hashed with bcrypt before storage; only the
cm_live_prefix is displayed in the UI - JWT tokens: Signed with
JWT_SECRET(HMAC), 7-day expiry
Never lose your ENCRYPTION_KEY
If you lose the ENCRYPTION_KEY, all stored SMTP passwords, IMAP credentials, and integration tokens become unrecoverable. Back up your .env file separately.
Authentication
- JWT-based auth: Login returns a JWT token set as an HttpOnly cookie (
auth_token) - API key fallback: Endpoints also accept
Authorization: Bearer cm_live_xxxfor programmatic access - Workspace access middleware: Every workspace-scoped request validates the user has access to that workspace
- Role-based access: Admin endpoints require
super_adminoradminrole
Rate Limiting
| Scope | Limit | Window |
|---|---|---|
| Login / Register (per IP) | 5 requests | 1 minute |
| Authenticated endpoints (per user) | 60 requests | 1 minute |
HTTP Security Headers
The security headers middleware adds to every response:
X-Content-Type-Options: nosniffX-Frame-Options: DENYStrict-Transport-Security: max-age=31536000(via Caddy)
CORS
CORS is restricted to origins defined in the ALLOWED_ORIGINS environment variable (comma-separated). In production, this should only contain your domain (e.g., https://app.yourdomain.com).
License Guard
A middleware checks license status on every request. If the license is revoked, all API routes are blocked with a 403 response — protecting against unauthorized usage after license expiration.
Input Validation
- Request body limit: 10 MB max (
MaxMultipartMemory) - Binding validation: Gin struct binding with required field annotations
- UUID parsing: All resource IDs are validated as UUIDs
- Workspace isolation: Queries always filter by workspace_id at the database layer
Webhook Security
- Outgoing webhooks: Signed with HMAC-SHA256 using per-subscription secrets
- Incoming webhooks (SES/Resend): Public endpoints for bounce/complaint processing
- Unsubscribe links: HMAC-signed tokens prevent enumeration attacks
- Tracking links: Unique per-delivery tracking IDs
Data Isolation
- All data queries are scoped by
workspace_id— enforced by middleware - Employees only see assigned workspaces (controlled via admin assignments)
- Clients only see their own workspace
- Campaign start validates step count and mailbox assignment
- Lead deduplication enforced by database unique index per workspace
Infrastructure Security
- .env file: Set to mode 640 (readable by root and docker group only)
- Docker socket: Mounted read-only for self-update capability
- No exposed ports: Only Caddy exposes 80/443; all services communicate internally
- Health checks: Internal only (not exposed through Caddy except
/health) - Redis: No authentication required (only accessible within Docker network)
- PostgreSQL: Password-protected, only accessible within Docker network
Roles & Permissions
| Role | Access |
|---|---|
super_admin | Everything + system updates + impersonation |
admin | Admin panel + all workspaces + employee/client management |
employee | Only assigned workspaces (campaigns, leads, mailboxes, threads) |
client | Read-only view of their workspace |