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

Frequently Asked Questions

Find answers to common questions

MTA-STS (Mail Transfer Agent Strict Transport Security) is a protocol that allows email domain owners to enforce TLS encryption for incoming emails. Without MTA-STS, email encryption via STARTTLS is opportunistic and vulnerable to downgrade attacks where attackers strip TLS to intercept emails in plaintext. MTA-STS tells sending servers to require valid TLS certificates and refuse delivery if encryption fails.

STARTTLS is a command that upgrades a plaintext SMTP connection to encrypted TLS, but it's opportunistic—if the TLS handshake fails, email is sent in plaintext. MTA-STS makes TLS mandatory: if a valid TLS connection cannot be established, the sending server must NOT deliver the email, preventing downgrade attacks and eavesdropping.

TLS-RPT (TLS Reporting) is a complementary protocol that provides feedback about TLS connection failures when sending email to your domain. While MTA-STS enforces encryption, TLS-RPT gives you visibility into failures—helping you identify misconfigurations, certificate issues, or attack attempts. Together, they provide enforcement and monitoring.

MTA-STS requires three components: 1) A DNS TXT record at _mta-sts.yourdomain.com indicating MTA-STS is enabled, 2) A policy file hosted at https://mta-sts.yourdomain.com/.well-known/mta-sts.txt, 3) A valid TLS certificate on your mail server. The policy file specifies which MX hosts are valid and the policy mode (testing, enforce, or none).

Start with 'testing' mode, which asks senders to report failures without actually blocking emails. Monitor TLS-RPT reports for issues. Once confident in your configuration, switch to 'enforce' mode, which instructs senders to reject delivery if TLS fails. Keep max_age low initially (86400 seconds = 1 day) for quick updates.

The policy file must be accessible at https://mta-sts.yourdomain.com/.well-known/mta-sts.txt over HTTPS with a valid certificate. You can host it on any web server, CDN, or static hosting service (AWS S3, Cloudflare Pages, GitHub Pages). The mta-sts subdomain can point to different infrastructure than your main site.

TLS-RPT reports are JSON files sent daily (or when failures occur) containing: your domain name, the reporting period, result types (success, failure), failure details (certificate errors, MTA-STS policy failures, STARTTLS failures), and counts of successful/failed sessions. Use these to monitor email encryption health.

Start with a short max_age (86400 seconds = 1 day) during initial deployment to allow quick policy changes. Once stable, increase to 604800 (1 week) or 1209600 (2 weeks) for better security. Longer max_age means senders cache your policy longer, providing better protection but slower propagation of changes.

Yes, major providers support MTA-STS: Google (Gmail, Workspace) enforces MTA-STS policies and sends TLS-RPT reports, Microsoft 365 supports receiving MTA-STS policies, and most enterprise mail systems support it. Yahoo, Apple, and other providers are gradually adopting it. Even partial adoption significantly improves security.

In 'enforce' mode, yes—if your mail server's TLS certificate is invalid, expired, or misconfigured, senders following MTA-STS will refuse to deliver email. Always start with 'testing' mode and monitor TLS-RPT reports. Ensure your MX servers have valid certificates matching the hostnames in your MTA-STS policy before enabling enforcement.

Ensure Email Encryption in Transit

Our team configures MTA-STS and TLS-RPT to enforce encrypted email delivery and monitor compliance.