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:

SectionToolWhat it measuresWeight
IPthe IP inspectorNetwork type, hosting reputation, geolocation10%
DNSthe DNS inspectorDNSSEC, CAA records, NS delegation, HTTPS records20%
TLSthe TLS inspectorCertificate chain, protocol version, cipher strength35%
HTTPthe HTTP inspectorSecurity headers, HSTS, CORS, cookie attributes20%
Emailnetray's email checksSPF, DKIM, DMARC, MTA-STS, DANE, BIMI, DNSBL15%

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:

GradeScoreMeaning
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-failFailing

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:

CheckApplies toWeight
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:

StateEffect on scoringWhen 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").