You added a sending domain, copied two DNS records, and your first email landed in spam anyway. Or Resend returns a 403 and refuses to send at all. Or Gmail delivers it and Outlook does not.
All three symptoms come from the same place: email has no built-in authentication, so three separate DNS records were bolted on over twenty years to answer the question "is this sender allowed to use this domain". Mailbox providers now check all three, and Google and Yahoo have required them for bulk senders since 2024.
What each record proves
SPF: which servers may send for this domain. A TXT record listing authorised senders:
v=spf1 include:amazonses.com ~all
The receiving server compares the connecting IP against that list. Two traps.
First, SPF checks the envelope sender, not the From: header a human sees,
which is why SPF alone cannot stop spoofing. Second, there is a hard limit of
ten DNS lookups; every include: costs at least one, and a domain with
Google Workspace plus a CRM plus a marketing tool plus a transactional provider
blows through it. Past ten, the check returns permerror and fails.
DKIM: this message was not tampered with, and the domain owner signed it.
Your provider signs each message with a private key; the public key sits in DNS
at <selector>._domainkey.yourdomain.com. The receiver fetches it and verifies.
DKIM survives forwarding, which SPF does not: a forwarded message keeps its
signature but arrives from the forwarder's IP.
DMARC: what to do when the first two fail, and where to send reports. A
TXT record at _dmarc.yourdomain.com:
v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; pct=100
DMARC adds the piece SPF and DKIM lack: alignment. It requires that the
domain that passed SPF or DKIM matches the domain in the visible From: header.
That is what actually stops someone sending as you.
p= is the instruction to receivers: none (monitor only), quarantine (spam
folder), reject (refuse outright).
Set it up on a subdomain
Use mail.yourdomain.com, not yourdomain.com.
Reputation is tracked per domain and per subdomain. Keeping transactional mail on its own subdomain means a bad week (a bounce spike, a spam complaint from one campaign) cannot damage the domain your team sends sales mail from, and a compromised marketing tool cannot damage your password resets.
In Resend: Domains → Add domain → mail.yourdomain.com. It shows you the
records. Add them at your DNS provider exactly as given, with two gotchas that
account for most failed verifications:
- Do not double the domain suffix. Many DNS UIs append the zone
automatically, so entering
resend._domainkey.mail.yourdomain.comcreatesresend._domainkey.mail.yourdomain.com.yourdomain.com. Enter the host part only. - Turn off proxying. On Cloudflare, DNS records for mail must be "DNS only" (grey cloud), not proxied.
Verification is usually minutes. Propagation can be an hour. Until the domain shows Verified, Resend rejects sends with a 403: this is not a filtering problem, and no amount of copy tuning fixes it.
Start DMARC at p=none
Publish p=none first and read the aggregate reports for a couple of weeks.
They arrive as XML at the rua= address; a free parser is enough. What you are
looking for is any legitimate mail failing alignment: a helpdesk, an invoicing
tool, a CRM, something a colleague set up two years ago.
Then tighten to p=quarantine, then p=reject. Going straight to reject
before you know what sends as you is how a company discovers on a Monday that
its invoices have been bouncing since Friday.
Reading a failure
Open the message in Gmail and choose Show original. The header block tells you everything:
SPF: PASS with IP 54.240.x.x
DKIM: PASS with domain mail.yourdomain.com
DMARC: PASS
- SPF fail, DKIM pass, DMARC pass: fine. Common after forwarding, and the reason DKIM matters more than SPF.
- DKIM fail: the record is wrong, missing, or the message was modified in transit (some mailing lists rewrite bodies). Re-check the selector record.
- DMARC fail with both passing: an alignment problem. Something is sending
with a
From:on your root domain while authenticating as a different one. - SPF permerror: you are past ten DNS lookups. Flatten the record or move senders onto subdomains.
The verify check
bun run verify asserts that the domain in EMAIL_FROM exists on the Resend
account and reads verified. It is deliberately part of the standard check
because "we forgot to verify the production domain" is a launch-day failure that
looks like a code bug.
One caveat worth knowing before it surprises you: listing domains needs a
full-access key, and the onboarding tells you to create a sending-access key,
which is the right advice. With such a key Resend answers restricted_api_key
and the check passes with a note saying the domain state was not read: confirm
it in the dashboard instead. Any other rejection still fails the check, so a
wrong key and a deliberately restricted one do not read the same.
Two more things worth doing
Do not send from a no-reply address. Replies to a black hole correlate with
complaints, and someone will reply. REPLY_TO exists for this.
Never put a customer's address in From:. "Sam invited you" as a display
name is fine; sam@theircompany.com as the actual From fails DMARC on their
domain, lands in spam, and is technically indistinguishable from spoofing. Set
replyTo to the inviter instead. agent/skills/add-email-template.md builds an
invite template as its worked example and does exactly this at the call site.