Mail configuration check API
Runs the MX, SPF and DMARC checks together and checks DKIM for any selectors you supply. DKIM is only checked for given selectors; a missing selector is not proof of missing DKIM.
GET https://api.probekit.dev/v1/mail/check
Updated 2026-10-09
Examples use the Base (eip155:8453) network.
Input
Query parameters.
| Name | Type | Required | Limits | Description |
|---|---|---|---|---|
domain | string | Yes | 1–1024 characters | The domain to check, for example example.com. |
dkim_selectors | array of string | No | at most 5 items; each 1–200 characters; default [] | DKIM selectors to include. DKIM keys can only be found by selector. |
Example request
curl -i 'https://api.probekit.dev/v1/mail/check?dkim_selectors=selector1&domain=example.com'import asyncio
import os
from eth_account import Account
from x402 import x402Client
from x402.http.clients import x402HttpxClient
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact import register_exact_evm_client
async def main() -> None:
signer = EthAccountSigner(Account.from_key(os.environ["EVM_PRIVATE_KEY"]))
client = register_exact_evm_client(x402Client(), signer, networks="eip155:8453")
async with x402HttpxClient(client) as http:
response = await http.get('https://api.probekit.dev/v1/mail/check', params={'dkim_selectors': ['selector1'], 'domain': 'example.com'})
response.raise_for_status()
print(response.json())
asyncio.run(main())
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});
const response = await fetchWithPayment("https://api.probekit.dev/v1/mail/check?dkim_selectors=selector1&domain=example.com");
console.log(await response.json());
curl: unpaid request — returns 402 with the price (PAYMENT-REQUIRED). The Python and JavaScript clients pay it and send the request again. Only ordinary wallets (EOA) can pay; smart-contract wallets get a 402 without charge. See payments.
Example response
{
"checked_at": "2026-10-24T12:00:00Z",
"data": {
"dkim": [
{
"domain": "example.com",
"key_bits": 2048,
"key_bytes": 294,
"key_type": "rsa",
"name": "selector1._domainkey.example.com",
"record": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs6ZX/GhLgrfk4EWkcLHLsAA/sQpuwePEUAD14pyq8i99ADfMijZm/6TcqsTnpjDtgAgZ8UR5WAOeY8V5E07QFzvrYrm+Yxv++MVwRgxn5pG4IASlD+gvXY56Pqx553B4GD7ktrskcBZJbPBgshVmzJyP+wrCy8UHfa4wF/AOjWFAUvVkI1iuxTcz3w/P2n0GKqiMh/KfURMc1RPVTqbFuSsoLzZjTAY5K8gLxPIffaXdwN9BQpzr+YSQSjf0gFTCo2CoBByxCR/7Me4yAyIyy7OKORBsvLsCwCFJxlU73QAwzuPW19NKdTHuv20wP+MsW4OXkgy9xmmDU6wvzBcmmwIDAQAB",
"revoked": false,
"selector": "selector1",
"tags": [
{
"name": "v",
"value": "DKIM1"
},
{
"name": "k",
"value": "rsa"
},
{
"name": "p",
"value": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs6ZX/GhLgrfk4EWkcLHLsAA/sQpuwePEUAD14pyq8i99ADfMijZm/6TcqsTnpjDtgAgZ8UR5WAOeY8V5E07QFzvrYrm+Yxv++MVwRgxn5pG4IASlD+gvXY56Pqx553B4GD7ktrskcBZJbPBgshVmzJyP+wrCy8UHfa4wF/AOjWFAUvVkI1iuxTcz3w/P2n0GKqiMh/KfURMc1RPVTqbFuSsoLzZjTAY5K8gLxPIffaXdwN9BQpzr+YSQSjf0gFTCo2CoBByxCR/7Me4yAyIyy7OKORBsvLsCwCFJxlU73QAwzuPW19NKdTHuv20wP+MsW4OXkgy9xmmDU6wvzBcmmwIDAQAB"
}
],
"testing": false
}
],
"dmarc": {
"adkim": "r",
"applied_policy": "reject",
"aspf": "r",
"domain": "example.com",
"org_domain": "example.com",
"pct": 100,
"policy": "reject",
"record": "v=DMARC1; p=reject; rua=mailto:[email protected]",
"record_domain": "example.com",
"rua": [
"mailto:[email protected]"
],
"ruf": [],
"subdomain_policy": "reject",
"tags": [
{
"name": "v",
"value": "DMARC1"
},
{
"name": "p",
"value": "reject"
},
{
"name": "rua",
"value": "mailto:[email protected]"
}
]
},
"domain": "example.com",
"mx": {
"domain": "example.com",
"hosts": [
{
"addresses": [
"192.0.2.25"
],
"exchange": "mx1.example.com",
"preference": 10,
"resolved": true
},
{
"addresses": [
"192.0.2.26"
],
"exchange": "mx2.example.com",
"preference": 20,
"resolved": true
}
],
"implicit_mx": false,
"null_mx": false
},
"spf": {
"all_qualifier": "-",
"dns_lookup_count": 1,
"domain": "example.com",
"lookup_count_complete": true,
"record": "v=spf1 ip4:192.0.2.0/24 include:_spf.example.net -all",
"tree": {
"children": [
{
"children": [],
"domain": "_spf.example.net",
"error": null,
"record": "v=spf1 ip4:198.51.100.0/24 -all",
"terms": [
{
"kind": "mechanism",
"name": "ip4",
"qualifier": null,
"raw": "ip4:198.51.100.0/24",
"value": "198.51.100.0/24"
},
{
"kind": "mechanism",
"name": "all",
"qualifier": "-",
"raw": "-all",
"value": null
}
],
"via": "include"
}
],
"domain": "example.com",
"error": null,
"record": "v=spf1 ip4:192.0.2.0/24 include:_spf.example.net -all",
"terms": [
{
"kind": "mechanism",
"name": "ip4",
"qualifier": null,
"raw": "ip4:192.0.2.0/24",
"value": "192.0.2.0/24"
},
{
"kind": "mechanism",
"name": "include",
"qualifier": null,
"raw": "include:_spf.example.net",
"value": "_spf.example.net"
},
{
"kind": "mechanism",
"name": "all",
"qualifier": "-",
"raw": "-all",
"value": null
}
],
"via": "root"
},
"void_lookup_count": 0
}
},
"endpoint": "mail.check",
"findings": [],
"meta": {
"cache_hit": false,
"duration_ms": 42,
"fresh_until": "2026-10-24T12:05:00Z",
"observed_at": "2026-10-24T12:00:00Z",
"resolver": "example",
"source": "dns"
},
"request_id": "req_aaaaaaaaaaaaaaaaaaaaaaaaaa",
"schema_version": "1.0",
"status": "ok"
}
Response fields
| Field | Meaning |
|---|---|
request_id | Unique id of this request. Quote it when you contact support. |
endpoint | The endpoint id, for example mail.spf. |
schema_version | Version of the response format, currently 1.0. |
checked_at | When the check ran (ISO 8601, UTC). |
status | ok, info, warning or error from the worst finding, or unknown when the check was incomplete. unknown is not an error in the domain. |
data | The check's own data. Its fields are listed below. |
findings | What was found: code, severity, message, explanation and evidence. |
meta | Duration, cache use, resolver and how long the result stays fresh. |
Fields in data
| Field | Type |
|---|---|
domain | string |
mx | MxData or null |
spf | SpfData or null |
dmarc | DmarcData or null |
dkim | array of DkimData |
Status codes
| Status | Code | Meaning | Do you pay? |
|---|---|---|---|
| 200 | — | The result is delivered, also when it has negative findings. | Yes: charged once per payment; redelivery of the same payment is free |
| 402 | — | Payment is required, or the attached payment was refused. The body states the price. | No |
| 400 | bad_request | The request is malformed. | No |
| 409 | payment_in_progress, payment_bound_to_other_request | This payment is already being processed, or it is bound to a different request. No new payment is taken. | No |
| 422 | invalid_input | The input failed validation on a request that carries a payment; nothing is charged. Without a payment, invalid input gets a 402 whose error field names the problem. | No |
| 429 | rate_limited | Too many requests from this IP address or payer. | No |
| 502 | check_failed | The check could not be completed. | No |
| 503 | busy, unavailable, payments_unavailable | The service is busy or temporarily unavailable. | No |
| 504 | deadline_exceeded | The check ran out of time. | No |
Errors use the common error body. See errors.
Findings this endpoint can return
Check (1)
- Warning
CHECK_QUERY_BUDGET_EXCEEDEDThe check stopped after reaching its DNS query limit.
DKIM (7)
- Error
DKIM_INVALID_KEYThe DKIM public key is not valid base64. - Warning
DKIM_KEY_REVOKEDThe DKIM key for this selector is revoked (empty p tag). - Error
DKIM_MULTIPLE_RECORDSMore than one DKIM record was found for the selector. - Warning
DKIM_NOT_FOUNDNo DKIM key was found for the given selector. - Error
DKIM_SYNTAX_ERRORThe DKIM record contains a tag that cannot be parsed or has an invalid value. - Info
DKIM_TESTING_MODEThe DKIM record is in testing mode (t=y). - Warning
DKIM_WEAK_KEYThe RSA key is shorter than 1024 bits.
DMARC (10)
- Info
DMARC_INHERITEDThe DMARC policy comes from the organisational domain. - Error
DMARC_INVALID_POLICYThe DMARC record has a missing or invalid p tag. - Error
DMARC_MULTIPLE_RECORDSMore than one DMARC record was found. - Warning
DMARC_NOT_FOUNDNo DMARC record was found for the domain or its organisational domain. - Info
DMARC_NO_RUAThe DMARC record has no aggregate report address (rua). - Info
DMARC_PARTIAL_PCTThe DMARC policy applies to only part of the failing mail (pct below 100). - Info
DMARC_POLICY_NONEThe DMARC policy is p=none (monitoring only). - Error
DMARC_SYNTAX_ERRORThe DMARC record contains a tag that cannot be parsed or has an invalid value. - Info
DMARC_UNKNOWN_TAGThe DMARC record contains tags that are not defined. - Info
DMARC_UNSUPPORTED_REPORT_URIA DMARC report address uses a scheme other than mailto.
DNS (3)
- Error
DNS_DOMAIN_NOT_FOUNDThe DNS resolver reported that the domain does not exist. - Warning
DNS_RESOLVER_FAILUREThe DNS resolver returned a server failure. - Warning
DNS_RESOLVER_TIMEOUTThe DNS resolver did not answer in time.
MX (5)
- Warning
MX_NOT_FOUNDThe domain has no MX records. - Info
MX_NULLThe domain publishes a null MX and does not accept mail. - Error
MX_NULL_WITH_OTHERSA null MX is published together with other MX records. - Error
MX_TARGET_IS_IPAn MX record points to an IP address instead of a host name. - Error
MX_TARGET_UNRESOLVABLEAn MX host has no A or AAAA records.
SPF (13)
- Error
SPF_INCLUDE_LOOPThe SPF chain returns to a domain it already evaluated. - Error
SPF_INCLUDE_NOT_FOUNDAn included or redirected domain has no SPF record. - Info
SPF_MACRO_NOT_EVALUATEDAn SPF record contains macros that were not evaluated. - Error
SPF_MULTIPLE_RECORDSMore than one SPF record was found for a name in the SPF chain. - Warning
SPF_NEUTRAL_ALLThe SPF policy ends with ?all. - Warning
SPF_NOT_FOUNDNo SPF record was found for the domain. - Warning
SPF_NO_ALLThe SPF record has no all mechanism and no redirect. - Error
SPF_PASS_ALLThe SPF policy ends with +all. - Warning
SPF_PTR_MECHANISMAn SPF record uses the ptr mechanism. - Info
SPF_SOFTFAIL_ALLThe SPF policy ends with softfail (~all). - Error
SPF_SYNTAX_ERRORAn SPF record contains a term that cannot be parsed. - Error
SPF_TOO_MANY_LOOKUPSThe SPF record needs more than 10 DNS lookups. - Error
SPF_TOO_MANY_VOID_LOOKUPSMore than two SPF lookups returned no data.