Gmail + Yahoo — February 2024
The original bar: any sender over 5,000 messages/day to personal accounts must authenticate with SPF and DKIM, publish DMARC, and offer one-click unsubscribe on marketing mail. This is the wave that started it all.
The three DNS-based email authentication protocols, SPF, DKIM, and DMARC, that get your app's mail trusted by inbox providers and stop attackers from spoofing your domain.
In the last lesson you added your sending domain to Resend, pasted the DNS records it handed you at your registrar, and watched the dashboard flip the domain to Verified. You sent a test email and it landed in your inbox. By every signal in front of you, the job is done.
It isn’t, and the reason is the most important idea in this chapter: deliverability is your app’s responsibility, not your vendor’s. When Resend says Verified, it is telling you one narrow thing: Resend can now sign mail on your behalf. That is not Gmail telling you it will trust the mail, and it is not Outlook promising to put it in the inbox instead of the spam folder. It also says nothing about stopping a stranger from sending mail that displays your domain in the From line to defraud your own customers.
Those are two separate gaps, and this lesson closes both. The first is trust at the receiving end. Since 2024, the major mailbox providers have moved from “we might spam-folder unauthenticated mail” to “we reject it outright,” and the bar keeps creeping down toward smaller senders. The second is brand protection: stopping spoofers from forging your From: address. The same three protocols close both gaps, and neither is something Resend can do for you, because the fix lives in your DNS and your operational decisions, not in their infrastructure.
Those three protocols are SPF , DKIM , and DMARC , and they stack in a specific order. SPF and DKIM are two peers you publish, and DMARC sits on top of both and makes them mean something. By the end of this lesson you will be able to read every record Resend gave you instead of pasting it blindly, and you will have a staged rollout plan for turning the protection on without breaking your own mail. There is almost no application code here. The deliverables are DNS records and a playbook, and they will pay you back for the rest of your career.
To understand why three separate protocols exist, you have to understand the thing they are bolted onto. Email moves over SMTP , a protocol designed in an era when every machine on the network was run by someone you knew. SMTP has no built-in way to verify who a message is from. The From: header is just text the sending server types in, and nothing stops a server from writing From: ceo@yourcompany.com on a message it composed itself. Think of the return address handwritten on a physical envelope: the post office delivers the letter regardless of what you scribble in that corner, because checking it was never the post office’s job. SMTP works the same way, and that open-trust default is the hole all three protocols exist to patch. Keep the envelope picture in mind, because it runs through this whole lesson.
Before any protocol detail, there is one fact to absorb: an email does not have one identity field. It has several, and they can disagree. This is the root of nearly every misunderstanding in this topic, so we will look at it first.
bounces@send.yourapp.com Answers: Which server was allowed to send this? The hidden return address the receiving server bounces failures to — the recipient never sees it. d=send.yourapp.com Answers: Was this message altered, and who signed it? Stamped inside a signature header — invisible to the reader. YourApp <hello@send.yourapp.com> Answers: Does the visible sender match the checks that passed? The only field the human actually reads in their inbox. Sit with that illustration, because it carries the whole lesson. The field a human reads in their inbox, the visible From:, is not the field SPF checks, and it is not the field DKIM checks. SPF validates the envelope sender , the hidden return address used during the SMTP handshake. DKIM validates the domain named in its own signature. Both of those are separate from the From: line your customer actually sees. A spammer can make SPF and DKIM pass perfectly on their own domain while writing yourbank.com in the visible From:, and unless something explicitly compares the passing checks back to that visible line, nobody catches it. Keeping those three fields apart in your head is what separates understanding this topic from being confused by it.
With that picture in place, here is what each protocol contributes, framed as a layered stack rather than three unrelated DNS settings:
From: the user sees, and if not, what should I do about it?SPF and DKIM are the two peers you publish. DMARC is the policy that turns them from a pair of disconnected checks into real brand protection. We will build them in that order.
SPF is the simplest of the three, so we start here. An SPF record is a single TXT record published at your sending domain that lists who is allowed to send mail for it. The list can be raw IP ranges, or, far more commonly, include: references that point at another provider’s published list of IPs. When mail arrives, the receiving server looks at the IP address of the machine that connected to it and checks whether that IP is covered by your SPF record. If the server is authorized, SPF passes; if it is unknown, SPF fails.
Here is the SPF record Resend asked you to add in the last lesson, on your sending subdomain:
v=spf1 include:_spf.resend.com ~allRead it left to right. v=spf1 is the version marker, and every SPF record opens with it. include:_spf.resend.com is the workhorse: it tells the receiver to look up Resend’s own SPF record and treat all the IPs it authorizes as authorized for you too. Resend maintains the actual list of sending IPs behind that name, so when they add or rotate servers, your record keeps working without you touching it. That is the whole point of the include mechanism . The last token, ~all, is the catch-all: for any server not matched above, softfail.
That final token is a real decision, not boilerplate, so make it deliberately. The choice is between ~all and -all. ~all is a softfail , which means “this server probably isn’t authorized for me, but don’t hard-reject the mail on that basis alone.” -all is a hardfail : “this server is definitely not authorized; treat any mail from it as a failure.” Hardfail is stricter and feels more correct, but it is only safe once you are certain that every path sending mail from this subdomain goes through Resend. Resend defaults to ~all, and that is the right default: start permissive, and tighten to -all only after you have confirmed Resend is the sole sender on the subdomain. Reach for the stricter setting when the evidence supports it, not on day one.
Here is a point the next two sections will pay off: SPF authenticates the envelope MAIL FROM, not the visible From:. Go back to the three-fields illustration. SPF only ever looks at that hidden return address from the SMTP handshake; it has no opinion about the From: line your customer reads. This is half of why DMARC has to exist, and we will close the loop on it shortly.
SPF carries one trap that bites hard in production, and it is worth understanding now because it shapes how you architect your domains. SPF allows a maximum of 10 DNS lookups when it evaluates a record, and every include: you add costs one of those lookups. Picture an apex domain that already includes Google Workspace, Microsoft 365, and three SaaS vendors that send on your behalf, a perfectly ordinary setup. You are close to the ceiling. Someone adds Resend’s include: to that same record, the evaluation tips over 10 lookups, and the receiving server gives up with a permerror. The consequence is severe and easy to miss: SPF doesn’t just fail for the new sender, it fails for every sender on that domain at once. The fix is structural, and it is the strongest single argument for the dedicated sending subdomain you have been using all along. When Resend sends from send.yourapp.com, that subdomain has its own SPF record with essentially nothing in it but Resend’s one include:. The lookup limit is never anywhere near in play, and your apex’s crowded SPF record is left untouched. The next lesson makes the full case for splitting your domains; for now, just notice that the split quietly defuses this problem.
There is one more record to recognize. Alongside the SPF entry, Resend also asks you to add an MX record on the sending subdomain. That sets up a custom return path so bounces and complaint feedback flow back to Resend instead of vanishing, which, as a bonus, is also what keeps SPF aligned on that subdomain. You don’t need to do anything with that feedback yet; just add the record and know it exists. A later lesson puts the bounce and complaint data it carries to work.
SPF answers whether mail came from an allowed server. It says nothing about whether the message itself was tampered with on the way, or whether the sender genuinely holds the keys to the domain they claim. DKIM fills both of those gaps, and it does it with public-key cryptography.
If signatures are fuzzy for you, here is the only model you need. A keypair is two matched keys. The private key signs data and is kept secret. The public key can verify that signature and can be shared with anyone. The key point is that holding the public key lets you check a signature but never forge one, because only the private key can produce a valid signature in the first place.
DKIM applies that directly to email. When Resend sends your message, its MTA signs the message with a private key and attaches a DKIM-Signature header. The matching public key is published in your DNS, as a TXT record at a special selector subdomain. Resend’s selector is resend, so the public key lives at resend._domainkey.send.yourapp.com. When the message arrives, the receiver reads the selector out of the signature header, fetches the public key from that exact DNS location, and verifies the signature against the message it received.
The record looks like this, and note right away that you would never type it by hand:
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ…v=DKIM1 is the version, k=rsa names the key algorithm, and p= carries the public key itself. That base64 blob is truncated above; in reality it runs for hundreds of characters. Resend generates this keypair for you, keeps the private half, and publishes the public half. You copy the value into your registrar exactly as given. You never author it, and Resend can rotate it over time without you re-learning anything.
Let’s trace the two halves of how this works, because the non-obvious mechanic is that the public key lives in your DNS and the receiver pulls it down at the moment of verification.
DKIM-Signature reads DKIM-Signature selector + d= domain resend._domainkey.send.yourapp.com
public key
published · anyone may read What does a passing DKIM check actually buy you? Two distinct guarantees that students often merge into one. The first is integrity: the parts of the message that were signed arrived exactly as they were signed, with no relay altering the body or the key headers in transit. The second is authorization: whoever produced the signature genuinely held the private key for the domain named in the signature’s d= tag. Those are real and valuable. But notice the gap, the same one SPF had. A passing DKIM check proves things about the d= domain, and the d= domain does not have to be the domain in the visible From:. On its own, DKIM does not prove that the From: your customer reads is legitimate. That, once again, is DMARC’s job, and now you have seen the gap from both sides.
We have pointed at this from both directions; now we put it together. This is the conceptual centre of the lesson, so slow down here.
Start with the hole that SPF and DKIM, by themselves, leave wide open. Both checks pass independently of the visible From: header. So picture an attacker. They register evil.com, a domain they fully control. They set up flawless SPF and DKIM for evil.com; nothing stops them, because it’s their own domain. Then they send mail with the visible From: set to security@yourbank.com. What happens at the receiver? SPF passes, because the connecting server really is authorized for evil.com. DKIM passes, because the message really is validly signed for evil.com. Both checks are green. And nothing in SPF or DKIM checks the obvious thing: the From: the human will read says yourbank.com, yet neither passing check has anything to do with yourbank.com. Authentication “worked,” and your brand was spoofed anyway.
DMARC is the rule that closes that hole, and it does it through one idea: alignment . The core takeaway of this entire lesson is a single sentence, so read it slowly:
DMARC requires that at least one of SPF or DKIM both passes and is aligned with the visible
From:domain, meaning the domain that passed is the same domain the human sees.
In the spoof above, both checks pass, but both pass on evil.com while the From: says yourbank.com. Nothing is aligned, so DMARC fails, and now the receiver has an explicit instruction to act on. Let’s make alignment visible by running the legitimate case and the spoof case side by side, this time forging your domain rather than a bank’s, so the stakes are concrete.
bounces@ send.yourapp.com
aligned
send.yourapp.com
aligned
hello@ send.yourapp.com reference bounces@ evil.com
not aligned
SPF passes — but on evil.com evil.com
not aligned
DKIM passes — but on evil.com security@ yourapp.com reference forged — the human reads this So, concretely, a DMARC record is a TXT record published at _dmarc.yourapp.com that declares two things SPF and DKIM never could: first, the policy to apply when no aligned check passes, and second, where to send reports about what’s happening to your mail. It is the only one of the three protocols that carries a policy and a feedback channel. SPF and DKIM are pure checks with no notion of what to do on failure.
That is the whole stack, so let’s name the layering one more time, cleanly. SPF and DKIM are the checks. DMARC is the rule that binds those checks to the From: your user reads, and tells receivers what to do when the binding fails. Hold onto that, and the three protocols stop being three things to memorize and become one coherent system.
Before we read an actual DMARC record, let’s pin down the mapping the whole lesson hinges on.
Match each protocol to the one thing it checks. Click an item on the left, then its match on the right. Press Check when done.
MAIL FROM.d= signing domain.From: and sets the policy on failure.Knowing DMARC exists is one thing; being able to read and tune the record is the skill you actually need, both for this chapter’s project and for steering an AI agent that’s editing your DNS. So let’s take a complete, realistic DMARC record apart field by field.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;Each field below is one you will actually read or adjust, so we’ll step through them one at a time.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;The version marker. It’s required, and it must come first; a record that doesn’t start with this is ignored entirely.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;The policy applied to the apex when no aligned check passes: none (monitor only, reporting but taking no action), quarantine (deliver to spam), or reject (refuse the mail outright). This is the dial the next section ramps, and the one that can break real mail, which is why it’s marked red.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;Where to send aggregate reports: daily XML roll-ups of how much of your mail passed and failed alignment, and from which sources. This is the single most valuable field while you’re at p=none, because it’s how you discover senders you forgot you had.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;Where to send forensic (per-message failure) reports. They’re redacted and privacy-limited, and many receivers don’t send them at all. Useful to have, but don’t expect much from it.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;Forensic-report options: 1 means generate a failure report if either SPF or DKIM fails to align. A minor knob that only matters if ruf reports actually arrive.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;The percentage of failing mail the policy applies to. At 100 it hits all failing mail. This is your rollout throttle: you’ll start a stricter policy at a low percentage and ramp it up.
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourapp.com; ruf=mailto:dmarc-failures@yourapp.com; fo=1; pct=100; adkim=s; aspf=s;Alignment mode for DKIM and SPF: s is strict (the domains must match exactly), r is relaxed (a subdomain of the From: org domain counts as aligned). Use strict on a sensitive apex, and relaxed when subdomains legitimately send on your behalf.
One field deserves separate attention precisely because it is absent from the record above: sp=, the subpolicy for subdomains. When you don’t set it, subdomains inherit whatever p= is. That inheritance is the source of a classic and dangerous mistake. Inheritance runs from the apex down to subdomains, so the DMARC record at your apex, _dmarc.yourapp.com, automatically protects every subdomain under it. The trap is to publish DMARC only at the subdomain, _dmarc.send.yourapp.com, and leave the apex with no DMARC record at all. Now the one domain attackers most want to spoof, your bare apex, is completely unprotected, even though you “set up DMARC.” The experienced reflex is the reverse: publish DMARC at the apex first, since it covers everything, and reach for sp= only when you specifically want a subdomain to run at a different strictness than the apex.
Now confirm you can author a record, not just read one.
You're publishing your first DMARC record. You want monitor-only (no mail affected), applied to all of your mail, with strict DKIM alignment. Fill in the blanks. Pick the right option from each dropdown, then press Check.
v=DMARC1; p=___; rua=mailto:dmarc-reports@yourapp.com; pct=___; adkim=___;Here is the part of the lesson that pays off most for the rest of your career, and the thing most people get wrong: p=reject is not a setting you turn on. It is a destination you arrive at, over weeks, on evidence.
The reason is that a wrong p=reject does not fail gracefully. It hard-bounces real mail: every message from any legitimate sender you forgot to authenticate. That includes the billing tool that emails invoices from your domain, the CRM that sends from a marketing address, and the founder running a mail-merge from their laptop. Flip straight to reject and all of it starts bouncing the moment your DNS propagates, invisibly, while you think you’ve just improved your security posture. So the discipline is the opposite of a one-time DNS edit: you start permissive, watch what actually happens, and tighten only when the evidence says it’s safe.
That discipline is a four-stage playbook.
Publish p=none with rua flowing to a report parser. At p=none, no mail is affected; you are doing nothing but collecting the daily aggregate reports. Point rua at a DMARC monitoring service rather than a raw mailbox, because the reports are XML and you should never hand-parse them. Services like dmarcian, PowerDMARC, and Postmark’s DMARC monitoring all have free tiers that turn the XML into a readable dashboard.
Watch for about a week and authenticate every legitimate source. This is what the reports are for: they reveal every system currently sending under your domain, including the ones you’d forgotten. Work down the list, adding SPF includes, wiring up DKIM, or routing stray senders through your sending subdomain, until everything legitimate shows up as aligned.
Move to p=quarantine with pct=10, then ramp pct toward 100 over a week. Now you begin enforcing, but on only a tenth of failing mail at first, watching the reports for collateral damage: any legitimate mail you missed showing up as a failure. If it’s clean, raise the percentage in steps.
Move to p=reject once quarantine has run clean for about two weeks. Only when enforcement has been quietly working with no legitimate casualties do you reach the destination: spoofed mail is now refused outright, and your brand is protected.
Seeing the ramp laid out on a timeline makes the logic click. Each stage is a deliberate step down in risk, not an arbitrary stop.
This is also exactly where your project lands. Your first deploy will publish p=none with monitoring and graduate up the ramp over the project’s lifetime. That is the realistic, correct posture, and it’s what this unit’s project chapter ships. You are not expected to launch at reject; you are expected to launch at none and know the path.
So when is it actually safe to take the next step? The decision is always gated on evidence from your reports, never on the calendar. Walk it through.
You can’t tighten what you can’t see. Point rua at a monitoring service, let the daily aggregate reports start flowing, and read them for about a week before you change anything.
Tightening now would hard-bounce the legitimate senders that still fail alignment. Work down the report, adding SPF includes, wiring up DKIM, or routing stray senders through your sending subdomain, until everything legitimate shows as aligned.
Begin enforcing, but on only a tenth of failing mail. Ramp pct toward 100 over about a week, watching the reports for any legitimate mail that starts failing. A clean window here is what earns the move to reject.
Enforcement has run quietly with no legitimate casualties. Move to p=reject: spoofed mail is now refused outright and your brand is protected. This is a destination reached on evidence, not a day-one setting.
Notice what every branch has in common: the gate is always what the reports show, never how much time has passed. That is the entire discipline.
Everything above would be good practice in any year. What makes it non-optional in 2026 is an external deadline that the major mailbox providers have already enforced. Here is the timeline, with no hype, just what happened and what it means for you.
Gmail + Yahoo — February 2024
The original bar: any sender over 5,000 messages/day to personal accounts must authenticate with SPF and DKIM, publish DMARC, and offer one-click unsubscribe on marketing mail. This is the wave that started it all.
Microsoft (Outlook / Hotmail / Live) — May 5, 2025
The same 5,000/day threshold and the same SPF + DKIM + DMARC requirement, with p=none as the minimum to be accepted at all.
La Poste — September 2025
France’s postal email service followed with equivalent authentication requirements, a sign the bar is now an industry baseline, not a Big-Tech quirk.
Read the trend, not just the dates. The explicit, written threshold is 5,000 messages a day to consumer mailboxes, but enforcement has hardened steadily, and the bar now applies in practice to far smaller senders. The right move is to build to the bar from day one, whatever your volume, because retrofitting authentication after your signup emails start disappearing is a bad week.
And “disappearing” is the right word, because the consequence has changed in kind. Failing mail is no longer quietly dropped into a spam folder where a determined user might dig it out; it is rejected outright. Here is Microsoft’s actual SMTP response when authentication doesn’t meet the bar:
550 5.7.515 Access denied, sending domain … does not meet the required authentication levelLet that difference land. “Went to spam” means the user could still find your verification email. “Rejected” means they never receive it, and your signup funnel breaks silently, with no error visible to you, because the rejection happens at the recipient’s server. That is the failure mode this whole lesson exists to prevent.
So here is the 2026 baseline to build against, a list you can scan and check off.
p=none is the minimum to send at all to the major providers; p=quarantine or p=reject is the target to work toward.List-Unsubscribe and List-Unsubscribe-Post headers below). Transactional mail, meaning verification codes, password resets, and receipts, is exempt from the unsubscribe requirement but not from authentication. The next lesson covers the transactional/marketing split.The unsubscribe headers named in that checklist look like this. You won’t implement them in this chapter, but recognize the shape so the line between marketing and transactional mail is concrete:
List-Unsubscribe: <https://yourapp.com/unsubscribe?token=…>List-Unsubscribe-Post: List-Unsubscribe=One-ClickThat is one-click unsubscribe : the List-Unsubscribe-Post header is what lets Gmail or Outlook show a native “unsubscribe” button that works in a single tap, without the user ever loading your page. Transactional mail doesn’t need it, but it still needs every authentication item above.
Finally, prove that the setup actually works. Once your records are live, verify them like this.
Send a test to a header-parsing verifier. Tools like learndmarc.com give you an address to mail, and their auto-reply reports your SPF, DKIM, and DMARC results back to you, broken down field by field. This is the fastest way to see all three checks at once.
Send to a Gmail inbox and open “Show original.” In the message’s three-dot menu, “Show original” displays the raw headers with a summary at the top. Confirm you see SPF: PASS, DKIM: PASS, and DMARC: PASS. This is the receiving provider’s own verdict, which is the one that counts.
Check the Resend dashboard. The latest send shows its authentication result, and if something failed, it points you straight at the misconfigured record so you know which DNS entry to fix.
When all three agree, your domain is genuinely authenticated: not merely Verified by your vendor, but trusted by the providers your users actually read mail in.
To go deeper on the specifics for your own domain, these are the references worth bookmarking.
Resend's canonical guide to the exact records and the staged p=none to p=reject path.
Google's authoritative statement of the Gmail authentication requirements and spam-rate thresholds.
Interactive visualizer that steps a real message through SPF, DKIM, and DMARC alignment live.
dmarcian's primer, plus the report-parsing service you point rua at in stage one of the rollout.