authentication.md
Authentication
How MachineMail gets SPF, DKIM and DMARC results for inbound mail without trusting forgeable headers, checks From alignment and feeds the trust level and score.
The From header is free text: anyone can write billing@stripe.com in it. Authentication is how a receiver learns whether the sending domain actually stood behind the message. The trust report summarises it in four fields:
"authentication": { "spf": "pass", "dkim": "pass", "dmarc": "pass", "aligned": true }
What each check means
- SPF checks that the server that delivered the message is allowed to send for the envelope sender's domain (the
Return-Path, not theFrom). - DKIM checks a cryptographic signature added by the sending domain, named by the signature's
d=tag. - DMARC checks that SPF or DKIM passed for a domain that matches the
Fromdomain, and applies the domain owner's published policy. - ARC carries authentication results across forwarders and mailing lists that would otherwise break SPF and DKIM.
SPF or DKIM passing on its own proves only that some domain sent the message. A phisher can pass SPF for paypa1-security.com while writing From: PayPal <service@paypal.com>. That is why alignment matters.
Where the results come from
MachineMail does not re-run DNS lookups or signature checks. It uses the results recorded by the mail service that received the message for MachineMail, and nothing the sender could have written:
- Verdicts reported by the receiving provider. When the inbound provider reports SPF, DKIM and DMARC results alongside the message, they win. The reasons then include
Authentication verdicts from <provider>. - The
Authentication-Resultsheader added by our receiving server. Only the topmost header carrying our receiving server's identifier counts. - Nothing else. Any other
Authentication-Resultsheader,ARC-Authentication-ResultsandReceived-SPFis ignored, because a sender can add them to a message before it reaches us. A forged header that claimsdmarc=passgets no credit.
If none of this is available, every field is none, never pass.
Results are mapped to the report's values:
| Field | pass |
fail |
other values |
|---|---|---|---|
spf |
pass |
fail, hardfail, permerror |
softfail and neutral are kept as such; anything else is none |
dkim |
any signature passed | a signature failed (fail, neutral, policy, permerror) and none passed |
none |
dmarc |
pass |
fail, quarantine, reject, permerror |
none |
When no trusted results are found, every field is none and the reasons say No trusted authentication results. If the message carried Authentication-Results headers from other servers, the reasons also say how many were ignored. A DKIM-Signature header with no verification result is noted in the reasons but does not count as a pass.
authentication_results on the message
Every inbound message (and the message.received webhook payload) also carries the results themselves as authentication_results, the field AgentMail clients read. It comes from the same sources as the summary above, but keeps more detail:
"authentication_results": {
"spf": "pass", "spf_domain": "mail.linear.app",
"dkim": "pass", "dkim_domain": "linear.app",
"dmarc": "pass", "dmarc_domain": "linear.app"
}
- Verdicts as recorded.
spf,dkimanddmarcuse theAuthentication-Resultsvocabulary:pass,fail,none,neutral,softfail,temperror,permerror. The summary's mapping above does not apply: apermerrorstayspermerror. A provider's "nothing to evaluate" (GRAYfrom Amazon SES) isnone; a provider check that could not run istemperror. - Domains.
spf_domainis the envelope sender domain SPF checked,dkim_domainthed=domain of the signature the verdict is about (for a pass, a passing signature, preferring one aligned withFrom; only forpassorfail), anddmarc_domaintheFromdomain. When the provider's verdicts are used, the domains come from our receiving server's header, or for DKIM from the message's only signing domain. - Absent, not
none. A mechanism nobody evaluated is left out. The whole field is absent on sent mail, on mail injected through the API, on mail whose results no trusted server recorded, and on messages received before the field was introduced. - Only what speaks for the sender, as in AgentMail.
spfis kept only whenspf_domainaligns with theFromdomain (shares its organisational domain, somail.acme.comaligns withacme.com), or with theSenderdomain whenFromnames several domains; an unaligned SPF result is withheld whatever its value, and so is a provider's SPF verdict when no trusted header names the domain.dkimanddmarcare withheld whenFromnames more than one domain or cannot be parsed. The summary above still records every result. - Domains, not decisions.
dkim_domainis the signing domain as written: compare it with theFromdomain before relying on a DKIMpass, and decide withtrust.level. Do not reject mail ondmarc: failalone, since forwarding breaks it. arc_sealernames the forwarder whose ARC seal the verdicts came from. MachineMail does not validate ARC chains and by default ignores ARC results (see above), so it does not send this key.
Alignment
aligned is true when a passing DKIM d= domain, or the SPF domain when SPF passed, has the same organisational domain as the From address. A DMARC pass reported by the receiving provider also counts as aligned, since DMARC itself requires alignment. The organisational domain is the registrable part: mail.acme.com aligns with acme.com, and suffixes such as co.uk are handled, so a.example.co.uk and b.example.co.uk align but example.co.uk and other.co.uk do not.
The reasons list says which domain passed and whether it aligned:
"reasons": [
"SPF passed for mail.linear.app",
"DKIM passed for linear.app",
"DMARC passed",
"Authenticated domain aligns with From domain linear.app"
]
How authentication feeds the verdict
Authentication affects the trust report in four places.
Level.
- Any
failin SPF, DKIM or DMARC makes the message at leastsuspicious. verifiedrequires DMARCpasswithaligned: true, plus prior correspondence or an allow-list entry. A receive allow entry also blocks every sender not on the list, so prior correspondence is the usual route.knownrequires at least one of SPF, DKIM or DMARC to pass. Mail from a domain every MachineMail organization shares (such asmachinemail.dev) also needs prior correspondence with that exact address or an allow-list entry; otherwise it isunknown.- With nothing passing and nothing else wrong, the level is
unknown, and the score is capped at 0.7.
Score. The authentication component carries 25% of the score:
| Result | Component |
|---|---|
| DMARC pass and aligned | 1.0 |
| SPF or DKIM pass, aligned | 0.8 |
| a pass, not aligned | 0.5 |
| nothing passed | 0.3 |
| anything failed | 0 |
Classifier. Failures raise the phishing and spam probabilities (DMARC fail most, then DKIM, SPF and SPF softfail), no passing authentication raises them a little, and an aligned DMARC pass lowers them. Mail that claims to come from your own domain without passing SPF or DKIM is flagged as spoofing.
Labels. If DMARC failed, or SPF and DKIM both failed, the message gets the UNAUTHENTICATED label and a message.received.unauthenticated event. It keeps the INBOX label, but message lists hide UNAUTHENTICATED mail unless you pass include_unauthenticated=true. The MCP wait_for_message tool does not wake for it.
Mail injected through the API
Messages posted to POST /inbound/raw (which needs a key with the explicit inbound_raw permission) carry whatever headers the caller wrote. MachineMail ignores all of them: authentication is always none, so injected mail is never verified or known. The message gets received_via: "raw" and the reason Injected via API (/inbound/raw); authentication results not verified. Use it for tests and fixtures.
Common patterns
| What you see | Usually means |
|---|---|
spf: pass, dkim: pass, dmarc: pass, aligned: true |
Real mail from the domain in From. |
spf: pass, dkim: none, dmarc: none, aligned: false |
Sent through a provider that signs with its own domain, or a lookalike domain. Check sender.domain. |
dkim: fail |
The message was modified in transit, often by a forwarder or mailing list, or the signature is forged. |
everything none |
No trusted results: the receiving server recorded none, or the mail was injected through the API. |
dmarc: fail |
The domain owner says this message is not theirs. Treat it as spoofed. |