How netray Scores a Domain
netray runs five checks in parallel and produces a single A+–F grade. This guide explains the scoring model, how email security is split between sending and receiving domains, and how to test specific DKIM selectors.
What netray checks
For any domain, netray calls five backends in parallel and aggregates the results:
| Section | Tool | What it measures | Weight |
|---|---|---|---|
| IP | the IP inspector | Network type, hosting reputation, geolocation | 10% |
| DNS | the DNS inspector | DNSSEC, CAA records, NS delegation, HTTPS records | 20% |
| TLS | the TLS inspector | Certificate chain, protocol version, cipher strength | 35% |
| HTTP | the HTTP inspector | Security headers, HSTS, CORS, cookie attributes | 20% |
| netray's email checks | SPF, DKIM, DMARC, MTA-STS, DANE, BIMI, DNSBL | 15% |
HTTP and Email sections are optional. When a backend is not configured, the remaining weights are rebalanced proportionally — a missing section does not penalise the grade.
Scoring algorithm
Each check has a verdict and a weight:
- pass → earns full weight
- warn → earns exactly half its weight (a weight-5 warn earns 2.5)
- fail / not found → earns zero
- skip → excluded from both earned and possible totals (not a penalty)
A section score is earned / possible × 100%.
The overall score is the weighted average of active section scores.
The letter grade is determined by comparing the overall percentage to
fixed thresholds:
| Grade | Score | Meaning |
|---|---|---|
| A+ | ≥ 97% | Exemplary — all checks pass |
| A | ≥ 90% | Excellent — minor gaps only |
| B | ≥ 75% | Good — some non-critical findings |
| C | ≥ 60% | Fair — notable gaps, action recommended |
| D | ≥ 40% | Poor — significant issues present |
| F | < 40% or hard-fail | Failing |
Hard failures
Four TLS conditions force the grade to F regardless of the numeric score:
- Untrusted chain — the certificate is not signed by a trusted CA. Browsers will show a security warning.
- Expired certificate — any certificate in the chain is past its validity period.
- Hostname mismatch — the certificate does not cover the checked hostname. Browsers will show a security warning.
- TLS unreachable — no address completed a TLS handshake.
These conditions indicate an immediately broken service, so the score is irrelevant. All other findings contribute to the numeric score rather than triggering a hard override.
Email security: sending vs. receiving
Email security has two distinct concerns that apply to different domains:
-
Sending identity — SPF, DKIM, and DMARC.
These apply to every domain, because any domain can be spoofed in
the
From:header of an email, even domains that never send mail. A parked domain with no MX records still needs SPF and DMARC to prevent impersonation. - Receiving infrastructure — MX records, MTA-STS, DANE/TLSA, TLS-RPT, BIMI, FCrDNS, and DNSBL reputation. These only apply to domains that actively receive email. A parked or web-only domain with no MX records has no receiving infrastructure to check.
netray reflects this split in the ten scored checks of the Email section:
| Check | Applies to | Weight |
|---|---|---|
| SPF | All domains | 3 |
| DKIM | All domains | 3 |
| DMARC | All domains | 4 |
| MX | Domains with MX records | 2 |
| MTA-STS | Domains with MX records | 2 |
| TLS-RPT | Domains with MX records | 1 |
| BIMI | Domains with MX records | 2 |
| DANE | Domains with MX records | 2 |
| DNSBL | Domains with MX records | 2 |
| FCrDNS | Domains with MX records | 1 |
When netray detects no MX records, the seven MX-dependent checks are marked
not applicable and use a skip verdict.
Skip verdicts are excluded from both earned and possible totals, so a
parked domain is not penalised for missing receiving infrastructure.
Only SPF, DKIM and DMARC (weight 10) are scored.
Worked example: mail domain vs. parked domain
Mail domain (MX records present)
example.com runs email. All ten checks are scored.
SPF, DKIM, and DMARC pass; MTA-STS is in testing mode (warn); everything else passes.
- SPF, DKIM, DMARC: 10/10
- MX, DNSBL, FCrDNS: 5/5
- MTA-STS: 1/2 (a warn earns half the weight)
- TLS-RPT, DANE, BIMI: 5/5
- Email section: 21/22 = 95.5% → grade A
Parked domain (no MX records)
parked.example.com is used only for web. No MX records exist.
The seven MX-dependent checks are skipped automatically.
- SPF: 3/3, DMARC: 4/4 (mandatory even for parked domains)
- DKIM: skipped (no DKIM keys published)
- MX, MTA-STS, TLS-RPT, BIMI, DANE, DNSBL, FCrDNS: skipped (no MX)
- Email section: 7/7 = 100% → grade A+
A parked domain with correct SPF and DMARC records scores A+ on the email section — the absence of receiving infrastructure is not a deficiency.
Testing DKIM selectors
By default, netray's email checks use a built-in map of known DKIM selectors for major
email providers (Google Workspace, Microsoft 365, etc.). For custom selectors,
pass them via the dkim_selectors parameter:
# Via URL parameter
https://netray.info/?d=example.com&dkim_selectors=google,selector1
# Via curl
curl -s -X POST 'https://netray.info/api/v1/domains/example.com/runs?dkim_selectors=default,s1' \
| jq '.run.checks[] | select(.id | startswith("email.dkim"))'
Selector validation rules:
- Characters:
[a-zA-Z0-9-]only - Length: 1–63 characters per selector
- Count: 1–5 selectors per request
- Separator: comma (whitespace around commas is trimmed)
Invalid selectors return HTTP 400 before any backend call.
Absent dkim_selectors parameter: netray's email checks use their built-in provider map.
Section states
A section can be in one of three states:
| State | Effect on scoring | When it occurs |
|---|---|---|
| Scored | Contributes to the overall weighted average | Backend responded successfully |
| Errored | Excluded from scoring silently | Backend unreachable or timed out |
| Not applicable | Excluded from scoring; reason recorded in summary.not_applicable |
Email backend timed out internally (grade: "Skipped") |
An errored section does not lower the grade — the overall score is computed
from whatever sections are available. A footnote appears in the UI when
not_applicable is non-empty.
Cache behaviour
A check of a domain is stored in its history, and a visit within 5 minutes of the
last stored check shows that check instead of running a new one.
A run with dkim_selectors is always a fresh run: it is never joined with
another check of the domain and never stored in the history, since selector-specific
results cannot be shared (the API answers "recorded": "not_stored").