Agent Wranglers

EML-75Recommended

Require TLS for incoming mail with MTA-STS and get TLS reports

Publish an MTA-STS policy in enforce mode and a TLS-RPT record for every domain that receives mail. Senders then refuse plain text and report failures to you.

How to do it

STARTTLS alone can be stripped by an attacker on the path. MTA-STS (RFC 8461) tells sending servers to deliver only over TLS with a valid certificate for your MX hosts. Serve the policy at https://mta-sts.example.com/.well-known/mta-sts.txt over HTTPS with a valid certificate. List every MX host, and set max_age to at least a week once it works. Publish _mta-sts.example.com as a TXT record with a new id each time the policy changes. Start in testing mode and read the TLS reports (RFC 8460) for two weeks before enforce. Request them with a _smtp._tls.example.com TXT record. A domain signed with DNSSEC can add Pin mail TLS with DANE as well.

Examples

Plain text example

version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age: 1209600

DNS example

_mta-sts.example.com.    3600  IN  TXT  "v=STSv1; id=20261027"
_smtp._tls.example.com.  3600  IN  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@example.com"

JavaScript example

export function mtaStsProblems(policyText, mxHosts) {
  const lines = Object.fromEntries(policyText.split('\n').map(line => line.split(': ')));
  const listed = policyText.split('\n').filter(line => line.startsWith('mx: ')).map(line => line.slice(4).trim());
  return [
    lines.mode?.trim() !== 'enforce' && 'mode is not enforce',
    ...mxHosts.filter(host => !listed.includes(host)).map(host => `${host} missing`),
  ].filter(Boolean);
}

Python example

def mta_sts_problems(policy_text: str, mx_hosts: list[str]) -> list[str]:
    lines = [line.strip() for line in policy_text.splitlines()]
    listed = [line[4:] for line in lines if line.startswith("mx: ")]
    problems = [] if "mode: enforce" in lines else ["mode is not enforce"]
    return problems + [f"{host} missing" for host in mx_hosts if host not in listed]

How to check

curl -s https://mta-sts.example.com/.well-known/mta-sts.txt returns, as text/plain, version: STSv1, mode: enforce and every host that dig +short MX example.com lists. dig +short TXT _mta-sts.example.com returns v=STSv1; id=…, and dig +short TXT _smtp._tls.example.com returns v=TLSRPTv1; rua=….

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/email/sending-domain-and-authentication/mta-sts-tls-rpt">Agent Wranglers email playbook</a>. In code you write, add a comment next to it with the same link.