Helm chart for Kubernetes
If you run a Kubernetes cluster, the mta-sts Helm chart serves your MTA-STS policy from your own infrastructure. DMARC Examiner maintains it, it is open source (MIT license) and it works on any plan, Free included.
If you do not want to maintain anything, use Hosted MTA-STS.
What it does
- Several domains. One release serves
mta-sts.<domain>for every domain in the list, each with its own policy. - Automatic
id. Theidof the_mta-stsrecord comes from the policy content: it changes only when the policy changes. - DNS records as code. With external-dns, it creates the
_mta-stsand_smtp._tls(TLS-RPT) TXT records. Without external-dns, it shows you the records to create. - TLS. It annotates the Ingress for cert-manager (
ClusterIssuerorIssuer). - Secure by default. Non-root nginx, read-only file system and no capabilities.
- Validation. It rejects invalid modes, a
max_ageout of range, malformed MX hosts and malformed TLS-RPT addresses before it installs anything. helm test. It downloads every policy through the Service and checks that an unknown host gets a 404.
Requirements
- Kubernetes 1.25 or later and Helm 3.8 or later.
- An Ingress controller reachable from the internet on port 443.
- A publicly trusted certificate for every
mta-sts.<domain>. cert-manager with Let's Encrypt works. mta-sts.<domain>must resolve to your Ingress.- Optional: external-dns with the
crdsource, to manage the TXT records.
Install
helm install mta-sts oci://ghcr.io/dmarc-examiner/charts/mta-sts \
--namespace mta-sts --create-namespace -f values.yamlOne domain on Google Workspace
Put your DMARC Examiner reporting address in tlsRpt.rua. You find it in the DNS Records tab of the domain, under TLS-RPT record.
domains:
- name: example.com
policy:
mode: testing
mx:
- aspmx.l.google.com
- "*.aspmx.l.google.com"
tlsRpt:
rua:
- mailto:<your-reporting-address>
ingress:
className: nginx
certManager:
clusterIssuer: letsencryptOn Microsoft 365, the MX is *.mail.protection.outlook.com.
Several domains with different policies
Each domain can override mode, maxAge, mx and tlsRptRua. Anything it does not override comes from policy and tlsRpt.
domains:
- name: example.com
- name: example.org
mode: enforce
mx:
- mx1.example.org
- mx2.example.org
- name: legacy.example.net
mode: none
policy:
mode: testing
mx:
- mx.example.comIn tlsRpt.rua, {domain} is replaced by the name of each domain.
DNS records with external-dns
externalDns:
enabled: true
labels:
dns: publicThe chart creates one DNSEndpoint per domain with these records:
| Name | Type | Value |
|---|---|---|
_mta-sts.example.com |
TXT | v=STSv1; id=<20 hex characters> |
_smtp._tls.example.com |
TXT | v=TLSRPTv1; rua=mailto:<your-reporting-address> |
external-dns needs --source=crd and, if you set labels, the matching --label-filter. The A or CNAME record of mta-sts.<domain> comes from the Ingress, as usual.
Without external-dns, helm install and helm upgrade show the exact records to create in the release notes.
The policy id
The chart sets id = first 20 hex characters of sha256(policy file). A helm upgrade that does not touch the policy keeps the id. If it changes the policy, the id changes on its own. With external-dns the TXT record follows; without it, copy the new value from the release notes.
Rollout and withdrawal
| Step | Values |
|---|---|
| Start | mode: testing and tlsRpt.rua |
| Switch to enforce | mode: enforce, after one or two weeks of TLS reports without failures |
| Change MX | Add the new MX hosts to mx before you change the DNS |
| Withdraw | mode: none for a whole maxAge; then helm uninstall |
The reasons for each step are in What is MTA-STS.
Check
helm test mta-sts -n mta-sts
curl -s https://mta-sts.example.com/.well-known/mta-sts.txt
dig +short TXT _mta-sts.example.comIn DMARC Examiner, the MTA-STS card of the domain shows the published mode and the TLS tab shows the reports from senders.
The full list of values is in the chart README.