Mailu¶
Mail server with SMTP, IMAP, webmail, spam filter and admin interface
| Chart | mailu |
| Version | 0.1.7 |
| Application version | 2024.06.46 |
| Kind | Application |
| Source | charts/mailu |
The values are listed under Values, the changes per version under Changelog.
Purpose¶
Mailu is a mail server: SMTP, IMAP and POP3, webmail (Roundcube), a spam filter (rspamd)
and an admin interface. This chart wraps the upstream chart mailu 2.6.3 and adds what a
project on OpenShift needs: a Route for webmail, a public Service for the mail protocols,
NetworkPolicies, a spam filter configuration and, on request, a certificate, monitoring
and a page to copy mailboxes from another server (imapsync).
The upstream chart is included under the alias upstream. Every value of the upstream
chart is set below upstream:, for example upstream.domain. A chart that includes this
chart as mailu sets the same value as mailu.upstream.domain.
Install¶
helm repo add grncloud https://apps.openshift.eu
helm install mailu grncloud/mailu \
--set upstream.domain=example.org \
--set upstream.initialAccount.domain=example.org \
--set 'upstream.hostnames={mail.example.org}'
Read the webmail address and the password of the first account (admin@<domain>):
oc get route mailu -o jsonpath='https://{.spec.host}{"\n"}'
oc get secret mailu-secrets -o jsonpath='{.data.initial-account-password}' | base64 -d
| Value | Default | Meaning |
|---|---|---|
upstream.domain |
example.org |
Mail domain, the part after the @. |
upstream.initialAccount.domain |
example.org |
Domain of the first admin account. Same value as the mail domain. |
upstream.hostnames |
[mail.example.org] |
Hostnames of the mail server. The first one goes in the MX record. |
upstream.persistence.size |
20Gi |
Size of the one volume that holds all mail and state. |
upstream.persistence.storageClass |
empty | Empty: the default StorageClass. |
secrets.type |
kubernetes |
externalSecret reads the four secrets from a secret store. |
secrets.initialAdminPassword |
empty | Password of the first admin account. Empty: generated. |
mailService.public |
false |
true gives the mail protocols a public address (LoadBalancer Service). |
dns.enabled |
false |
Ask the platform's DNS (external-dns) for the records of the mail domain. See Delivery instructions. |
global.dkim.managed |
false |
true: cert-manager makes the DKIM signing key. Set it at install. |
global.dkim.secretTemplate |
{} |
Labels and annotations of the Secret with that key. A platform that publishes the DKIM record recognises the Secret by them. |
dns.spf, dns.dmarc |
v=spf1 mx -all, v=DMARC1; p=quarantine |
Content of the SPF and DMARC records. |
global.publicAddress.shared |
false |
Let the other public Services of the project share one address. The mail Service never shares. |
mailService.annotations |
{} |
Annotations of that Service, for example the address pool. |
mailService.ports.* |
mail ports on | Ports of that Service. http and https are off. |
egressService.enabled |
false |
Second public address, used as the source of outgoing mail. |
route.hostname |
empty | Hostname of webmail. Empty: OpenShift assigns one. |
certificate.enabled |
false |
Request a certificate for upstream.hostnames from certificate.issuerName. |
monitoring.enabled |
false |
ServiceMonitors and alert rules. Needs the right to create them. |
monitoring.alerts |
true |
false keeps the ServiceMonitors and leaves out the alert rules, for a platform that alerts centrally. |
imapsync.enabled |
false |
Page to copy mailboxes from another mail server. |
Web access goes through the Route: the router ends TLS and sends plain HTTP to the mail
front end. Webmail is at /webmail, the admin interface at /admin. To serve the web
pages on the mail address itself, set mailService.ports.http and
mailService.ports.https to true.
Without certificate.enabled the mail ports use a certificate of the cluster's internal
service CA. Mail clients and other mail servers do not trust it. Set certificate.enabled
before real use.
Secrets left empty are generated at the first install and kept on every upgrade with
helm upgrade. A tool that renders the chart without access to the cluster, such as
Argo CD or helm template, cannot read the existing Secret and generates new values on
every render: set secrets.secretKey, secrets.apiToken, secrets.roundcubeKey and
secrets.initialAdminPassword there.
All pods that share the mail volume run on one node, because the volume is ReadWriteOnce. The default requests add up to about 0.5 CPU and 2.2Gi of memory.
Install one release per project: the object names start with mailu, not with the
release name.
Delivery instructions¶
The platform team does these steps for you. Ask for them in one request and name your project, the mail domain and the hostname of the mail server.
- Public address. Set
mailService.public=true. The mail Service gets a public IP address only from the public address pool. Ask which annotation selects that pool and set it, for example--set 'mailService.annotations.metallb\.io/address-pool=<pool>'. Read the address withoc get service mailu-front-ext. - Inbound port 25. Ask the platform team to confirm that port 25 from the internet reaches the public range. Without it you can send mail but not receive it.
- Outgoing address. Outgoing mail must leave from a public address with reverse DNS. Ask
whether the platform sends the mail of your pods from the address of the mail Service.
If it does not, set
egressService.enabled=truewith the same pool annotation; outgoing mail then leaves from the address ofmailu-postfix-egress. - DNS records.
<mail host>is the first ofupstream.hostnames,<domain>isupstream.domain,<address>is shown byoc get service mailu-front-ext.
| Type | Name | Value |
|---|---|---|
A |
<mail host> |
<address> |
MX |
<domain> |
10 <mail host> |
TXT |
<domain> |
v=spf1 mx -all (SPF). Add ip4:<outgoing address> when mail leaves from another address. |
TXT |
dkim._domainkey.<domain> |
The record shown in the admin interface under Mail domains, Details (DKIM). Press Generate keys first, unless global.dkim.managed=true. |
TXT |
_dmarc.<domain> |
v=DMARC1; p=quarantine; rua=mailto:postmaster@<domain> (DMARC) |
CNAME |
autoconfig.<domain> |
<mail host>. Optional; also set mailService.ports.http and https to true. |
PTR |
<outgoing address> |
<mail host>. Set by the owner of the address: ask the platform team. |
Your own DNS zone: create these records at your DNS provider. Start with A and wait
until it resolves, then MX; mail for the domain arrives here from that moment.
A zone the platform serves: set dns.enabled=true and global.dkim.managed=true. The
A record then follows the Service, SPF and DMARC are requested with a DNSEndpoint,
and the DKIM record follows the signing key that cert-manager keeps in the Secret
mailu-dkim. The MX record is not written by the platform: ask the platform team
for MX <domain> 10 <mail host>. and check with dig MX <domain>. Do not press
Generate keys with a managed key: the key file is read-only.
- Shared address. With global.publicAddress.shared=true the public Services of the
project that can share one address do so, and switch to externalTrafficPolicy:
Cluster. The mail Service keeps its own address: behind a shared address every
sending server would appear with the address of a cluster node, which defeats the
spam checks and makes the server relay for anyone.
- Admin pod. The upstream chart always adds the capability NET_BIND_SERVICE to the
admin container and the container must start as root. The anyuid SCC allows root but
no added capability. Ask the platform team to confirm that the admin pod can run in
your project before you rely on this chart.
- Webmail hostname. A route.hostname outside the default application domain needs a DNS
record pointing at the platform's ingress.
- Certificate. Ask for the name of the issuer and set certificate.enabled=true and
certificate.issuerName. The certificate is issued once the DNS records exist.
- Monitoring. monitoring.enabled=true needs the role monitoring-edit in your project.
- With secrets.type=externalSecret, create four items in your vault collection, named
as in secrets.externalSecret.items, each with the value in the password field, and
set secrets.externalSecret.storeName to your store.