SPF checks the envelope, not the From header
Which SPF record a receiver actually reads, why that means your apex record is often not consulted at all, and the two things that catch people out when it isn't.
Yowl never touches your apex SPF record, - your apex is not even consulted for the mail we send. Our sending identity is a TXT record on a send. subdomain, on a label nothing is using, which means connecting us cannot push your apex over a lookup limit, cannot create a second v=spf1, and cannot be broken by a later edit to the apex. Nothing to merge, nothing to migrate, nothing to undo.
That is worth more than it sounds during a move. The apex SPF record is the single most dangerous line in a domain’s zone — shared by every sender you have, it fails silently, and the failure is global. A provider that asks you to edit it - is asking you to take a risk. We don’t, and the reason is an evaluation rule almost no setup page states.
⭐ You can check which record is in play without reading a header. Yowl measures every record it expects, live, with the published value beside the expected one, and parses your DMARC reports into the list of who actually sent as you & whether they are aligned.
Connect Claude to your organisation over MCP and “is our sending set up right?” is one question with Yowl returning both answers.
Almost every SPF setup page tells you what to publish - and skips the question that decides if it matters: which record does the receiver look up?
Not the one belonging to the address your reader sees. SPF authenticates the envelope sender — MAIL FROM, the Return-Path negotiated before the message body is sent. That domain is the name queried for a TXT record. The From header isnt consulted by SPF.
Why that is the whole story
If a provider sends your mail using its own subdomain as the envelope sender — send.example.com, mail.example.com, a per-customer bounce domain — then the record evaluated for that stream is the one on that name. Your apex record is not read.
For that stream:
- The apex’s ten-lookup budget isnt relevant, - none of it’s includes are evaluated;
- you cannot create a second apex
v=spf1, - you are not adding to the apex; - a later edit to the apex won’t break it.
Better than “be careful with your SPF record”. The apex is not being handled carefully. It is not in the code path. In the catalogue of apex failure modes is a description of what goes wrong when the envelope domain is the apex — and for a well-behaved transactional provider it isn’t.
None of this is new. Custom MAIL FROM, bounce domains and per-customer return paths are standard at every serious sending platform. The part that is rarely written down is the evaluation rule that makes it work, which is why people keep flattening an apex record that isn’t consulted.
Why DMARC has to agree
SPF passing on send.example.com does nothing for a reader looking at you@example.com unless the two are aligned, and that is DMARC’s test, not SPF’s.
DMARC’s default is aspf=r, relaxed: the same organisational domain is enough, so send.example.com aligns with a From of @example.com. Publish aspf=s and it does not — strict wants the exact domain. A subdomain envelope stops aligning the day you set it. Worth checking before you tighten anything.
DKIM alignment is the second, independent path. A message whose SPF alignment fails can still pass DMARC on an aligned DKIM signature, which is why DKIM is worth getting right first.
The failure to avoid
The moment mail is forwarded, the header it produces is the most confusing thing. A real one, from a shared mailbox receiving mail forwarded by an Exchange server:
Authentication-Results: mx.google.com;
dkim=neutral (body hash did not verify) header.i=@example.com;
spf=pass (domain of user@mydomain.com designates 1.1.1.1 as permitted sender) smtp.mailfrom=user@mydomain.com;
dmarc=fail (p=REJECT dis=NONE) header.from=example.com
SPF passed and DMARC failed, on the same message.
Nothing broken about the SPF record, - no amount of editing it would change this line. The pass is for mydomain.com, the From header says example.com, and by the rule above those are two different questions — so DMARC discards the pass as unaligned.
⛔ A forward breaks both of DMARC’s legs at once:
- DKIM dies because the forwarder modified the message. It does not take much: re-serialising MIME so a part header becomes
charset="UTF-8"instead ofcharset=UTF-8is enough, since that text is inside the signed body. - SPF dies as an alignment matter because the forwarder has to rewrite the envelope — it now owns the bounces, so
MAIL FROMbecomes it’s address rather than the author’s. The pass is now about the wrong domain.
Forwarding a message arrives authenticated as the forwarder and unauthenticatable as its author, - if the author publishes p=reject it is refused. ⭐ The repair is not on the sending side & not in your records — it is ARC. The forwarding hop records what SPF, DKIM and DMARC looked like before it touched the message, seals that statement. A receiver that trusts the sealer can honour it over the later failure. Gmail both seals and validates it.
⚠ Expect your DMARC reports to keep saying fail for these flows even though delivery is fine. ARC is a receiver-side policy override, not a re-pass — so a forwarded stream that is being delivered correctly still shows up as failures in the aggregate data.
Shoudl not be read as a misconfiguration.
Two gotchas
A CNAME at that name blocks the record. DNS does not let a name carrying a CNAME carry other data, so if send.example.com is already a CNAME — pointing at a CDN, a status page, an old host — the SPF TXT cannot be published there at all. This is an onboarding footgun, the error can be cryptic. Check the label is unused before you commit to it:
dig +short CNAME send.example.com # anything here means pick another label
dig +short TXT send.example.com # what is actually published
Bounces are not the same check. A bounce message arrives with a null reverse-path, and with no MAIL FROM domain to look up there is nothing for SPF to evaluate against; receivers check the HELO/EHLO name.
Which record is in play
Send a message & read its Authentication-Results header. The domain after smtp.mailfrom= is the one that was evaluated:
Authentication-Results: mx.example.net;
spf=pass smtp.mailfrom=send.example.com;
dmarc=pass header.from=example.com
Two different domains on two lines, both correct. If smtp.mailfrom shows your apex, then the apex record is in the path and the failure modes in the other post apply to you.