How to check DKIM selector
Read the s= tag of the DKIM-Signature header on a message the domain sent. No DNS query lists the selectors a domain publishes, so the message is the only authoritative source. Guessing common names finds some of them and never tells you the set is complete.
Why check this
You need a selector before you can run How to check DKIM record, and the usual moment is a support ticket: mail from a new provider is failing DKIM at one receiver, and nobody in the room knows which selector the provider signs with. The failure it prevents is a wasted afternoon spent querying a selector the provider stopped using two rotations ago.
Say the hard part first. DNS answers questions of the form "what is at this name". It has no query that enumerates the children of _domainkey.<domain>, and no tool can invent one. A page or a product that offers a "DKIM lookup without selector" is guessing from a list, the same as step 4 below, and its silence about a selector means nothing.
Prerequisites
- The raw source of one message from the domain. Gmail: open the message, kebab menu, Show original, Download Original. Outlook on the web: More actions, View, View message source. Save it as
message.eml. - RFC 6376 section 3.5 for the tag list in the signature header.
- The sample below was signed on the test machine with a key generated for it, using the procedure in How to verify DKIM signature, so no third party's mail was read.
- Node 22 for steps 2 and 4. Save
dkim-sig-tags.js:
const fs = require('fs');
const head = fs.readFileSync(process.argv[2], 'utf8').split(/\r?\n\r?\n/)[0];
for (const field of head.split(/\r?\n(?![ \t])/)) {
if (!/^DKIM-Signature:/i.test(field)) continue;
const flat = field.replace(/\r?\n[ \t]+/g, ' ').slice('DKIM-Signature:'.length);
const t = {};
for (const p of flat.split(';')) { const i = p.indexOf('='); if (i > 0) t[p.slice(0, i).trim()] = p.slice(i + 1).trim(); }
console.log('selector (s=) : ' + t.s);
console.log('domain (d=) : ' + t.d);
console.log('lookup name : ' + t.s + '._domainkey.' + t.d);
console.log('algorithm (a=): ' + t.a);
console.log('canon (c=): ' + t.c);
console.log('signed (h=): ' + t.h);
}
- And
dkim-selectors.jsfor step 4:
const { Resolver } = require('dns');
const r = new Resolver();
r.setServers(['8.8.8.8']); // Node's default resolver is not always usable
const GUESSES = ['default', 'google', 'selector1', 'selector2', 's1', 's2', 'k1', 'k2',
'mail', 'dkim', 'smtp', 'mandrill', 'zendesk1', 'sendgrid', 'mailjet', 'protonmail'];
const domain = process.argv[2];
const get = (n) => new Promise((res) => r.resolveTxt(n, (e, x) => res(e ? null : x[0].join(''))));
const keyed = (rec) => /(^|;)\s*p=\s*[A-Za-z0-9+/]/.test(rec);
(async () => {
// A name nobody would publish. An answer here means a wildcard, so every guess below is worthless.
const control = await get('zq7x4k9n2v._domainkey.' + domain);
if (control !== null) {
console.log('WILDCARD at *._domainkey.' + domain);
console.log(' control name answered: ' + control);
console.log(' guessing cannot find a selector on this domain. Read s= from a message instead.');
return;
}
console.log('control name: no TXT record, so an answer below is a real selector');
let found = 0;
for (const s of GUESSES) {
const rec = await get(s + '._domainkey.' + domain);
if (rec === null) continue;
found += 1;
console.log(' ' + s.padEnd(11) + (keyed(rec) ? 'key published' : 'record present, p= empty (revoked)'));
}
console.log('guessed ' + GUESSES.length + ' names, found ' + found);
console.log('this is not the domain\'s selector list. It is the part of it these guesses cover.');
})();
Steps
- Step 1.
Print the whole
DKIM-Signatureheader, folded lines included.awk '/^DKIM-Signature:/{f=1;print;next} f&&/^[ \t]/{print;next} f{exit}' message.emlDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=build.example; s=ci2026; t=1789502400; h=from:to:subject:date:message-id:mime-version:content-type; bh=Zis1WdJ93u+EtNEcsRue0z7MwsjZ8A2hL7/YzPYFX98=; b=okbboUlhK01AQeF2jwaU8bZFWP6qj6cyA182m2I+BIBYPwxrK5ABcpYpAwuoq6WmVK2m XHRZnr7IvHh+7YMWN/ZEBpQiKQ5T3gfzlQIvbzJYTPUtKVG4s8p43RgUaDa+hgDzfE1I hnBnStWFvasT9/6b7V1qPCbOVpQpEWzHPw+PA69iwJx+jvlsvwFfZN9rxHKlr5QsAyg7 6Kik3m6yLpbVlC1LyXZ9islb8MKbe4lKNkhKiydfzDyIlwGy45KIIHnfBD8n6N+aqTEV /2RVNqslLm3Jp8Yv/fRkqIFkg4Op21OiGWl7sBod6B4NBRSJ5alruX+ftZxUDSJjS3pG vQ==A header field continues while the next line begins with a space or a tab, which is why a one-line grep loses most of it.
- Step 2.
Split the tags and build the DNS name from
s=andd=.node dkim-sig-tags.js message.emlselector (s=) : ci2026 domain (d=) : build.example lookup name : ci2026._domainkey.build.example algorithm (a=): rsa-sha256 canon (c=): relaxed/relaxed signed (h=): from:to:subject:date:message-id:mime-version:content-typeThe name to query comes from
s=andd=together.d=is the signing domain, which is not always the domain in theFrom:header. - Step 3.
Ask the parent name for a listing and read what DNS returns instead.
nslookup -type=TXT _domainkey.github.com 8.8.8.8Server: dns.google Address: 8.8.8.8 github.com primary name server = ns-1707.awsdns-21.co.uk responsible mail addr = awsdns-hostmaster.amazon.com serial = 1 refresh = 7200 (2 hours) retry = 900 (15 mins) expire = 1209600 (14 days) default TTL = 86400 (1 day)An SOA record and no answer section. The name has no TXT data, and asking it yields no list of the selectors below it.
- Step 4.
Guess a list of common selectors, with a control query for a wildcard.
node dkim-selectors.js github.comcontrol name: no TXT record, so an answer below is a real selector google key published selector1 key published s1 key published s2 key published k1 key published k2 key published zendesk1 key published guessed 16 names, found 7 this is not the domain's selector list. It is the part of it these guesses cover. - Step 5.
Run the same script against a domain whose zone answers everything.
node dkim-selectors.js example.comWILDCARD at *._domainkey.example.com control name answered: v=DKIM1; p= guessing cannot find a selector on this domain. Read s= from a message instead.Without the control query, this domain reports sixteen selectors out of sixteen guesses and every one of them is the same empty record.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| s= in a signature header | The selector that signed this message | Query <s>._domainkey.<d> and read the key. |
| Several DKIM-Signature headers | The domain and a relay both signed | Each carries its own s= and d=. Check the one whose d= is yours. |
| d= differs from the From: domain | The provider signed with its own domain | DKIM passes and DMARC can still fail on alignment. |
| Control name answers | A wildcard at *._domainkey | Guessing is useless here. Get a message. |
| Guessing found nothing | These sixteen names are not in use | The domain may still sign. Absence of a hit is not absence of a selector. |
| The record at s= is missing | The selector rotated out, or the zone is wrong | Compare against a message sent today, not one from last quarter. |
Common mistakes
What to check next
- How to check DKIM record: the lookup that the selector from step 2 makes possible.
- How to verify DKIM signature: what the key at that selector is used for.
- How to check if an email has passed SPF DKIM and DMARC: the receiver's own verdict on the same message.
- How to check DMARC record: why a
d=that differs from theFrom:domain matters. - Email testing checklist: the reads to run before mail moves to a new provider.
FAQ
How to check DKIM without a selector?
You cannot look one up. What you can do is guess from a list, as step 4 does, and treat a miss as no information. The reliable route is a message from the domain, or the sending provider's own setup screen.
Where is the selector in a raw message?
In the s= tag of the DKIM-Signature header. The header is usually near the top of the file, above From:, because signers prepend it.
Can a domain have more than one selector?
Yes, and busy domains have several at once: one per sending platform, plus an old one kept live through a rotation. github.com answered seven of sixteen guessed names on 2026-09-11.
Does the selector name mean anything?
No. It is an opaque label. Dated names like 20230601 and provider names like zendesk1 are conventions, not rules, and nothing in DNS enforces them.
Verified
Verified by Maks Vernynode 22.23.2nslookup Windows 10.0.22631awk 5.0.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate6 minpublished updated Maks Verny