Mailu
Audience
PLANA staff handling email infrastructure. Customer email is not sent or received via this server — customers use their own email provider.
PLANA's email server is Mailu, the open-source email stack (Postfix + Dovecot + Rspamd + a web admin UI + webmail). It is the only thing on plana.cloud and is the only PLANA service that does NOT run on the SKS cluster.
Why a separate box
Email is unforgiving:
- IP reputation is hard to rebuild once burned
- Reverse DNS must match HELO
- SPF, DKIM, DMARC all need to align
- Outbound rate limits, blocklist watching, abuse reports — operational weight is real
Running it inside the K8s cluster with rotating pod IPs would guarantee bad email reputation within a week. So Mailu runs on a dedicated Hetzner box with a static IP, careful reverse DNS, and warm sender reputation.
The box
| Property | Value |
|---|---|
| Provider | Hetzner Online (Falkenstein, Germany) |
| Host | mail01 |
| IP | 194.182.177.165 |
| Domain | plana.cloud (DNS) |
| User-facing hostnames | mail.planapulse.com, webmail.planapulse.com, smtp.planapulse.com |
| Stack | Mailu (Docker Compose) |
| TLS | Let's Encrypt (per-domain, on-box certbot) |
| Backups | Daily encrypted dump to SOS |
The box is operated by hand — no GitOps, no Flux. Mailu's release cadence is slow, configuration changes are infrequent, and the operations are too email-specific for our K8s tooling.
Runbook: infra/docs/runbooks/mailu.md.
What it serves
| Address pattern | Use |
|---|---|
*@plana.solutions | PLANA staff email |
*@planapulse.ai | Transactional emails sent by PLANA services |
bills@<workspace>.planapulse.app | OCR-enabled bill ingestion |
noreply@planapulse.com | Automated platform notifications |
It does not serve mail for customer tenants' own domains.
SMTP relay for services
Cluster services that need to send email (welcome emails, password reset, alert notifications) use Mailu as their SMTP relay:
SMTP host: smtp.planapulse.com
SMTP port: 587 (submission, STARTTLS)
SMTP user: <per-service>
SMTP password: <SOPS-encrypted>The credentials are per-service so we can revoke or audit individually.
Hairpin-NAT consideration
The Exoscale NLB doesn't hairpin — a pod inside the SKS cluster can't reach mail.planapulse.com over the LB IP. For mail, the in-cluster SMTP traffic goes directly to the Mailu box's internet-routable IP (194.182.177.165), not via the LB. So hairpin isn't a problem for SMTP.
For receiving the bill-ingestion emails (bills@<workspace>...), an inbound webhook from Mailu posts to the tenant's pulse-account-api endpoint — see pulse-account-api.
Email reputation hygiene
| Practice | Why |
|---|---|
| Static IP | Reputation is per-IP; rotating would reset every week |
| Reverse DNS matches HELO | Receivers reject mail without it |
| SPF, DKIM, DMARC published | Standard expectations |
| No mass mailing from the relay | Marketing campaigns go via dedicated marketing senders, not Mailu |
| Bounce monitoring | Bounce rate over 2% is a yellow flag, over 5% is critical |
What "do not touch from the cluster" means
Operationally:
- No Ansible / Flux / Crossplane resource targets the Hetzner box
- No
kubectloperation reaches it - The only cluster → Hetzner connections are SMTP submissions on 587 and the bill-ingestion webhook back into the cluster (which is initiated FROM the cluster's HTTPRoute, not from Mailu)
If you need to change Mailu config, SSH into the box and edit by hand, per the runbook.
Where to read more
- Architecture → Domains —
plana.cloudis Mailu-only - pulse-account-api — uses Mailu for outbound transactional email
- Runbook:
infra/docs/runbooks/mailu.md(internal)