Tutorial
Transactional email that actually arrives: SPF, DKIM, DMARC
Your sign-in codes are worthless in spam. The DNS records that authenticate your domain, and why one suppression list will lock users out.
Transactional email deliverability is the difference between a sign-in code arriving and a support ticket. It’s mostly not about your email provider — it’s three DNS records, one suppression rule, and resisting a mistake that will lock real users out of their accounts.
Three records, and what each one proves
Receiving servers don’t trust your mail because you sent it. They check whether the domain it claims to be from actually authorised it.
SPF is a TXT record listing who may send as your domain. It proves the sending server is on your list. It breaks in one predictable way: two SPF records on the same name is a hard fail, so if you already have one, merge rather than add.
DKIM is a cryptographic signature on every message, verified against a public key you publish in DNS. Stronger than SPF because it survives forwarding, and it proves the content wasn’t altered in transit.
DMARC is the policy that ties them together — it tells receivers what to do when SPF and DKIM disagree with the From address, and where to send reports. Gmail and Yahoo now require it of bulk senders.
Start at p=none:
Name: _dmarc.yourdomain.com
Type: TXT
Value: v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com
p=none means monitor only. Read the reports for a few weeks, confirm your legitimate mail passes, then tighten to quarantine and eventually reject. Going straight to reject is how people discover their invoicing system was sending as them.
Send from a subdomain
Use mail.yourdomain.com or send.yourdomain.com rather than the bare domain.
Reputation attaches to the sending domain. If something goes wrong — a bad list, a compromised key, a bug that sends the same message four times — the damage is contained to the subdomain, and your primary domain’s normal correspondence is unaffected. It costs nothing to set up this way and is painful to retrofit.
The suppression list mistake
Here’s the one that causes real damage, and it’s a data-modelling error rather than a DNS one.
You need a suppression list. A hard bounce means the address doesn’t exist; a spam complaint means someone marked you as junk. Continuing to mail either one degrades your reputation for every other user, so both must stop immediately.
The mistake is putting unsubscribes on that same list.
Someone who opts out of your product newsletter has not opted out of their own password reset. If unsubscribing suppresses everything, you have built a mechanism that locks people out of their accounts — and they will experience it as your login being broken.
Suppression needs scope:
all— bounces and complaints. Send nothing, ever.marketing— unsubscribes. Blocks campaigns. Never blocks a sign-in code, a password reset, or a receipt.
Then every send declares what it is, and the check respects the scope. The test worth writing:
unsubscribed user + marketing email → blocked unsubscribed user + login code → delivered
If the second one fails, you have a support queue in your future.
One-click unsubscribe, on the right messages
Gmail and Yahoo require bulk senders to support one-click unsubscribe. That means two headers, not one:
List-Unsubscribe: <https://yourdomain.com/unsubscribe?...>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe alone renders the button but makes it a mailto. The second header is what turns it into a single click your endpoint handles.
Put these on marketing only. On a login code they’d invite people to opt out of the mechanism they need to get into their account.
And sign the unsubscribe link. A bare ?email= parameter is an open endpoint: anyone can walk a list and unsubscribe strangers, and mail clients that prefetch links will opt people out for merely previewing the message. An HMAC bound to the address fixes both.
Consume the webhook
Your provider will tell you what happened — delivered, bounced, complained. Handle it, because that feed is what keeps the suppression list accurate without manual work.
Two properties matter. Verify the signature: the endpoint is public, so without it anyone can POST a forged bounce and permanently suppress any address they choose. And make it idempotent by keying on the provider’s event id — webhooks are at-least-once, and a retry after a slow-but-successful handler is normal traffic, not an error.
Velaris mints, sends and verifies its own codes — read how those codes are stored.