An SPF, DKIM and DMARC setup is three DNS records and one signing daemon. The reason people get stuck is not difficulty — it is order. DKIM cannot be published before the key exists, DMARC cannot be tightened before you know who sends, and none of them matter if MX points at the wrong host or outbound port 25 is blocked by your provider.
This walkthrough goes record by record, with the exact values, on a Postfix + Dovecot + OpenDKIM server. Where Vexlyx automates a step, I say so — and where it deliberately leaves the decision to you, I say that too.
Step 0: the two things that are not your DNS
Before writing records, confirm the two prerequisites that sit outside your zone file.
MX and the mail hostname. Mail has to arrive somewhere. Publish an A record for the mail hostname and an MX that points at it:
mail.example.com. A 203.0.113.5
example.com. MX 10 mail.example.com.
MX targets a hostname, never an IP. If that hostname does not resolve back to the server, everything after this step is wasted effort.
Reverse DNS (PTR). PTR maps your server's IP back to a hostname and it lives with your hosting provider, not your registrar. Open a ticket and ask them to set the IP's PTR to mail.example.com, then confirm it yourself:
dig -x 203.0.113.5 +short
Vexlyx cannot set this for you and does not pretend to. The panel's own install summary tells you to configure PTR at your provider and to allow inbound TCP 25, 587 and 993 — the installer opens those four ports (plus 143) in UFW for you, but only inbound; whether your provider lets you send out of port 25 is a separate ticket.
Step 1: SPF — who may send as your domain
SPF is one TXT record at the apex. Start with a soft fail:
example.com. TXT "v=spf1 mx a ip4:203.0.113.5 ~all"
Read it left to right: the hosts listed in your MX records may send, the host at example.com's A record may send, this specific IP may send, and anything else is a soft fail. ~all says "a mismatch is suspicious", while -all says "a mismatch is forgery — reject it". On a new server, soft fail is the right opening move, because one forgotten sender — a website contact form, an invoice tool, an old CRM — silently dies with -all.
That is exactly the record Vexlyx generates when a domain gets its first mailbox: v=spf1 mx a ip4:<server-ip> ~all, TTL 3600. Generating it on the first mailbox rather than on a toggle is deliberate: a domain with no mailbox has nothing to authenticate.
Three constraints that break SPF setups:
- One SPF record per domain. Two records is not "both applied" — it is a permanent SPF failure. Some panels write a second TXT when you re-run setup; check with
dig +short TXT example.comand delete duplicates. - Ten DNS lookups, maximum. Every
include:and everymx/amechanism costs a lookup.spf permerrormeans you exceeded it, and it is treated as a fail, not a warning. - Forwarding breaks SPF. SPF is checked against the envelope sender and the
HELOname, from the IP that connected. When a forwarder relays your mail from its own IP, SPF fails no matter how correct your record is. DKIM survives this, which is why you do not skip step 2.
Step 2: DKIM — sign the mail you actually send
DKIM hashes selected headers and the body with a private key and puts the matching public key in DNS. The receiver looks up the selector, reads p=, and verifies nothing changed in transit.
Generate the key pair, then publish the TXT record at <selector>._domainkey:
default._domainkey.example.com. TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ..."
Use 2048-bit RSA keys; 1024-bit DKIM is a known-weak size that some receivers ignore. Vexlyx generates a 2048-bit RSA key with the selector default and writes both halves on the server — the private key under docker/postfix/opendkim/keys/<domain>/, the public TXT into DNS. Private keys are gitignored by design; if you are managing the box yourself, make sure your key directory is not world-readable and never ends up in a commit.
Then wire OpenDKIM so Postfix actually signs. Three files matter:
# /etc/opendkim/KeyTable — which key signs for which domain
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
# /etc/opendkim/SigningTable — which addresses use that selector
*@example.com default._domainkey.example.com
# /etc/opendkim/TrustedHosts — which clients may ask for signing
127.0.0.1
mail.example.com
After editing, reload OpenDKIM and confirm the DKIM-Signature header appears on a message you send to yourself. A domain that publishes a DKIM record but never signs anything scores no better than one with no record at all.
Rotating a key. Do not overwrite a live selector. Publish the new selector first, sign with it, and leave the old TXT in place until propagation finishes — receivers cache, and a signature whose selector has vanished fails. Vexlyx's "Rotate Key" action creates a dated selector (dk20261102, with -2, -3 suffixes on same-day collisions), marks the old key as retiring and leaves its DNS record untouched. Nothing deletes it automatically: you remove the old TXT once you are sure no mail still references it. There is no rotation schedule in the panel, so put it in a calendar.
Step 3: DMARC — say what to do, and where reports go
DMARC does not authenticate anything. It tells receivers what to do when your From: domain is not backed by a passing, aligned SPF or DKIM check — and where to send the reports.
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:postmaster@example.com; pct=100"
p=none— monitor only. Change nothing about delivery.rua=mailto:postmaster@example.com— aggregate reports to this mailbox.pct=100— the policy applies to all mail, so later tightening is not sampling.
This is the record Vexlyx publishes by default, with rua pointing at postmaster@<that same domain>. It is the safe default for a self-hosted server, and you change the policy yourself by editing that TXT — in the panel's DNS editor if Vexlyx hosts your zone, or at your registrar if it does not.
Understand what you are opting into with rua. Aggregate reports arrive as compressed XML, one file per receiving provider, listing who sent mail claiming to be your domain, in what volume, and with which SPF/DKIM results. Nothing in Vexlyx reads them — publishing the record does not give you a dashboard. Either point rua at a free DMARC reporting service that will parse them for you, or plan to open the XML yourself. What you are looking for in either case: senders you forgot about. That list is what makes p=quarantine safe or dangerous.
Escalate only when every source in the reports is one you recognise and can sign or list:
v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com; pct=100 # after 2–4 clean weeks
v=DMARC1; p=reject; rua=mailto:postmaster@example.com; pct=100 # after quarantine holds
One detail that trips up self-hosters: alignment. DMARC needs the authenticated domain to match your From: domain at the organizational-domain level. SPF alignment uses the envelope-from (often your bounce domain, which is not the visible From:), while DKIM alignment uses the d= tag. So mail sent through a third-party service can pass its own SPF and still fail DMARC. DKIM is what normally carries DMARC for a self-hosted server.
Step 4: verify each record, then verify a real message
Check the DNS first, from outside your own box:
dig +short MX example.com
dig +short TXT example.com
dig +short TXT default._domainkey.example.com
dig +short TXT _dmarc.example.com
dig -x 203.0.113.5 +short
Then send a message and read the header. You want three lines in Authentication-Results: spf=pass, dkim=pass, and dmarc=pass. spf=pass with dmarc=fail almost always means alignment, not a broken record.
Vexlyx shows the same picture in two different ways, and the difference matters. If it hosts your DNS (Managed), it scores from the records it wrote itself — that tells you what is configured, not what the world can see yet. If your zone stays at your registrar (Connected, the default), the panel looks your records up against Cloudflare (1.1.1.1), Google (8.8.8.8) and Quad9 (9.9.9.9) and shows a table headed "Add these records at your registrar" with each row marked Live or Not found. The DKIM row is stricter than the others: it counts as live only if the published p= value equals the key the panel generated. A leftover key from a previous server reads as Not found, which is correct.
The score itself is 0–100, 25 points per record for SPF, DKIM, DMARC and MX, graded Poor / Needs Improvement / Good / Excellent. It is a presence-and-syntax check, not a reputation reading — and it is not mail-tester.com either. For a delivery verdict, send to a mail-tester address and fix whatever is under 9/10 before you send real volume.
What a panel cannot do in this setup
The five records take minutes once you know the order. Everything that decides whether receivers believe you sits outside them:
- PTR belongs to your hosting provider. You ask, they set, you verify with
dig -x. - Outbound port 25 is blocked by many VPS providers by default. Inbound mail works fine, so the failure looks like a DNS problem.
- Sending behaviour — volume on a fresh IP, lists you did not earn, engagement.
- Reputation, which never transfers from your old provider.
Vexlyx automates the record half of this: on the first mailbox it generates SPF, DKIM, DMARC and MX, publishes them into its own zone when it hosts DNS or hands you the exact values when it does not, checks public DNS for what actually landed, keeps a 0–100 score with the reason behind each record, and gives you a mail queue and a delivery log so a growing queue points at a cause instead of at a guess. It deliberately starts you at ~all and p=none — the defaults that do not destroy real mail before you understand who is sending.
What it cannot do is the reputation part, and any product that promises otherwise is selling you a nicer dashboard. The self-hosted email use case lists which half is automated; why self-hosted email lands in spam covers the other half.
The mistakes that break it
- Publishing
-allbefore you have found every sender. - Two SPF TXT records after re-running a setup script.
- Stacking
include:entries until SPF returnspermerror. - Publishing a DKIM record and never adding the domain to
SigningTable. - Rotating a DKIM key by overwriting the live selector, then deleting the old TXT too early.
- Setting
p=rejecton day one while an old contact form still sends. - Pointing
ruaat a mailbox nobody reads — you collect reports you never open. - Treating a 100/100 panel score as a deliverability guarantee.
FAQ
Do I need all three: SPF, DKIM and DMARC?
SPF and DKIM are what receivers check. DMARC turns those checks into a policy you can actually enforce, and tells you who else is sending as you. Gmail and Yahoo require SPF or DKIM plus a DMARC record from bulk senders, so in practice all three. Start at p=none — it costs nothing and produces reports.
Which comes first, SPF or DKIM?
Either, but do not publish DMARC until both exist. SPF is a text record you can write in two minutes; DKIM needs a key pair first, so if you are doing it by hand, generate the key, publish the TXT, then wire KeyTable and SigningTable.
Why does DKIM fail even though my DNS is right?
Three usual causes: the record is at the wrong name (<selector>._domainkey.example.com, not _domainkey alone), the private key on the server no longer matches the published p=, or the message was modified in transit by something that rewrote headers. Check the d= and s= tags in the DKIM-Signature header against what is actually in DNS.
How long should I stay on p=none?
Two to four weeks of clean aggregate reports is the common guidance — long enough to catch a monthly or quarterly sender you forgot. Move to p=quarantine, watch again, and only then p=reject. If you cannot read the reports, point rua at a service that will.
Can I keep my DNS at my registrar and still use a panel?
Yes. That is connect-only mode: the panel generates the records and shows you the exact values, you paste them at the registrar, and the panel verifies them against public resolvers once they propagate. Only the zone stays outside the panel; the mail server itself still runs on yours. If you are moving off a shared host, the cPanel comparison walks through what changes.
Setting up mail on the same server that runs your sites and apps? See how Vexlyx handles self-hosted email, or read why self-hosted email lands in spam before you send your first campaign.