Home/Blog/Cybersecurity/MTA-STS and TLS-RPT Guide: Enforcing Email Encryption in Transit
Cybersecurity

MTA-STS and TLS-RPT Guide: Enforcing Email Encryption in Transit

Implement MTA-STS (Mail Transfer Agent Strict Transport Security) and TLS-RPT to enforce TLS encryption for email in transit and gain visibility into encryption failures.

By Inventive Software
MTA-STS and TLS-RPT Guide: Enforcing Email Encryption in Transit

MTA-STS and TLS-RPT Guide: Enforcing Email Encryption in Transit

MTA-STS (Mail Transfer Agent Strict Transport Security) enforces TLS encryption for email in transit, preventing downgrade attacks. TLS-RPT provides visibility into TLS failures. Together, they secure email communication beyond what STARTTLS alone provides.

Why MTA-STS Matters

┌─────────────────────────────────────────────────────────────────────────────┐
│                    THE PROBLEM: STARTTLS DOWNGRADE ATTACKS                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  WITHOUT MTA-STS (Opportunistic TLS):                                       │
│                                                                             │
│  ┌─────────────┐    STARTTLS?    ┌──────────────┐    TLS OK    ┌─────────┐ │
│  │   Sender    │───────────────▶│   MITM       │─────────────▶│ Your MX │ │
│  │   Server    │                │   Attacker   │              │ Server  │ │
│  └─────────────┘                └──────────────┘              └─────────┘ │
│                                        │                                   │
│                                        │ Strips "250 STARTTLS"             │
│                                        │ from server response              │
│                                        ▼                                   │
│                                 ┌──────────────┐                           │
│                                 │ Sender sees  │                           │
│                                 │ "TLS not     │                           │
│                                 │ supported"   │                           │
│                                 │              │                           │
│                                 │ Email sent   │                           │
│                                 │ in PLAINTEXT │                           │
│                                 └──────────────┘                           │
│                                                                             │
│  ⚠️  Attacker reads and/or modifies email content!                          │
│                                                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                    THE SOLUTION: MTA-STS (Enforced TLS)                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  WITH MTA-STS:                                                              │
│                                                                             │
│  ┌─────────────┐  1. Fetch policy   ┌──────────────────────────────────┐   │
│  │   Sender    │───────────────────▶│ https://mta-sts.example.com/     │   │
│  │   Server    │                    │ .well-known/mta-sts.txt          │   │
│  └─────────────┘                    │                                  │   │
│        │                            │ version: STSv1                   │   │
│        │                            │ mode: enforce                    │   │
│        │                            │ mx: mail.example.com             │   │
│        │                            │ max_age: 604800                  │   │
│        │                            └──────────────────────────────────┘   │
│        │                                                                   │
│        │  2. Require TLS + valid cert                                      │
│        │     matching policy                                               │
│        ▼                                                                   │
│  ┌─────────────┐     TLS REQUIRED    ┌──────────────┐                     │
│  │   Sender    │─────────────────────│   MITM       │                     │
│  │   Server    │     CAN'T STRIP     │   Attacker   │                     │
│  └─────────────┘─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶│   BLOCKED!   │                     │
│        │                              └──────────────┘                     │
│        │                                                                   │
│        │  3. TLS connection directly                                       │
│        │     to legitimate MX                                              │
│        ▼                                                                   │
│  ┌─────────────┐     ENCRYPTED       ┌─────────────┐                      │
│  │   Sender    │════════════════════▶│   Your MX   │                      │
│  │   Server    │       TLS 1.3       │   Server    │                      │
│  └─────────────┘                     └─────────────┘                      │
│                                                                             │
│  ✅ Email encrypted, attacker cannot intercept!                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

MTA-STS Components

┌─────────────────────────────────────────────────────────────────────────────┐
│                         MTA-STS ARCHITECTURE                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  COMPONENT 1: DNS TXT Record                                                │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ _mta-sts.example.com. IN TXT "v=STSv1; id=20250108120000"            │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│  Purpose: Signals MTA-STS is enabled, ID changes trigger policy refresh    │
│                                                                             │
│  COMPONENT 2: HTTPS Policy File                                             │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ URL: https://mta-sts.example.com/.well-known/mta-sts.txt             │ │
│  │                                                                        │ │
│  │ Contents:                                                              │ │
│  │   version: STSv1                                                       │ │
│  │   mode: enforce                                                        │ │
│  │   mx: mail.example.com                                                 │ │
│  │   mx: mail2.example.com                                                │ │
│  │   max_age: 604800                                                      │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│  Purpose: Defines allowed MX hosts and policy strictness                   │
│                                                                             │
│  COMPONENT 3: TLS-RPT DNS Record (Optional but Recommended)                 │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ _smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:[email protected]"│ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│  Purpose: Receive reports about TLS connection successes and failures      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Step-by-Step Implementation

Step 1: Verify Prerequisites

Before implementing MTA-STS:

┌─────────────────────────────────────────────────────────────────────────────┐
│                         PREREQUISITES CHECKLIST                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ☐ MX servers have valid TLS certificates                                  │
│    • Certificate matches MX hostname exactly                                │
│    • Certificate is not expired                                             │
│    • Certificate is issued by trusted CA (not self-signed)                 │
│    • Certificate chain is complete                                          │
│                                                                             │
│  ☐ MX servers support TLS 1.2 or higher                                    │
│    • TLS 1.0 and 1.1 are deprecated                                        │
│    • TLS 1.3 recommended if possible                                        │
│                                                                             │
│  ☐ Ability to host HTTPS content at mta-sts.yourdomain.com                 │
│    • Valid TLS certificate for mta-sts subdomain                           │
│    • Web server, CDN, or static hosting available                          │
│                                                                             │
│  ☐ DNS management access                                                    │
│    • Can add TXT records                                                    │
│    • Can add A/CNAME records for mta-sts subdomain                         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Verify MX TLS Certificate:

# Check TLS certificate on MX server
openssl s_client -connect mail.example.com:25 -starttls smtp < /dev/null 2>/dev/null | \
  openssl x509 -noout -dates -subject

# Expected output:
# notBefore=Jan  1 00:00:00 2024 GMT
# notAfter=Dec 31 23:59:59 2025 GMT
# subject=CN = mail.example.com

Step 2: Create the Policy File

Create mta-sts.txt with your policy:

version: STSv1
mode: testing
mx: mail.example.com
mx: mail2.example.com
max_age: 86400

Policy Fields:

FieldRequiredDescription
versionYesAlways STSv1
modeYesnone, testing, or enforce
mxYesMX hostnames (one per line, can use wildcards)
max_ageYesPolicy cache time in seconds (max: 31557600)

Mode Options:

ModeBehavior
noneMTA-STS disabled, ignore policy
testingLog failures, deliver anyway
enforceReject delivery on TLS failure

Example Policies:

# Single MX server
version: STSv1
mode: enforce
mx: mail.example.com
max_age: 604800

# Multiple MX servers
version: STSv1
mode: enforce
mx: mail1.example.com
mx: mail2.example.com
mx: mail3.example.com
max_age: 604800

# Wildcard MX (Google Workspace, Microsoft 365)
version: STSv1
mode: enforce
mx: *.mail.google.com
mx: *.outlook.com
max_age: 604800

# Google Workspace specific
version: STSv1
mode: enforce
mx: aspmx.l.google.com
mx: *.googlemail.com
max_age: 604800

Step 3: Host the Policy File

The policy must be served at:

https://mta-sts.yourdomain.com/.well-known/mta-sts.txt

Option A: Cloudflare Pages

  1. Create a repository with:
.well-known/
  mta-sts.txt
  1. Connect to Cloudflare Pages
  2. Add custom domain: mta-sts.yourdomain.com

Option B: AWS S3 + CloudFront

# Create S3 bucket
aws s3 mb s3://mta-sts-example-com

# Upload policy file
aws s3 cp mta-sts.txt s3://mta-sts-example-com/.well-known/mta-sts.txt \
  --content-type "text/plain"

# Configure CloudFront with custom domain and ACM certificate

Option C: Nginx

server {
    listen 443 ssl http2;
    server_name mta-sts.example.com;

    ssl_certificate /etc/letsencrypt/live/mta-sts.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mta-sts.example.com/privkey.pem;

    location /.well-known/mta-sts.txt {
        root /var/www/mta-sts;
        default_type text/plain;
    }
}

Option D: GitHub Pages

  1. Create repository mta-sts
  2. Add file .well-known/mta-sts.txt
  3. Enable GitHub Pages
  4. Configure custom domain mta-sts.yourdomain.com
  5. Add DNS CNAME: mta-sts.yourdomain.com → youruser.github.io

Step 4: Add DNS Records

MTA-STS TXT Record:

_mta-sts.example.com.  IN  TXT  "v=STSv1; id=20250108120000"

The id field should be a unique identifier that changes when the policy changes (timestamp works well).

TLS-RPT TXT Record:

_smtp._tls.example.com.  IN  TXT  "v=TLSRPTv1; rua=mailto:[email protected]"

Or send reports to a web endpoint:

_smtp._tls.example.com.  IN  TXT  "v=TLSRPTv1; rua=https://report.example.com/tlsrpt"

MTA-STS Subdomain (A or CNAME):

; If hosting on same server
mta-sts.example.com.  IN  A  203.0.113.10

; If using CDN/external hosting
mta-sts.example.com.  IN  CNAME  yourcdn.example.net.

Step 5: Verify Configuration

Check DNS Records:

# Check MTA-STS TXT record
dig +short TXT _mta-sts.example.com
# Expected: "v=STSv1; id=20250108120000"

# Check TLS-RPT record
dig +short TXT _smtp._tls.example.com
# Expected: "v=TLSRPTv1; rua=mailto:[email protected]"

Verify Policy File:

# Fetch and verify policy
curl -sS https://mta-sts.example.com/.well-known/mta-sts.txt

# Expected output:
# version: STSv1
# mode: testing
# mx: mail.example.com
# max_age: 86400

Online Validators:

TLS-RPT Reports

Report Structure

TLS-RPT reports are JSON files containing TLS connection information:

{
  "organization-name": "Google LLC",
  "date-range": {
    "start-datetime": "2025-01-07T00:00:00Z",
    "end-datetime": "2025-01-08T00:00:00Z"
  },
  "contact-info": "[email protected]",
  "report-id": "2025-01-08T00:00:00Z_example.com",
  "policies": [
    {
      "policy": {
        "policy-type": "sts",
        "policy-string": [
          "version: STSv1",
          "mode: enforce",
          "mx: mail.example.com",
          "max_age: 604800"
        ],
        "policy-domain": "example.com",
        "mx-host": "mail.example.com"
      },
      "summary": {
        "total-successful-session-count": 1547,
        "total-failure-session-count": 3
      },
      "failure-details": [
        {
          "result-type": "certificate-expired",
          "sending-mta-ip": "209.85.220.41",
          "receiving-mx-hostname": "mail.example.com",
          "receiving-ip": "203.0.113.10",
          "failed-session-count": 2,
          "additional-information": "Certificate expired 2025-01-05"
        },
        {
          "result-type": "starttls-not-supported",
          "sending-mta-ip": "209.85.220.42",
          "receiving-mx-hostname": "mail.example.com",
          "receiving-ip": "203.0.113.11",
          "failed-session-count": 1
        }
      ]
    }
  ]
}

Failure Types

Result TypeDescriptionAction
certificate-expiredMX certificate expiredRenew certificate immediately
certificate-not-trustedUnknown CA or self-signedUse trusted CA certificate
certificate-host-mismatchCert doesn't match hostnameReissue cert with correct hostname
starttls-not-supportedServer doesn't support TLSEnable STARTTLS on mail server
validation-failureGeneral validation errorCheck certificate chain
sts-policy-invalidMalformed policy fileFix policy file syntax
sts-webpki-invalidPolicy host certificate issueFix mta-sts subdomain certificate
dns-errorDNS lookup failedCheck DNS configuration

Processing Reports

Simple Email Processing:

#!/usr/bin/env python3
"""Parse TLS-RPT reports from email"""

import json
import gzip
import email
import sys
from email import policy

def parse_tlsrpt(msg_file):
    with open(msg_file, 'rb') as f:
        msg = email.message_from_binary_file(f, policy=policy.default)

    for part in msg.walk():
        content_type = part.get_content_type()
        if content_type in ['application/json', 'application/tlsrpt+json',
                           'application/tlsrpt+gzip']:
            payload = part.get_payload(decode=True)

            # Handle gzip compression
            if content_type.endswith('+gzip') or payload[:2] == b'\x1f\x8b':
                payload = gzip.decompress(payload)

            report = json.loads(payload)
            analyze_report(report)

def analyze_report(report):
    print(f"Report from: {report.get('organization-name', 'Unknown')}")
    print(f"Date range: {report['date-range']['start-datetime']} - "
          f"{report['date-range']['end-datetime']}")

    for policy in report.get('policies', []):
        domain = policy['policy'].get('policy-domain', 'Unknown')
        summary = policy.get('summary', {})

        success = summary.get('total-successful-session-count', 0)
        failure = summary.get('total-failure-session-count', 0)

        print(f"\nDomain: {domain}")
        print(f"  Success: {success}, Failures: {failure}")

        for failure in policy.get('failure-details', []):
            print(f"  - {failure['result-type']}: {failure['failed-session-count']} sessions")
            print(f"    MX: {failure.get('receiving-mx-hostname', 'Unknown')}")

if __name__ == '__main__':
    parse_tlsrpt(sys.argv[1])

Using Third-Party Services:

Several services aggregate and visualize TLS-RPT reports:

  • Postmark - Free TLS-RPT processing
  • Report URI - TLS-RPT + DMARC aggregation
  • EasyDMARC - Full email security monitoring
  • URIports - TLS reporting service

Deployment Strategy

┌─────────────────────────────────────────────────────────────────────────────┐
│                      MTA-STS DEPLOYMENT TIMELINE                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  WEEK 1: Setup                                                              │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ Day 1-2: Verify MX TLS certificates are valid                        │ │
│  │ Day 3-4: Create and host policy file (mode: testing)                 │ │
│  │ Day 5-7: Add DNS records, verify with online tools                   │ │
│  │                                                                        │ │
│  │ Policy: mode: testing, max_age: 86400 (1 day)                        │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│                                                                             │
│  WEEKS 2-4: Monitoring                                                      │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ • Monitor TLS-RPT reports daily                                       │ │
│  │ • Investigate any failure-details                                     │ │
│  │ • Fix certificate or configuration issues found                       │ │
│  │ • Verify all MX servers are working correctly                        │ │
│  │                                                                        │ │
│  │ Policy: mode: testing, max_age: 86400 (1 day)                        │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│                                                                             │
│  WEEK 5: Enforcement Preparation                                            │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ Day 1: Review all TLS-RPT reports - ensure near-zero failures        │ │
│  │ Day 2: Update policy to mode: enforce                                │ │
│  │ Day 3-7: Monitor closely for delivery issues                         │ │
│  │                                                                        │ │
│  │ Policy: mode: enforce, max_age: 86400 (1 day)                        │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│                                                                             │
│  WEEKS 6+: Production                                                       │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │ • Increase max_age to 604800 (1 week) or 1209600 (2 weeks)           │ │
│  │ • Continue monitoring TLS-RPT reports weekly                          │ │
│  │ • Set up automated alerting for failures                             │ │
│  │                                                                        │ │
│  │ Policy: mode: enforce, max_age: 604800 (1 week)                      │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Platform-Specific Configuration

Google Workspace

Google Workspace domains can use MTA-STS with Google's MX servers:

version: STSv1
mode: enforce
mx: aspmx.l.google.com
mx: alt1.aspmx.l.google.com
mx: alt2.aspmx.l.google.com
mx: alt3.aspmx.l.google.com
mx: alt4.aspmx.l.google.com
mx: *.aspmx.l.google.com
max_age: 604800

Or use wildcard:

version: STSv1
mode: enforce
mx: *.google.com
mx: *.googlemail.com
max_age: 604800

Microsoft 365

version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
max_age: 604800

Proofpoint

version: STSv1
mode: enforce
mx: *.pphosted.com
max_age: 604800

Self-Hosted

version: STSv1
mode: enforce
mx: mail.example.com
mx: backup-mail.example.com
max_age: 604800

Troubleshooting

Common Issues

IssueSymptomSolution
Policy not found"No MTA-STS policy"Verify HTTPS URL works, check DNS
Certificate mismatchTLS-RPT shows certificate-host-mismatchMX cert must match MX hostname
Policy fetch failssts-webpki-invalidFix mta-sts subdomain certificate
Invalid policy syntaxsts-policy-invalidCheck policy file format (no extra spaces)
DNS issuesPolicy ID mismatchUpdate DNS TXT record ID when policy changes

Debug Commands

# Test full MTA-STS flow
# 1. Check DNS TXT record
dig +short TXT _mta-sts.example.com

# 2. Verify policy is reachable
curl -v https://mta-sts.example.com/.well-known/mta-sts.txt

# 3. Check MX server TLS
openssl s_client -connect mail.example.com:25 -starttls smtp < /dev/null 2>/dev/null | \
  openssl x509 -noout -subject -dates -issuer

# 4. Verify certificate matches MX hostname
echo | openssl s_client -connect mail.example.com:25 -starttls smtp 2>/dev/null | \
  openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

# 5. Check TLS-RPT record
dig +short TXT _smtp._tls.example.com

Policy Update Procedure

When updating your policy:

  1. Update the policy file at mta-sts.yourdomain.com
  2. Update DNS TXT record ID to force cache refresh:
_mta-sts.example.com.  IN  TXT  "v=STSv1; id=20250108150000"
  1. Wait for propagation (DNS TTL + sender cache)

Best Practices

Security Recommendations

  1. Start with testing mode - Monitor before enforcing
  2. Keep certificates updated - Automated renewal recommended
  3. Use short max_age initially - 86400 seconds for flexibility
  4. Monitor TLS-RPT reports - Set up automated alerting
  5. Document all MX servers - Ensure all are in policy
  6. Test certificate changes - Before renewal/replacement

Policy File Best Practices

# DO: Use exact hostnames when possible
mx: mail.example.com
mx: backup.example.com

# DO: Include all MX servers
mx: mx1.example.com
mx: mx2.example.com
mx: mx3.example.com

# CAUTION: Wildcards can be too permissive
mx: *.example.com

# DON'T: Forget backup MX servers
# (will cause mail rejection if primary fails)

# DON'T: Set max_age too high initially
# (makes fixing issues slow)

Automated Certificate Monitoring

#!/bin/bash
# Check MX certificate expiration

DOMAIN="example.com"
MX_SERVERS=$(dig +short MX $DOMAIN | awk '{print $2}')

for mx in $MX_SERVERS; do
  expiry=$(echo | openssl s_client -connect ${mx}:25 -starttls smtp 2>/dev/null | \
           openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

  expiry_epoch=$(date -d "$expiry" +%s)
  now_epoch=$(date +%s)
  days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))

  echo "$mx: $days_left days until expiration"

  if [ $days_left -lt 30 ]; then
    echo "WARNING: Certificate for $mx expires in $days_left days!"
  fi
done

Tools

Need Expert Cybersecurity Guidance?

Our team of security experts is ready to help protect your business from evolving threats.