MTA-STS Policy Publication Drift and Enforce-Mode Readiness avatar

MTA-STS Policy Publication Drift and Enforce-Mode Readiness

Pricing

from $2.00 / 1,000 run starteds

Go to Apify Store
MTA-STS Policy Publication Drift and Enforce-Mode Readiness

MTA-STS Policy Publication Drift and Enforce-Mode Readiness

Give it your domains. It reads _mta-sts and _smtp._tls TXT, the live MX set and the MTA-STS policy file, compares each one with the snapshot of the last run, parses your TLS-RPT report files, and tells you if the domain can move to enforce mode. It finds

Pricing

from $2.00 / 1,000 run starteds

Rating

0.0

(0)

Developer

kingii98

kingii98

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Categories

Share

MTA-STS Policy Publication Drift and Enforce-Mode Readiness Gate

You publish MTA-STS in testing mode. You must move to enforce mode without a sender that fails delivery, and you must never change the policy file without a change of the _mta-sts TXT id, because every sender keeps the old cached policy until max_age runs out.

This Actor checks, for each of your domains:

  • the _mta-sts TXT record (how many, and the id tag),
  • the policy file at https://mta-sts.<domain>/.well-known/mta-sts.txt (HTTP status, content type, redirect, certificate name, SHA-256),
  • the policy body (version, mode, max_age, mx patterns),
  • the live MX set against the mx patterns,
  • the _smtp._tls TXT record (the TLS-RPT rua values),
  • your TLS-RPT report files (RFC 8460 JSON), grouped into failure groups,
  • and everything above against the snapshot that the last run wrote.

It then gives one readiness verdict for each domain: ready, not_ready or overdue.

The Actor uses DNS over HTTPS and one HTTPS GET for each domain. It opens no SMTP connection, because outbound port 25 is blocked on most cloud hosts. It follows no redirect, as RFC 8461 section 3.3 requires. It refuses a report URL that resolves to a private or reserved address.

What you put in

FieldWhat it holds
domains1 to 200 mail domains.
tlsrpt_report_filesOptional. 0 to 200 RFC 8460 JSON files (.json or .json.gz). Each item is an HTTPS URL, a record key of the default key-value store, or store-name/record-key. Limits for each file: 5 MB download, 50 MB after decompression.
target_modetesting or enforce. Default: enforce.
gate_dateOptional. One ISO date for each domain, for example {"example.com": "2026-08-20"}. The key * holds for every domain. A key that names a domain outside domains is skipped with a note in the run summary.
clean_window_daysDefault 7. The number of report days with no failure session that a domain needs before it can move into enforce mode.
alert_webhook_urlOptional. One public HTTPS URL. One JSON POST when the run finds a policy defect.
state_store_nameThe named key-value store that holds the snapshot of each domain. 3 to 63 letters, digits and hyphens, and it must not start or end with a hyphen. Default mta-sts-policy-state.

A run with empty input {} uses the schema defaults and succeeds.

What you get

Four record types in one dataset, and a Markdown digest in the DIGEST record of the default key-value store.

  1. domain — one record for each domain: stsTxtCount, stsId, policyHttpStatus, contentType, redirectFound, certificateNameMatch, policyVersion, mode, maxAge, mxPatterns, liveMxHosts, mxNotCovered, tlsrptRua, policySha256.
  2. policy-defect — one record for each defect, with the previous value and the current value.
  3. tlsrpt-failure — one record for each failure group: report date, sending organization, receiving MX, result type, failed session count.
  4. readiness — one record for each domain: current mode, target mode, days clean, verdict, and the next required change.

A run also writes one run-summary record.

Defect codes

CodeWhat it means
policy_changed_id_unchangedThe policy file changed, the TXT id did not. Senders keep the old policy until max_age runs out.
id_changed_policy_unchangedThe TXT id changed, the policy file did not. Every sender fetches the same policy again for nothing.
mode_changedThe mode changed between two runs, for example enforce back to testing.
mx_not_coveredA live MX host that no mx pattern of the policy covers. In enforce mode a sender refuses that host.
policy_redirectThe policy URL answers a redirect. RFC 8461 section 3.3 forbids it.
wrong_content_typeThe policy file is not served as text/plain.
multiple_sts_records_mta-sts publishes more than one v=STSv1 record. A sender must find exactly one.
max_age_out_of_rangemax_age is not between 1 and 31557600 seconds.
tlsrpt_missing_smtp._tls publishes no v=TLSRPTv1 rua value, so no reporter can tell you about a failed session.
sts_record_missing_mta-sts publishes no v=STSv1 record.
sts_record_no_idThe record has no valid id tag (1 to 32 letters or digits).
policy_unreachableThe policy URL could not be read.
certificate_name_mismatchThe certificate of mta-sts.<domain> does not carry that name.
policy_http_errorThe policy URL answers a status other than 200.
policy_malformedThe policy body does not follow RFC 8461 section 3.2.

The first three codes need the snapshot of the last run. The first run of a domain writes the baseline and reports only the defects that need no history.

A domain that publishes no v=STSv1 record at all gets sts_record_missing alone, because a sender never reads the policy file of that domain. A failed DNS lookup is marked in the domain record and is never read as a missing record.

Verdicts

  • ready — the published mode is the target mode and the domain has no defect.
  • not_ready — a defect, an unknown mode, or a clean window that is too short.
  • overdue — not_ready after the gate date of the domain.

A verdict is a business result. The run ends SUCCEEDED for every verdict. Only a malfunction gives a FAILED run.

State between runs

For each domain the Actor keeps one record in the named key-value store (mta-sts-policy-state by default) with the last TXT id, the last policy SHA-256, the last mode, and the report days with their failure counts. It also keeps the ids of the report files that it has parsed, so a file that you pass again is not parsed twice and not charged twice.

Use the same state_store_name in each run of the same domains.

How to run it

Run it on an Apify schedule once each day: it finds policy drift before the sender caches run out. Run it again after each MX change, certificate change or policy deploy, and before the gate date.

Pricing

This Actor uses pay per event.

EventUnitPrice (USD)When
run-startedone Actor run0.002Once for each run with a valid input.
domain-checkedone domain0.003Once for each domain whose DNS records and policy file are checked and compared with its snapshot.
tlsrpt-report-parsedone report file0.003Once for each new report file that is downloaded and parsed. A report that the state already holds is skipped and not charged. A file that fails is not charged.
policy-defect-flaggedone defect0.01Once for each policy defect record of one domain.

What one daily run costs

10 domains, 2 new report files, no defect:

EventCountPriceCost (USD)
run-started10.0020.0020
domain-checked100.0030.0300
tlsrpt-report-parsed20.0030.0060
policy-defect-flagged00.010.0000
Total0.0380

That is USD 1.14 for 30 daily runs.

How the size of the run moves the bill

DomainsReport filesDefectsUncapped (USD)Charged (USD)
10200.03800.0380
503050.29200.2920
200200401.60201.6020
2002002003.20203.0000

The maximum total charge of a run is USD 3.00 by default. When a run reaches it, the Actor stops charging, writes what it has, says so in the status message and in run.chargeLimitReached, and still ends SUCCEEDED. Raise the maximum in the run options when you check many domains.

Limits

  • DNS over HTTPS with public resolvers, at most 1200 lookups in one run.
  • One policy GET for each domain, 10 s timeout, 64 KB maximum body, no redirect.
  • Report files: 5 MB download, 50 MB after decompression, 100:1 compression ratio.
  • The webhook URL and every report URL must be public HTTPS. The Actor checks the address of each hop and refuses a private, loopback, link-local or reserved one.