Appearance
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-stsPath Parameters
| Parameter | Type | Description |
|---|---|---|
id | uuid | Domain 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
| Field | Description |
|---|---|
enabled | Whether MTA-STS is switched on for this domain |
mode | testing reports failures without blocking delivery; enforce requires TLS |
maxAge | How long receivers cache the policy, in seconds (86400 to 31557600) |
mxPatterns | The MX hosts the policy authorizes |
hosted | Whether MailShield serves the policy file |
policyId | The current policy ID. This is what belongs in the _mta-sts TXT record |
policyContent | The policy file body that would be served |
expectedRecords | The DNS records this configuration requires |
observed | What 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:
| Field | Description |
|---|---|
policyFetched | true 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 |
dnsId | The policy ID currently published in your _mta-sts TXT record |
policyIdInSync | false when dnsId differs from policyId (see Policy ID rotation) |
valid / errors | Whether 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-stsUpdate MTA-STS Configuration
PATCH /api/v1/domains/{id}/mta-stsRequest Body
| Field | Type | Description |
|---|---|---|
enabled | boolean | Switch MTA-STS on or off |
mode | string | testing or enforce |
maxAge | integer | Policy lifetime in seconds (86400 to 31557600) |
mxPatterns | string[] | MX hosts the policy authorizes |
hosted | boolean | Whether 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-stsErrors
400- Invalid body, orenforcerequested with an emptymxPatterns403- Hosting the policy (hosted: true) needs a verified domain and a plan that includes hosted MTA-STS, or the token is read-only404- 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:
PATCHreturns the newpolicyIdand an updatedexpectedRecordsobserved.policyIdInSyncreadsfalseuntil you republish the TXT record- Republish
_mta-sts.<domain>with the newv=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; doneEach 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.