Skip to content

MTA-STS ​

Read and change a domain's MTA-STS configuration, including the DNS records it requires and what was last observed in DNS.

MTA-STS tells sending servers to require TLS when delivering to your domain. It needs two DNS records and a policy file served over HTTPS. MailShield can host the policy file for you.

Get MTA-STS Configuration ​

GET /api/v1/domains/{id}/mta-sts

Path Parameters ​

ParameterTypeDescription
iduuidDomain ID

Response ​

json
{
  "data": {
    "enabled": true,
    "mode": "testing",
    "maxAge": 604800,
    "mxPatterns": ["mail.example.com", "*.example.com"],
    "hosted": true,
    "policyId": "ha7rqCmk",
    "policyContent": "version: STSv1\nmode: testing\nmx: mail.example.com\nmx: *.example.com\nmax_age: 604800\n",
    "policyUrl": "https://mta-sts.example.com/.well-known/mta-sts.txt",
    "updatedAt": "2024-01-15T10:30:00Z",
    "expectedRecords": [
      {
        "purpose": "mta-sts-policy-host",
        "name": "mta-sts.example.com",
        "type": "CNAME",
        "value": "mta-sts.mailshield.app."
      },
      {
        "purpose": "mta-sts-policy-id",
        "name": "_mta-sts.example.com",
        "type": "TXT",
        "value": "v=STSv1; id=ha7rqCmk"
      }
    ],
    "observed": {
      "dnsId": "ha7rqCmk",
      "policyFetched": true,
      "policyMode": "testing",
      "valid": true,
      "errors": [],
      "checkedAt": "2024-01-15T10:30:00Z",
      "policyIdInSync": true
    }
  }
}

Fields ​

FieldDescription
enabledWhether MTA-STS is switched on for this domain
modetesting reports failures without blocking delivery; enforce requires TLS
maxAgeHow long receivers cache the policy, in seconds (86400 to 31557600)
mxPatternsThe MX hosts the policy authorizes
hostedWhether MailShield serves the policy file
policyIdThe current policy ID. This is what belongs in the _mta-sts TXT record
policyContentThe policy file body that would be served
expectedRecordsThe DNS records this configuration requires
observedWhat the most recent check saw. null until the domain has been checked once

The observed block ​

observed is the answer to "is this actually live yet". It reflects the last check rather than your configuration. Checks run every 2 hours, or when you call Run DNS Checks, so observed doesn't change between checks:

FieldDescription
policyFetchedtrue when the policy file was retrieved over HTTPS. This also confirms the policy host completed a TLS handshake for your domain, so it is the signal to wait on after publishing the CNAME
dnsIdThe policy ID currently published in your _mta-sts TXT record
policyIdInSyncfalse when dnsId differs from policyId (see Policy ID rotation)
valid / errorsWhether the observed setup validates, and why not

Waiting for the policy host

After you publish the CNAME, the policy host needs to issue a certificate for mta-sts.<your-domain> before it can serve anything. Run a check and wait for policyFetched to become true rather than guessing at a delay (see Scripted setup). Publishing DNS before enabling the domain will not work: the policy host does not issue certificates for domains that are switched off.

Example ​

bash
curl -H "Authorization: Bearer ms_your_token" \
  https://app.mailshield.app/api/v1/domains/550e8400-e29b-41d4-a716-446655440000/mta-sts

Update MTA-STS Configuration ​

PATCH /api/v1/domains/{id}/mta-sts

Request Body ​

FieldTypeDescription
enabledbooleanSwitch MTA-STS on or off
modestringtesting or enforce
maxAgeintegerPolicy lifetime in seconds (86400 to 31557600)
mxPatternsstring[]MX hosts the policy authorizes
hostedbooleanWhether MailShield serves the policy file

All fields are optional; omitted fields keep their current value. Unknown fields are rejected.

Response ​

Returns the same shape as GET, reflecting the updated configuration.

Example ​

bash
curl -X PATCH \
  -H "Authorization: Bearer ms_your_token" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "mode": "testing", "mxPatterns": ["mail.example.com"]}' \
  https://app.mailshield.app/api/v1/domains/550e8400-e29b-41d4-a716-446655440000/mta-sts

Errors ​

  • 400 - Invalid body, or enforce requested with an empty mxPatterns
  • 403 - Hosting the policy (hosted: true) needs a verified domain and a plan that includes hosted MTA-STS, or the token is read-only
  • 404 - Domain not found

Start in testing mode

New domains default to mode: testing. Serving enforce to a domain whose MX set has not been confirmed causes hard delivery failures on live mail, so switch to enforce only once observed.policyFetched is true and the reports look clean.

Enabling enforce with an empty mxPatterns is rejected with a 400, because that would serve a policy matching no host and break delivery for every strict-TLS sender.


Policy ID rotation ​

The id= in your _mta-sts TXT record is what tells receivers to drop a cached policy. If it does not change, receivers keep honoring the old policy until max_age expires, which can be a week.

MailShield rotates policyId automatically whenever the served policy changes, meaning any change to mode, maxAge or mxPatterns. When that happens:

  1. PATCH returns the new policyId and an updated expectedRecords
  2. observed.policyIdInSync reads false until you republish the TXT record
  3. Republish _mta-sts.<domain> with the new v=STSv1; id=<policyId>

After a check, treat observed.policyIdInSync === false from GET as "DNS needs republishing". This is the drift that is otherwise invisible: the policy you serve and the policy receivers cache silently diverge.


Scripted setup ​

A full enable-and-publish loop:

bash
DOMAIN_ID="550e8400-e29b-41d4-a716-446655440000"
AUTH="Authorization: Bearer ms_your_token"

# 1. Enable in testing mode with your MX hosts (hosting needs a verified domain)
curl -sX PATCH -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"enabled": true, "mode": "testing", "mxPatterns": ["mail.example.com"], "hosted": true}' \
  "https://app.mailshield.app/api/v1/domains/$DOMAIN_ID/mta-sts"

# 2. Read the records to publish
curl -s -H "$AUTH" \
  "https://app.mailshield.app/api/v1/domains/$DOMAIN_ID/mta-sts" \
  | jq -r '.data.expectedRecords[] | "\(.name) \(.type) \(.value)"'

# 3. Publish those records in your DNS, then run a check every minute until
#    the policy is live. Reading GET alone would only show a change after the
#    next scheduled check, which can be up to 2 hours away.
until curl -s -X POST -H "$AUTH" \
  "https://app.mailshield.app/api/v1/domains/$DOMAIN_ID/checks" \
  | jq -e '.data.results.mtaSts.policyFetched == true' >/dev/null; do sleep 60; done

Each call runs every check for the domain, which can take several seconds. One call a minute stays well under the limit of 5 checks per minute per domain.

Monitor and secure your email domains.