Email & deliverability

SPF and DMARC for a receive-only domain: stop spoofing

A domain pointed here takes mail in and sends none — which makes it the easiest domain in the world to protect, and the easiest one to forget. Three records, the strictest settings that exist, and none of the six-month rollout everybody else needs.

  • Intermediate
  • 19 min read
A blue envelope sliding into a grey mailbox while a second envelope is turned away by a blue shield

Two directions, and you have configured one

Pointing a domain here takes one DNS record. That record — the MX — answers exactly one question: where does mail addressed to this domain go? It is the whole of what a catch-all needs, and once it resolves, the domain works. It is also, at that moment, only half configured, because a domain name is used in two directions and the MX record has nothing to say about the second one.

Mail addressed to youanything@your domainYour inbox heredelivered and keptyour MX record decides thisMail forged as youyour domain in the FromSomebody else's inboxnever reachedyour SPF and DMARC decide thisTwo directions, two sets of records. Publishing one does nothing for the other.
Both lanes are the same domain name. The record you published governs the top one; the bottom one is answered by records you have not written yet, and an unanswered question is answered by each receiving server's own guess.

The second direction is the interesting one. Nothing in the mail protocol stops a stranger from writing your domain in the From: line of a message they send from their own machine. They do not need access to your DNS, your registrar or your mailbox — the address in a message is a claim, not a credential, and it always has been. What decides whether that claim is believed is what your domain says about who is allowed to make it:

The domain that says nothing
A receiving server finds no SPF and no DMARC policy, and has to fall back on its own heuristics: reputation, content, how the domain has behaved before. A brand-new domain with no history and no policy is roughly the ideal thing to forge, because there is nothing to contradict the forgery and nothing to blame afterwards.
The domain that says no
The same server finds a published policy stating that no machine anywhere is authorised to send as this domain, and that failures should be rejected. It stops being a judgement call. The message is refused at the door, usually without ever reaching a spam folder.

This is not a hypothetical about large brands. Domains with no outbound mail are picked precisely because they have no policy — a name nobody protects, used for a fortnight and dropped, is worth more to a sender of unwanted mail than a name that fights back.

Why you get to skip the part that takes everybody else six months

If you have read anything about DMARC before, you have read that it is a long, careful project. That advice is correct, and it is not about you. It is written for a domain that sends mail, and it is long for one reason: before you can tell the world to reject everything that fails, you have to find every system that legitimately sends as you and make each of them pass. In a normal organisation that list is longer than anybody expects, and it is discovered rather than known:

  1. Publish a policy that does nothing. p=none asks receivers to report but change nothing, so that a forgotten sender cannot be cut off by the policy itself.
  2. Read reports for weeks. The invoicing system, the helpdesk, the newsletter tool, the recruitment platform somebody signed up for in 2019 — each one turns up as a source that fails, and each has to be authorised or retired.
  3. Tighten in stages. Move to quarantine, wait, watch, and only then to reject, because every step of the tightening can silently stop mail somebody depends on.

Every one of those steps exists to protect legitimate senders from your own policy. A domain that has been pointed at a receive-only inbox has no legitimate senders. The list is not long, or hard to discover, or partly unknown — it is empty. And a policy that rejects everything cannot break a sender that does not exist.

There is a second-order argument for going straight to the end state, too. A domain sitting on p=none is publishing a policy that asks for nothing, and a receiving server treats it very nearly the same as a domain with no policy at all. The record exists, so it looks handled — which is worse than obviously unfinished, because nobody comes back to finish it.

The three records

All three are TXT records, and all three go in beside the MX you already have. Names are written the way most DNS panels want them — relative to the zone, with @ meaning the domain itself. If yours asks for a fully qualified name, write yourdomain.com, _dmarc.yourdomain.com and *._domainkey.yourdomain.com instead.

NameTypeValueWhat it settles
@TXTv=spf1 -allNo machine is authorised to send mail with this domain in the envelope. Not some machines — none.
_dmarcTXTv=DMARC1; p=reject; sp=reject; adkim=s; aspf=sAnything that arrives claiming to be this domain and cannot prove it should be rejected, and the same goes for every subdomain.
*._domainkeyTXTv=DKIM1; p=Every DKIM key this domain might be asked about is revoked, so a forged signature cannot be made to verify.

If your provider requires the value to be quoted, quote it. Some panels add the quotes for you and end up storing them twice, which produces a record that no checker can read — if a value comes back from dig with two sets of quotes around it, that is what happened.

The three values, ready to paste. Copy them rather than retyping: a missing semicolon in the DMARC record makes the whole tag list unparseable, and an unparseable policy is treated as no policy at all.

SPF — on the domain itselfv=spf1 -all

DMARC — on the name _dmarcv=DMARC1; p=reject; sp=reject; adkim=s; aspf=s

DKIM — on the name *._domainkeyv=DKIM1; p=

What each word in those records actually does

Five decisions, and it is worth knowing which is which — mostly so that you recognise, later, which one to relax if the domain ever starts sending.

-all
The end of the SPF record, and the only part of it that matters here. It means everything not listed above is a forgery, and nothing is listed above. The common alternative, ~all, means probably a forgery, but deliver it anyway and mark it — which is the right answer while you are still finding your own senders, and the wrong one when you are certain there are none.
p=reject
What a receiving server should do with mail that claims this domain and fails. none means report only, quarantine means treat as suspicious, reject means refuse it in the SMTP conversation. Reject is the one that keeps the message out of a human's sight entirely.
sp=reject
The same, applied to every subdomain — including the ones that do not exist. Without it a forger uses billing.yourdomain.com, which has no records of its own, and inherits nothing to stop it. This tag defaults to whatever p says, so the record behaves identically without it; it is written out because a policy you cannot see when you read the record back is a policy you will assume is missing.
adkim=s, aspf=s
Strict alignment. It requires the authenticated domain to be the domain in the From: line exactly, rather than merely a relative of it — so a message from anything.yourdomain.com cannot borrow a pass that belonged to the parent. Relaxed is the default, and strict is what a domain with nothing to authorise should be saying.
v=DKIM1; p=
A DKIM public key with no key in it. The specification is explicit that an empty key means revoked, so any signature that names any selector under this domain fails verification instead of being ignored. The wildcard covers every selector name a forger might invent, because they get to choose the name.

And the record that must stay exactly as it is

Nothing above touches inbound mail, and nothing above should. The MX record is what makes every address on the domain a mailbox here, and it is unchanged by any of it:

MX record — unchanged10 smtp.grabmail.io

The three new records and this one answer different questions, so they do not interact: SPF and DMARC are read by servers deciding whether to accept mail that claims to be you, and the MX is read by servers deciding where to deliver mail addressed to you. A domain with all four is a domain that receives everything and vouches for nothing.

Checking your work, in three commands

DNS is not a place to assume. Every one of these records is published to be read by strangers, which means you can read it exactly as they will — no account, no tool, no site to paste your domain into:

shell
$ dig +short TXT      yourdomain.com
dig +short TXT _dmarc.yourdomain.com
dig +short MX       yourdomain.com

Substitute your own domain. What you are looking for is this, give or take the other TXT records you may already have on the name:

what it should answer
"v=spf1 -all"
"v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s"
10 smtp.grabmail.io.

Four ways this comes back wrong, in rough order of how often:

The first command answers nothing at all
The record has not propagated yet, or it was added to the wrong name. TXT records on the domain itself go on @, not on www — a panel that defaults to the last name you edited is the usual culprit.
Two SPF records come back
A domain is allowed exactly one. Two is not stricter than one — it is a permanent error, and a receiving server that finds two treats the SPF check as broken rather than as failing. If you already had an SPF record, edit that one instead of adding a second.
The DMARC line comes back but nothing enforces it
Read the value carefully: it has to start with v=DMARC1, and the tags are separated by semicolons. A record missing the version tag, or one where a semicolon became a comma, is not a weaker policy — it is an unparseable one, which counts as no policy at all.
The MX answer has changed
It should not have. If the MX line is now missing, or shows a bare dot, or lists a host that is not smtp.grabmail.io, stop and put it back before doing anything else — that is the record your inbox depends on.

The part almost everybody misses: subdomains

DMARC and SPF do not divide the namespace the same way, and that difference is where a carefully protected domain leaks. sp=reject genuinely does cover every subdomain, existing or not. SPF does not work like that at all: it is looked up on the exact name in the envelope, and a name with no SPF record of its own has no SPF record — it does not inherit the parent's.

What a forger usesSPF on that nameWhat stops it
yourdomain.comFound: -all, so the check fails.SPF and DMARC together. This is the case the three records above were written for.
billing.yourdomain.comNone, unless you publish one. SPF returns no result rather than a failure.DMARC alone, through sp=reject — which is enough, and is why that tag is not optional.
yourdomaln.com, a lookalikeIrrelevant. It is not your domain.Nothing you can publish. Different name, different owner, different records.

DMARC covers the second row on its own, so this is belt and braces rather than a hole — but a wildcard SPF record costs one line and closes the gap at the SPF layer too, which matters for the receivers that check SPF and do not implement DMARC:

Wildcard SPF — one more TXT record* TXT v=spf1 -all

A DNS wildcard only answers for names that have no records of their own. If mail.yourdomain.com already has any TXT record on it, the wildcard is not consulted for that name and you would need to publish the SPF record there explicitly. For a domain used only as a catch-all, that situation is rare enough to be worth knowing rather than worth planning for.

Reports, and whether they are worth having

DMARC has a reporting side: add a rua tag and participating receivers send you a daily summary of everything that claimed to be your domain and what happened to it. It is the one part of DMARC that tells you something you did not already know, and on a domain pointed here it costs nothing to collect, because the address it goes to can be an address on the domain itself:

DMARC with reporting — replace the domainv=DMARC1; p=reject; sp=reject; adkim=s; aspf=s; rua=mailto:dmarc@yourdomain.com

Sending the reports to an address on the same domain avoids a piece of the specification that catches people out: if rua names an address at a different domain, that other domain has to publish a record authorising it to receive your reports, and until it does most senders will not send any. Same domain, no such record, nothing to get wrong. What arrives is worth knowing what it is before you enable it:

  • They are XML, gzipped, as an attachment. Not a summary you read over coffee. Small domains get a handful a day from the large mailbox providers, each a few kilobytes; you will want something to unpack and read them.
  • Empty is the good result. A domain that sends nothing should generate reports listing nothing but failures, and a quiet week means nobody is currently forging you — which is information, and the only way to get it.
  • They arrive as ordinary mail. Here that means an ordinary mailbox: readable in the browser and over the API like anything else, and deleted after 5 days along with everything else.

If you would rather not collect them at all, leave the tag off entirely. A DMARC record with no rua is a perfectly valid record and protects exactly as well; reporting is how you learn what is happening, not how the policy is enforced.

What this does not do

These records are narrow, and being clear about the edges is the difference between a control you rely on correctly and one you rely on wrongly. Four things they do not cover:

They do not filter your own inbox
SPF and DMARC on your domain are instructions to servers receiving mail from you. They are read by other people's mail systems, never by yours, and they have no effect on what turns up in the catch-all. What arrives is still whatever the world sends to an address whose only credential is knowing it.
They do not stop a name in a From line
A message reading Your Company <attacker@gmail.com> passes every check, because the domain that was authenticated really is the attacker's. DMARC protects the domain, not the display name in front of it — and on a phone, the display name is frequently all that is shown.
They do not touch a lookalike domain
A domain one character away from yours is somebody else's domain with somebody else's records. Nothing you publish reaches it. That is a registrar and monitoring problem, and it is genuinely a different problem.
They do not make the mailbox private
There is still no password on an address here: whoever knows it can read it, which is the bargain the whole service makes. Your own domain removes the guessing, not the reading — the honest version of that is in the guide on catch-all domains.

Doing it, start to finish

The whole job, in the order that leaves the domain working at every step:

  1. Confirm the MX first. dig +short MX yourdomain.com should answer smtp.grabmail.io and nothing else. Fix that before adding anything, because the rest is worthless on a domain that is not receiving.
  2. Add the SPF record on @, unless one is already there — in which case edit it, since two are an error rather than a stricter setting.
  3. Add the DMARC record on _dmarc, straight to the strict policy. There is no rollout to stage here and nothing to break by skipping it.
  4. Add the wildcard DKIM record on *._domainkey, and the wildcard SPF record if you want the subdomain layer closed twice.
  5. Read all four back with dig, against a public resolver, once the TTL has had time to expire.
  6. Send yourself something. Mail one of your own addresses on the domain from anywhere and open the inbox. If it arrives, the inbound side survived the change, which is the only regression worth checking for.

That is the domain finished: it accepts every address you will ever invent on it, and it vouches for nobody at all. If you have not connected the domain yet, start with the catch-all guide — it is one record and the same five minutes — and come back to this page afterwards, which is the order that leaves nothing half done.

Questions

Do I really need a DKIM record if I never sign anything?

You do not need it for your own mail to work, since there is none. You publish it so that a forger cannot invent a selector name, sign a message with their own key and have it verify — the empty key is a standing answer of revoked to every selector name they might choose. It is one record, it never needs maintenance, and it closes the one path that SPF and DMARC leave open.

Will any of this reduce the spam arriving in my catch-all?

No, and it is worth being blunt about that because it is the most common expectation. These records govern mail claiming to come from your domain. Mail arriving at your domain is unaffected — every address on it still accepts everything, which is what a catch-all is. If unwanted mail at a particular address is the problem, the fix is to stop using that address, not to change these records.

What if I want to send mail from this domain later?

Then you change two things, in this order: authorise the new sender in the SPF record before you send anything, and add its DKIM key on the selector it asks for. The *._domainkey wildcard does not block a real selector — an explicit record on that exact name is found first, and the wildcard is only consulted for names that have none. Nothing here paints you into a corner; it just means the domain is closed by default rather than open by default.

Should I start at p=none or p=quarantine to be safe?

Those stages exist to protect senders you have not found yet. You have no senders, so there is nothing for the stages to protect and nothing for the strict policy to break. Starting at none on a receive-only domain does not reduce risk — it publishes a record that asks receivers to do nothing, and leaves the domain as forgeable as it was before, with the added disadvantage of looking finished.

Does adding these records affect my MX record or my inbox?

Not at all. They are separate records answering separate questions, and no receiving server consults your SPF or DMARC when deciding where to deliver mail addressed to you. The one thing that would break the inbox is changing the MX itself — which is why the null MX record recommended by parked-domain guides has a section of its own above.

How long before it takes effect?

New records are usable as soon as they propagate, which is normally minutes. Changing a record you already had takes as long as its old TTL, because resolvers that fetched the previous value keep it until it expires. If you are about to edit an existing record, lowering its TTL a day beforehand is the trick that makes the change quick — and if you have already edited it, waiting is the only option.

Do I need a separate record for every subdomain?

For DMARC, no: sp=reject covers all of them, including subdomains that have never existed. For SPF, strictly yes — a subdomain does not inherit its parent's SPF record — but one wildcard TXT record on * answers for every name that has no records of its own, which on a catch-all domain is all of them.

Can I point the DMARC reports at a Gmail address instead?

You can, but the other domain then has to authorise it: a rua address outside your own domain requires a record at yourdomain.com._report._dmarc.gmail.com, which you cannot publish because that name is not yours. This is why the example above sends the reports to an address on your own domain, where no authorisation is needed and the mail simply lands in the catch-all.

How do I know it is actually working?

The direct proof is a report: turn on rua and the summaries name every source that tried to send as you and what each receiver did about it. Without reports, reading the records back with dig from a public resolver is the practical check — the policy is enforced by the servers that read it, so a record that resolves correctly to strangers is a record that is working.

Try it while it is fresh

An address takes one click, no account and no card. Everything in this guide works on it straight away.

Welcome back

Your inboxes and your domains, in one place.