Guides

How to set up DKIM

Selectors and the one case where two providers do collide, TXT versus CNAME, the 255-octet split that fails silently, key length, rotation, and what alignment actually needs.

Yowl publishes your DKIM key for you, under its own selector, and then keeps watching it. The move that usually breaks mail — two providers signing for one domain — cannot collide here: our key lands beside whatever your old provider left, both keep verifying, and the old one comes out a week later when you say so.

Stop reading dig output: ask your agent

Here is the part that has no equivalent at an ordinary mail host. Yowl hands your domain’s whole mail posture to an AI agent over MCP. You connect Claude to the inbox once, and from then on “is my DKIM still good?” is a question you ask in English.

What comes back is not a green tick. It is every record your provider currently asks for, measured live, one at a time, with the value expected beside the value published:

{ "type": "TXT", "name": "resend._domainkey",
  "expected":  "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8MGqVPX+vBaw4w+A4tyIv95Z9...",
  "published": ["p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8MGqVPX+vBaw4w+A4tyIv95Z9..."],
  "state": "ok" }

Three things in that payload are worth more than the tick:

It names which way a record is wrong. Not “not found” — missing, different, or unknown, where unknown means the lookup failed and is never presented as a fault. A key that is real but stale reads as stale, and you are told to replace it rather than to add it again. That distinction has its own page: why DKIM fails when the record is already there.

It knows your DNS host, and what that host does to you. Cloudflare proxies records by default and mail records must be unproxied — so the answer comes back carrying that warning rather than leaving you to discover it. One fewer afternoon.

It keeps the history. Every step of the domain’s life with us is an append-only trail: inspected, reserved, records issued, registrar says pending, registrar says failed, verified — each timestamped. When someone says “but it was working last week”, that is a question with an answer.

Ask for the estate rather than a record and you get the same treatment across every domain you run, including the parked ones. Ask “has anything changed since Tuesday?” and the trail answers it. None of that is a dashboard you have to remember to open — it is a question, and the answer arrives where you already are.

What you are publishing, if you do it by hand

DKIM signs each outgoing message with a private key the sending server holds. The public key sits in DNS at <selector>._domainkey.<domain>. A receiver reads the selector out of the message’s own DKIM-Signature header, fetches that key, and checks the signature.

Because the signature rides along with the message rather than describing the connection, it survives forwarding — though not edits. A mailing list that rewrites the Subject or appends a footer breaks it as surely as editing the body does.

Selectors, and the one case where they collide

The record lives under a selector, so two providers signing for one domain normally sit side by side without touching each other. That is what makes DKIM the safe record during a migration: add the new provider’s key beside the old one and both keep verifying.

Normally, not always. Collisions happen when two providers want the same selector label — k1 is the documented case, since Mailgun and Mailchimp both default to it. Publishing two keys at one selector is the version that really hurts, because a receiver has no way to choose between them: one Marketo user whose IT team added a second instance’s key alongside the first at the same name had over 90% of their mail flagged as spam.

There is a DNS rule underneath it too: a name carrying a CNAME must not carry other data, so a CNAME selector with a stray TXT at the same name is malformed whatever either one says.

Most providers let you rename the selector. Ask for a distinctive one up front and avoid default.

TXT or CNAME

Either a TXT holding the key itself:

google._domainkey   TXT   "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

Or a CNAME to a hostname the provider controls, where they rotate the key for you:

selector1._domainkey   CNAME   selector1-example-com._domainkey.acme.onmicrosoft.com

Take the CNAME when it is offered — rotation becomes their problem.

One detail matters more than it looks. A 2048-bit key runs to about 392 characters and a single DNS character-string caps at 255 octets, so the value has to be published as two quoted strings that DNS concatenates on the way out. Some control panels chunk it for you. Some store one string and quietly truncate. That failure is invisible from the sending side — mail still goes out, your logs stay clean, and receivers reject the signature.

Key length

Publish RSA-2048. Google’s sender guidelines still set the floor at 1024 bits and RFC 8301 makes 1024 the protocol minimum, so 1024 does pass — but NIST deprecated 1024-bit RSA in 2013 and some filtering gateways score key strength, so there is no reason to publish it on a new domain. You will find 2026 posts asserting Google now requires 2048; that contradicts Google’s own documentation, so treat it as a firm recommendation rather than a threshold.

Ed25519 (RFC 8463) is shorter and stronger, and verifiers are formally required to implement it. Deployment has not followed. The honest position for 2026 is that RSA-2048 is still the key to publish, because Ed25519 support among the large receivers is uneven enough to be a deliverability risk. Sources disagree on how uneven, and the ones that tested rather than surveyed are gloomier.

Alignment

A passing signature is only half the job. For DMARC to count a DKIM pass, the d= domain in the signature has to align with the visible From domain — the same organisational domain under relaxed, exactly equal under strict. A provider that signs with d=provider.com hands you a green dkim=pass that does nothing for your DMARC.

One correction, because a lot of guides get this wrong and this one did until today: header.d in an Authentication-Results header is the signing domain, not an alignment verdict. The verdict is dmarc=. Microsoft’s own documentation carries the counter-example — spf=pass ... dkim=pass header.d=adatum.com; dmarc=fail ... header.from=contoso.com — two passes and a fail, because neither authenticated domain matched the From.

Rotation

New key under a new selector, switch the signer to it, then leave the old record up for a week: mail already in flight and queued retries still carry the old selector. Then remove it. CNAME providers do all of this without you noticing.

Check it

dig +short TXT   google._domainkey.example.com
dig +short CNAME google._domainkey.example.com

Check both types at the name — either can be the collision. Then send yourself a message and read its Authentication-Results header:

Authentication-Results: mx.example.net;
  dkim=pass header.d=example.com header.s=google

header.s is the selector, which tells you which provider signed. header.d is the signing domain. For whether it aligned, read dmarc=.

If the record is published and verification still refuses to pass, that is a different problem with its own three causes: why DKIM fails when the record is already there.

Connect a domain to Yowl →