Endpoints
POST /api/trace
Create a new trace. Returns a unique email address to send a test email to and a trace ID for polling.
Example:
curl -X POST https://mailcheck.biz/api/trace
Response:
{
"email": "abcdefgh@xyzabc.mailcheck.biz",
"traceID": "xyzabc-abcdefgh"
}
GET /api/status/{id}
Poll the status of a trace. Returns the current processing state.
Example:
curl https://mailcheck.biz/api/status/xyzabc-abcdefgh
Response:
{
"status": "pending"
}
| Status | Meaning |
pending | Waiting for email to arrive |
analyzing | Email received, analysis in progress |
ready | Analysis complete, report available |
expired | Trace expired (5 min timeout) |
notfound | Unknown trace ID |
GET /api/report/{id}
Retrieve the full analysis report as JSON. Stored reports are normalized on read, so old trace files are returned using the current public contract.
Example:
curl https://mailcheck.biz/api/report/xyzabc-abcdefgh
Response excerpt:
{
"id": "xyzabc-abcdefgh",
"timestamp": "2026-03-06T14:30:00Z",
"email_auth": {
"spf": { "result": "pass" },
"dkim": [{
"result": "pass",
"domain": "example.com",
"selector": "google",
"algorithm": "rsa-sha256",
"key_type": "rsa",
"key_bits": 2048,
"alignment_result": "pass",
"alignment_mode": "relaxed",
"security_score": 82,
"security_rating": "good",
"selector_provider": "Google Workspace",
"signing_software": "Gmail",
"replay_window": "moderate",
"header_coverage": {
"critical_count": 9,
"critical_total": 13,
"missing_critical": ["Reply-To", "CC"]
}
}],
"dmarc": {
"result": "fail",
"lookup_status": "present",
"policy": "reject"
}
},
"blacklists": {
"status": "clean",
"checked": ["zen.spamhaus.org", "b.barracudacentral.org"],
"clean": true
},
"domain_dns": {
"mx_state": "explicit",
"mx_records": [{"host": "mail.example.com", "priority": 10}]
},
"active_scan": {
"banner": "220 mail.example.com ESMTP Postfix",
"mta_name": "Postfix",
"jarm_hash": "2ad2ad16d2ad2ad0004...",
"probe_os_guess": "Linux 4.x-6.x",
"probe_ja4ts": "65535_2-1-3-1-1-4-1-1-8_1460_8",
"probe_os_confidence": 0.6,
"ja4tscan_hash": "65535_2-1-3-1-1-4-1-1-8_1460_8_1-2-4-8",
"ja4tscan_pattern": "Linux/FreeBSD (exponential backoff)",
"ja4x_hash": "a5c7e3d...",
"ja4s_hash": "t13d1716..."
},
"server_identity": {
"primary_mta": "Postfix",
"trust_score": 87,
"rating": "Good"
}
}
Key field semantics:
| Field | Meaning |
email_auth.dmarc.result | DMARC message verdict: pass, fail, or unknown |
email_auth.dmarc.lookup_status | DMARC DNS lookup status: present, none, temperror, or permerror |
email_auth.dkim[].result | DKIM verification: pass, fail, none, or temperror |
email_auth.dkim[].security_score | Per-signature security score 0-100 (higher = better) |
email_auth.dkim[].security_rating | Rating: excellent (≥90), good (≥75), fair (≥50), poor (≥25), critical (<25) |
email_auth.dkim[].alignment_result | DMARC alignment: pass or fail |
email_auth.dkim[].selector_provider | Detected email provider from DKIM selector (e.g. "Google Workspace", "Microsoft 365") |
email_auth.dkim[].signing_software | Detected signing software (e.g. "Gmail", "Rspamd", "OpenDKIM") |
email_auth.dkim[].replay_window | Replay attack window: safe (≤7d), moderate (7-30d), wide (>30d), unlimited |
email_auth.dkim[].header_coverage | Object: critical_count, critical_total, missing_critical[], oversigned[] |
email_auth.dkim[].security_checks[] | Array: id, title, status (pass/warn/fail/info), severity, detail |
email_auth.dkim[].body_hash_verified | Independent body hash check: pass, fail, or error |
email_auth.dkim[].body_modified | True if body was modified after DKIM signing (bh= mismatch) |
email_auth.dkim[].failure_type | Classified failure: body_modified, header_modified, key_mismatch, dns_error, syntax_error, expired, revoked |
email_auth.dkim[].syntax_valid | True if DKIM-Signature syntax passes all RFC 6376 checks |
blacklists.status | Authoritative DNSBL state: clean, listed, or unknown |
blacklists.clean | Compatibility boolean. true only when status=clean; use status for tri-state logic. |
domain_dns.mx_state | Mail routing state: explicit, implicit, none, or dns_error |
tls.parrot_analysis.verdict | uTLS / TLS parrot verdict: clean, suspicious (max_conf ≥ 0.7), or parrot_detected (max_conf ≥ 0.9) |
tls.parrot_analysis.max_confidence | Highest confidence value among triggered parrot rules (0.0–1.0) |
tls.parrot_analysis.triggered_rules[] | Array of {key, value, confidence, evidence} for each rule that fired |
tls.parrot_analysis.extension_order_hash | SHA-256 prefix of ClientHello extension type order — stable across sessions for honest stacks |
tls.parrot_analysis.ch_length_mod_512 | ClientHello length mod 512 (Chrome pads to 512-byte boundary; non-zero on Chrome impostors) |
tls.parrot_analysis.psk_binder_lengths | Lengths of PSK binders, if pre_shared_key extension present |
tls.parrot_analysis.has_cookie | Whether the cookie extension (44) was present in the initial ClientHello (illegal — only valid in HRR retry) |
tls.parrot_analysis.grease_values[] | GREASE codepoints observed across cipher_suites/extensions/groups |
tls.parrot_analysis.ch_record_count | Number of TLS records the ClientHello was split across (Chrome ≥ 124 fragments, libraries don't) |
tls.parrot_analysis.handshake_rtt_ns | Wall-clock duration of the handshake in nanoseconds |
tls.parrot_analysis.ech_grease_aeads[] | ECH GREASE AEAD IDs offered (Chrome only sends AES-128-GCM = 0x0001) |
tls.parrot_analysis.ech_grease_payload_len | ECH GREASE payload length in bytes (must be a multiple of 32 for Chrome) |
tls.parrot_analysis.session_history_count | Number of recent ClientHellos seen from this source IP (used by cross-session rule) |
TLS Parrot Rule Catalog
The detector evaluates 31 invariant rules. Each rule emits a signal with source=tls-parrot, a stable key, and a confidence in [0,1]. The verdict is derived from the maximum confidence:
| Rule key | Confidence | What it detects |
cookie_in_initial_ch | 0.95 | cookie extension (44) in initial CH — only legal on HRR retry |
psk_binder_length_invalid | 0.90 | PSK binder length doesn't match negotiated hash size |
psk_not_last | 0.95 | pre_shared_key extension is not the final extension (RFC 8446 violation) |
keyshare_pq_advertised_not_sent | 0.90 | X25519MLKEM768 in supported_groups but not in key_share |
chrome_profile_with_firefox_ext | 0.95 | Chrome GREASE+ALPS+CompressCert plus Firefox-only extensions |
chrome_missing_padding_no_pq | 0.95 | Chrome profile but no padding extension and no PQ key share |
grease_outside_chrome_set | 0.85 | GREASE codepoints used that don't match Chrome's published rotation |
ext_order_mismatch_chrome | 0.85 | Extension order hash doesn't match any known Chrome golden preset |
ext_order_mismatch_firefox | 0.85 | Extension order hash doesn't match any known Firefox golden preset |
ja3_chrome_ja4_firefox | 0.90 | JA3 fingerprint says Chrome but JA4 says Firefox (or vice versa) |
alps_codepoint_pair_invalid | 0.85 | Only one of ALPS 17513/17613 present (Chrome always sends both) |
compress_cert_brotli_only_chrome | 0.80 | compress_certificate=brotli without other Chrome markers |
delegated_cred_with_legacy_sig | 0.80 | delegated_credential extension paired with legacy SHA-1 sig algs |
renegotiation_info_in_tls13 | 0.75 | renegotiation_info extension on a TLS 1.3-only client (illegal) |
session_ticket_with_psk | 0.75 | session_ticket extension and PSK both present (mutually exclusive) |
tls12_only_with_chrome_profile | 0.85 | Chrome markers but no TLS 1.3 in supported_versions |
sni_with_dot_atend | 0.70 | SNI hostname ends in trailing dot (some libs forget to strip) |
ext_count_outlier_chrome | 0.75 | Extension count outside Chrome's known range |
ech_grease_chacha20 | 0.90 | ECH GREASE offers ChaCha20 AEAD (Chrome never does) |
ech_grease_aes256 | 0.85 | ECH GREASE offers AES-256-GCM (Chrome only sends AES-128-GCM) |
ech_grease_payload_length_wrong | 0.80 | ECH GREASE payload length not a multiple of 32 bytes |
ext_order_unstable_cross_session | 0.85 | Extension order hash differs across sessions from same IP (Chrome is stable) |
ch_not_fragmented_chrome | 0.80 | Chrome profile but ClientHello fits in 1 record (Chrome ≥124 fragments) |
ch_length_mod_512_nonzero_chrome | 0.75 | Chrome profile but CH length not aligned to 512-byte boundary |
pq_advertised_fast_handshake | 0.65 | X25519MLKEM768 advertised but handshake completed in <2ms (no real PQ math) |
hrr_response_inconsistent | 0.85 | Active HRR probe: client refused to retry with selected group (impostor doesn't implement HRR) |
hrr_no_response | 0.70 | Active HRR probe: client never responded to HelloRetryRequest |
unknown_ext_rejected | 0.75 | Active probe: client aborted on unknown ServerHello extension (real stacks ignore) |
alpn_mismatch_accepted | 0.85 | Active probe: client accepted server ALPN it didn't offer |
downgrade_canary_ignored | 0.90 | Active probe: client accepted forged TLS 1.2 ServerHello downgrade (no canary check) |
backconnect_lib_mismatch | 0.80 | Backconnect JA3S indicates a different TLS library than the inbound CH claimed |
GET /api/report/{id}?fields=...
Retrieve a filtered report with only the specified fields. The id and timestamp fields are always included. Unknown field names are silently ignored.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=tls,email_auth"
Query parameter:
| Parameter | Type | Description |
fields | string | Comma-separated list of top-level field names to include. Nested field selection is not supported. |
Available fields:
- envelope
- dns_queries
- tcp_fingerprint
- tls
- email_auth
- ptr
- blacklists
- dns_leak
- headers
- smtp_dialog
- active_scan
- client_verdict
- domain_dns
- ns_servers
- whois
- geoip
- mail_security
- port_scan
- web_probe
- web_fingerprint
- network_anomalies
- server_identity
- received_analysis
- route_trace
- transport_fp
Trust Score & Server Identity
The server_identity field includes a category-based Trust Score (0-100) with detailed breakdown and actionable recommendations.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=server_identity"
Response (server_identity object):
{
"server_identity": {
"primary_mta": "Postfix",
"mta_confidence": 0.95,
"trust_score": 87,
"rating": "Good",
"bonus": 4,
"categories": [
{"name": "authentication", "cap": 30, "penalty": 0},
{"name": "blacklists", "cap": 25, "penalty": 0},
{"name": "dns_config", "cap": 15, "penalty": 5},
{"name": "tls", "cap": 10, "penalty": 0},
{"name": "server_identity", "cap": 10, "penalty": 0},
{"name": "headers", "cap": 5, "penalty": 0},
{"name": "advanced", "cap": 5, "penalty": 0}
],
"recommendations": [
{
"category": "dns_config",
"priority": "high",
"title": "Fix forward-confirmed rDNS",
"fix": "Ensure the A record resolves to the sending IP",
"impact": 5
}
]
}
}
Trust Score categories:
| Category | Max Penalty | Description |
authentication | 30 | SPF, DKIM (incl. per-signature security score), DMARC results and policy strength |
blacklists | 25 | DNSBL listings (tiered by severity) |
dns_config | 15 | PTR, FCrDNS, EHLO/PTR match, MX record |
tls | 10 | Certificate validity, self-signed, cross-port consistency |
server_identity | 10 | MTA/OS/TLS consistency, anomaly detection |
headers | 5 | Received chain integrity, timestamp order, TLS hops |
advanced | 5 | DNS leak, domain age, web presence |
infrastructure | 30 | Open proxies, VPN services, suspicious ports, HTTPS cert, HTTP/2, TLS versions |
Infrastructure penalties (detailed):
| Finding | Penalty |
| Open proxy detected | -25 |
| Suspicious ports (IRC, database, etc.) | -15 |
| Tor relay detected | -10 |
| VPN service detected | -8 |
| RDP exposed (port 3389) | -5 |
| Self-signed HTTPS certificate | -5 |
| Legacy TLS only (no TLS 1.2+) | -3 |
| SMTP/HTTPS cert mismatch | -2 |
| No HTTP/2 support | -1 |
Rating scale:
| Score | Rating |
| 90-100 | Excellent |
| 75-89 | Good |
| 50-74 | Fair |
| 25-49 | Poor |
| 0-24 | Critical |
Recommendation priority levels: high,
medium,
low. Each recommendation includes an
impact field showing how many points would be recovered if the issue is fixed.
Bonus points (up to +10) are awarded for advanced security standards: MTA-STS enforce (+3), DANE TLSA (+3), BIMI (+1), DMARC p=reject (+1).
TLS Fields (tls)
The tls field contains detailed TLS analysis of the sender's STARTTLS connection, including ClientHello fingerprinting, cipher suite analysis, and security assessment.
| Field | Type | Description |
used | boolean | Whether TLS was used (STARTTLS) |
version | string | Negotiated TLS version (e.g. "TLS 1.2") |
cipher_suite | string | Negotiated cipher suite name |
server_name | string | SNI hostname from ClientHello |
ja3_hash | string | JA3 fingerprint (MD5 hash) |
ja3_string | string | Raw JA3 string before hashing |
ja4_hash | string | JA4 TLS fingerprint (SHA-256 based) |
session_resumed | boolean | Whether TLS session was resumed via ticket/PSK |
negotiated_alpn | string | ALPN protocol negotiated (unusual for SMTP) |
detected_library | string | Detected TLS library (OpenSSL, Go, BoringSSL, etc.) |
inferred_mta | string | MTA inferred from TLS library |
inferred_mta_method | string | Method used for MTA inference: tls_deep_fingerprint |
inferred_mta_evidence | string[] | Evidence list supporting the MTA inference (e.g. cipher composition, extension order patterns) |
Security Assessment (tls.security)
| Field | Type | Description |
grade | string | Overall TLS grade: A+, A, B, C, D, F |
forward_secrecy | boolean | Whether all offered ciphers use forward secrecy (ECDHE/DHE) |
aead_only | boolean | Whether only AEAD ciphers are offered (GCM, CCM, ChaCha20) |
min_version | string | Lowest TLS version offered |
max_version | string | Highest TLS version offered |
has_tls13 | boolean | Whether TLS 1.3 is offered |
has_deprecated | boolean | Whether deprecated TLS 1.0/1.1 is offered |
weak_ciphers | string[] | List of weak cipher suites offered (NULL, RC4, 3DES) |
strong_ciphers | string[] | List of strong AEAD cipher suites offered |
warnings | string[] | Security warnings |
ClientHello (tls.client_hello)
| Field | Type | Description |
record_version | string | TLS record layer version |
client_version | string | ClientHello protocol version |
session_id_length | int | Length of legacy session ID (0=TLS 1.3 native, 32=compat) |
client_hello_length | int | Total raw ClientHello size in bytes |
grease_count | int | Number of GREASE values (indicates BoringSSL/Chrome) |
supported_versions | string[] | TLS versions offered (e.g. ["TLS 1.3", "TLS 1.2"]) |
cipher_suites | string[] | Offered cipher suites with IANA names |
extensions | string[] | TLS extensions with names |
supported_groups | string[] | Elliptic curve / DH groups with names |
key_share_groups | string[] | Deprecated. Use key_share_entries instead. Still present for backward compatibility |
signature_algorithms | string[] | Signature algorithms with names |
compression_methods | string[] | Compression methods (should be ["null"] only) |
psk_modes | string[] | PSK key exchange modes (TLS 1.3) |
alpn_protocols | string[] | ALPN protocol list (unusual for SMTP) |
ec_point_formats | string[] | EC point format list |
extension_flags | object | Boolean flags for 13 notable TLS extensions (see table below) |
compress_cert_algorithms | string[] | Certificate compression algorithms from ext 27 (brotli, zlib, zstd) |
signature_algorithms_cert | string[] | Certificate-specific signature algorithms from ext 50 |
record_size_limit | int | Record size limit value from ext 28 (0 if absent) |
padding_length | int | Padding extension data length in bytes from ext 21 |
key_share_entries | object[] | Key share groups with key lengths ({group, key_length}). Replaces key_share_groups |
delegated_cred_algorithms | string[] | Delegated credential signature algorithms from ext 34 |
has_cbc_suites | bool | Whether CBC-mode cipher suites are offered |
has_ccm_suites | bool | Whether CCM-mode cipher suites are offered (wolfSSL/mbed TLS) |
has_ffdhe_groups | bool | Whether finite-field DH groups are offered (GnuTLS/Java) |
has_post_quantum | bool | Whether post-quantum/hybrid key exchange groups are offered |
tls13_cipher_order | string | TLS 1.3 cipher suite order (e.g. "1301→1302→1303") |
cipher_suite_count | int | Total number of cipher suites offered |
extension_count | int | Total number of extensions in ClientHello |
key_share_count | int | Number of key share entries offered |
cipher_categories | object | Cipher suite composition breakdown by key exchange type (see Cipher Composition below) |
strength_ordered | bool | Whether cipher suites appear in @STRENGTH order (256-bit before 128-bit) |
extension_order_first | string | First non-GREASE extension name (e.g. "server_name", "extended_master_secret") |
extension_order_hash | string | SHA-256 hash of extension ID order (GREASE filtered, 12 hex chars) |
group_order_pattern | string | Named group order pattern (e.g. "x25519_first", "p256_first", "p384_before_p521") |
ffdhe_count | int | Number of finite-field DH groups (ffdhe2048, ffdhe3072, etc.) |
version_pattern | string | TLS version triplet: record/handshake/max (e.g. "rec=0301,hs=0303,max=0304") |
has_legacy_versions | bool | Whether TLS 1.0 or 1.1 is in supported_versions extension |
Cipher Composition (tls.client_hello.cipher_categories)
Breaks down offered cipher suites by key exchange and authentication type. Useful for distinguishing MTAs with similar TLS libraries (e.g. Postfix vs Exim on OpenSSL).
| Field | Type | Description |
ecdhe_ecdsa | int | Number of ECDHE-ECDSA cipher suites |
ecdhe_rsa | int | Number of ECDHE-RSA cipher suites |
dhe_rsa | int | Number of DHE-RSA cipher suites |
rsa | int | Number of static RSA cipher suites |
other | int | Number of other cipher suites (PSK, anon, etc.) |
has_camellia | bool | Whether Camellia cipher suites are offered |
has_aria | bool | Whether ARIA cipher suites are offered |
has_ccm | bool | Whether CCM cipher suites are offered |
has_ccm8 | bool | Whether CCM-8 (short tag) cipher suites are offered |
has_low_grade | bool | Whether low-grade cipher suites are offered (EXPORT, NULL) |
Extension Flags Reference
| Flag key | Ext ID | What it indicates |
extended_master_secret | 23 | Protection against triple handshake attack |
encrypt_then_mac | 22 | Encrypt-then-MAC for CBC suites (OpenSSL/GnuTLS) |
session_ticket | 35 | TLS session resumption via tickets |
renegotiation_info | 65281 | Secure renegotiation (TLS 1.2) |
status_request | 5 | OCSP stapling support |
signed_cert_timestamps | 18 | Certificate Transparency (SCT) |
grease | — | GREASE values present (BoringSSL/NSS) |
padding | 21 | ClientHello padding (BoringSSL) |
post_handshake_auth | 49 | Post-handshake authentication (NSS/Firefox) |
compress_certificate | 27 | Certificate compression (BoringSSL/NSS) |
alps | 17513 | Application-Layer Protocol Settings (BoringSSL only) |
ech | 65037 | Encrypted Client Hello (modern browsers) |
delegated_credential | 34 | Delegated Credentials (RFC 9345) |
Client Certificate (tls.client_cert)
Present only when the sending server provides a client certificate during TLS handshake (mTLS). Rare in SMTP but seen on corporate mail servers. Includes full x509 certificate details and chain information.
| Field | Type | Description |
presented | bool | Whether a client certificate was presented |
acquisition_method | string | server_requested_client_provided or server_requested_not_provided |
subject | string | Full subject DN |
issuer | string | Full issuer DN |
organization | string | Subject Organization (O) — company name |
organizational_unit | string | Subject Organizational Unit (OU) |
country | string | Subject Country (C) |
province | string | Subject State/Province (ST) |
locality | string | Subject Locality/City (L) |
common_name | string | Subject Common Name (CN) |
san | string[] | Subject Alternative Names (DNS + IP) |
email_addresses | string[] | Email addresses from SAN extension |
uris | string[] | URI SANs |
valid_from | string | Certificate NotBefore date |
valid_to | string | Certificate NotAfter date |
remaining_validity | string | Human-readable remaining validity or "expired X ago" |
is_expired | bool | Whether the certificate has expired |
is_self_signed | bool | Whether subject matches issuer |
is_ca | bool | Whether BasicConstraints CA flag is set |
serial_number | string | Certificate serial number (hex) |
version | int | X.509 version (typically 3) |
signature_algorithm | string | Signature algorithm (e.g. ECDSA-SHA256) |
fingerprint | string | SHA-256 fingerprint of DER certificate |
key_type | string | Public key algorithm (RSA, ECDSA, Ed25519) |
key_bits | int | Public key size in bits |
key_usage | string[] | Key usage flags (DigitalSignature, KeyEncipherment, etc.) |
ext_key_usage | string[] | Extended key usage (ClientAuth, EmailProtection, etc.) |
subject_key_id | string | Subject Key Identifier (hex) |
authority_key_id | string | Authority Key Identifier (hex) |
ocsp_servers | string[] | OCSP responder URLs |
crl_distribution_points | string[] | CRL distribution point URLs |
issuing_certificate_url | string[] | Issuing CA certificate URLs (AIA) |
chain_length | int | Total number of certificates in the chain |
chain_certs | object[] | Intermediate/root certificates (same fields as above, excluding chain-specific fields) |
ja4x_hash | string | JA4X fingerprint of the client certificate (OID-based hash of issuer/subject/extensions using raw hex OID bytes per JA4+ spec) |
ja4x_software | string | Certificate generation software identified via JA4X lookup (e.g. "OpenSSL", "Go crypto/x509", "Let's Encrypt") |
TLS Security Grading
| Grade | Criteria |
| A+ | TLS 1.3 only, all AEAD ciphers |
| A | TLS 1.2+ with AEAD ciphers and forward secrecy |
| B | TLS 1.2+ but CBC ciphers offered |
| C | 3DES cipher suites offered |
| D | TLS 1.0/1.1 or RC4 offered |
| F | NULL ciphers offered — no encryption |
Active Scan & TCP Fingerprinting
The active_scan field contains results from actively probing the sender's mail server on port 25: SMTP banner, EHLO capabilities, JARM TLS fingerprint, TCP SYN-ACK fingerprint, JA4TScan OS detection, and TLS certificate details.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=active_scan"
Response (active_scan object):
{
"active_scan": {
"banner": "220 mail.example.com ESMTP Postfix",
"mta_name": "Postfix",
"jarm_hash": "2ad2ad16d2ad2ad00042d42d00000069d641f34fe76acdc05c40262f8815e5",
"ehlo_caps": ["PIPELINING", "SIZE 10240000", "STARTTLS", "AUTH PLAIN LOGIN"],
"probe_os_guess": "Linux 4.x-6.x",
"probe_ja4ts": "65535_2-1-3-1-1-4-1-1-8_1460_8",
"probe_os_confidence": 0.6,
"ja4tscan_hash": "65535_2-1-3-1-1-4-1-1-8_1460_8_1-2-4-8",
"ja4tscan_os": "Linux 4.x-6.x",
"ja4tscan_retransmits": "1-2-4-8",
"ja4tscan_pattern": "Linux/FreeBSD (exponential backoff)",
"server_uptime": "14d 6h",
"cert_subject": "mail.example.com",
"cert_issuer": "Let's Encrypt"
}
}
Key fields:
| Field | Type | Description |
banner | string | SMTP banner from port 25 |
mta_name | string | Detected MTA software (Postfix, Exim, Exchange, etc.) |
jarm_hash | string | JARM TLS fingerprint of port 25 |
ehlo_caps | string[] | EHLO capability lines |
probe_os_guess | string | OS guess from SYN-ACK TCP fingerprint |
probe_ja4ts | string | JA4TS hash of the server's SYN-ACK response (window_options_mss_ws) |
probe_os_confidence | float | OS detection confidence (0.0–0.7, capped because SYN-ACK depends on client's SYN) |
ja4tscan_hash | string | Full JA4TScan hash: JA4TS + retransmit timing section |
ja4tscan_os | string | OS guess from JA4TScan (combines SYN-ACK + retransmit pattern) |
ja4tscan_retransmits | string | Retransmit timing pattern, e.g. 1-2-4-8 (exponential) or 1-1-1-R2 (flat+RST) |
ja4tscan_pattern | string | Classified OS family: Linux/FreeBSD (exponential backoff), Windows (RST after retransmits), macOS (flat 1s retransmits) |
ja4x_hash | string | JA4X TLS certificate fingerprint (OID-based hash of server certificate using raw hex OID bytes) |
ja4x_software | string | Certificate generation software identified via JA4X (e.g. "OpenSSL", "Go crypto/x509", "Let's Encrypt") |
ja4s_hash | string | JA4S ServerHello fingerprint (server-selected TLS parameters hash) |
ja3s_hash | string | JA3S hash (MD5) of SMTP ServerHello — compatible with Shodan/GreyNoise databases |
server_hello_extensions | string[] | Extensions present in the server's ServerHello response |
server_uptime | string | Estimated server uptime from TCP timestamps |
JA4TScan methodology:
JA4TScan sends a standardized SYN packet (MSS=1460, WS=7, SACK, Timestamps) and captures the server's SYN-ACK response plus retransmissions over 15 seconds. The retransmit timing pattern is OS-specific:
| Pattern | OS Family | Example |
| Exponential backoff | Linux / FreeBSD | 1-2-4-8 |
| Flat + RST | Windows | 1-1-1-R2 |
| Flat 1s | macOS | 1-1-1-1-1 |
Web & Infrastructure Fingerprint (web_fingerprint)
The web_fingerprint field contains results from analyzing the sender's web infrastructure on ports 80/443: TLS fingerprints, HTTP/2 fingerprint, web server detection, certificate analysis, reverse IP lookup, open proxy/VPN detection, and port scanning.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=web_fingerprint"
Response (web_fingerprint object):
{
"web_fingerprint": {
"ja3s": "ae4eef27d57a55a3c5765e9f8cfd5bf0",
"ja4s": "t13d3112_a5b2c3...",
"jarm_https": "27d40d40d00042d...",
"tls_versions": [
{"version": "TLS 1.2", "supported": true, "cipher_suite": "ECDHE-RSA-AES128-GCM-SHA256"},
{"version": "TLS 1.3", "supported": true, "cipher_suite": "TLS_AES_128_GCM_SHA256"}
],
"http2_supported": true,
"http2_fingerprint": "3:128;4:65535|0|0",
"http2_server": "nginx",
"server_detect": {
"server": "nginx",
"version": "1.24.0",
"confidence": 0.92,
"technology": "PHP",
"cdn": "Cloudflare"
},
"reverse_ip_domains": ["example.com", "shop.example.com"],
"cert_comparison": {"verdict": "smtp_neglected"},
"proxy_checks": [{"port": 1080, "protocol": "socks5", "status": "open"}],
"port_profile": "proxy_host",
"scan_level": 2
}
}
Key fields:
| Field | Type | Description |
ja3s | string | JA3S hash (MD5) of HTTPS ServerHello |
ja4s | string | JA4S hash (SHA-256) of HTTPS ServerHello |
jarm_https | string | JARM fingerprint of port 443 |
tls_versions | object[] | TLS version support (1.0-1.3) with cipher per version |
http2_supported | bool | HTTP/2 support via ALPN h2 |
http2_fingerprint | string | Akamai-format HTTP/2 fingerprint |
server_detect | object | Web server detection (12 methods, see below) |
reverse_ip_domains | string[] | Domains from SAN of default cert (no SNI) |
cert_comparison | object | SMTP vs HTTPS cert comparison verdict |
proxy_checks | object[] | Open proxy detection (SOCKS4/5, HTTP CONNECT) |
vpn_checks | object[] | VPN detection (OpenVPN, PPTP, WireGuard, etc.) |
port_profile | string | Profile: clean, proxy_host, vpn_server, compromised |
open_ports | object[] | Open ports: port, transport (tcp/udp), category, banner, response_ms, level (1 or 2) |
scan_level | int | 1 (25 key ports) or 2 (expanded, triggered by proxy/VPN findings) |
https_cert | object | HTTPS certificate (with SNI): subject, issuer, san_domains, san_ips, valid_from, valid_to, key_type, chain_length, self_signed, expired, fingerprint, ja4x, is_wildcard |
domain_cert | object | Certificate from domain A-record IP (if different from sender IP). Same structure as https_cert |
domain_cert_ip | string | IP that the domain resolved to (if different from sender) |
default_cert | object | Certificate without SNI (reveals shared hosting). Same structure as https_cert |
web_presence | object | Web presence analysis (same structure as top-level web_probe) |
ja4x_https | string | JA4X certificate OID fingerprint (HTTPS cert) |
ja4x_software | string | Certificate software identified via JA4X |
http2_server | string | Web server matched from HTTP/2 SETTINGS (nginx, Apache, Caddy, etc.) |
http2_settings | object | HTTP/2 SETTINGS values: HEADER_TABLE_SIZE, MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, etc. |
error | string | Web fingerprint error (if any) |
Server detection methods:
| Method | Confidence | What it checks |
| HTTP/2 SETTINGS | 0.90 | Server defaults for MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE |
| Error page | 0.85 | Default 404/403 page HTML patterns |
| Unique headers | 0.90 | x-envoy-*, X-LiteSpeed-*, X-Powered-By |
| ETag format | 0.80 | ETag structure differs per server |
| Method handling | 0.80 | Response codes for OPTIONS, DELETE, unknown methods |
| Header order | 0.70 | Order of Date/Server/Content-Type |
| Keep-Alive | 0.70 | timeout= max= format (Apache-unique) |
| URL handling | 0.75 | Case sensitivity, backslash (IIS) |
| Cookies | 0.80 | PHPSESSID, JSESSIONID, ASP.NET_SessionId |
| Endpoints | 0.85 | /server-status, /aspnet_client/ |
| WAF/CDN | 0.90 | Cloudflare, Akamai, Sucuri headers |
| HTTP/3 | 0.70 | Alt-Svc h3 advertisement |
Proxy status values:
| Status | Meaning |
open | Confirmed open proxy |
auth_required | Proxy requires authentication |
blocked | CONNECT method blocked |
confirmed | VPN service confirmed |
possible | Port open but not confirmed |
Cert comparison verdicts:
| Verdict | Meaning |
same_infra | Same cert on SMTP and HTTPS |
shared_infra | Different certs, same CA |
smtp_neglected | SMTP self-signed, HTTPS from CA |
self_signed_both | Both self-signed |
no_web | No HTTPS response |
split_infra | Different infrastructure |
Transport FP (transport_fp)
Transport-layer fingerprinting combining two analysis techniques to detect proxies and identify TLS libraries from application data record patterns.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=transport_fp"
Response (transport_fp object):
{
"transport_fp": {
"cross_layer_rtt": {
"tcp_rtt_us": 45230,
"smtp_rtt_us": 89450,
"starttls_rtt_us": 92340,
"tls_rtt_us": 95120,
"smtp_ratio": 1.97,
"starttls_ratio": 2.04,
"tls_ratio": 2.10,
"max_rtt_diff_us": 49890,
"proxy_detected": true,
"proxy_type": "socks5",
"confidence": 0.87,
"evidence": ["smtp_ratio_elevated", "consistent_gradient", "latency_pattern_socks5"]
},
"tls_record_analysis": {
"record_sizes": [100, 100, 100, 512, 512, 512],
"pattern": "uniform_then_large",
"tls_library": "OpenSSL",
"confidence": 0.92,
"avg_record_size": 341,
"first_record": 100,
"growth_detected": true,
"evidence": ["size_pattern_openssl", "record_distribution_match"]
}
}
}
Cross-Layer RTT fields:
| Field | Type | Description |
tcp_rtt_us | int | TCP handshake round-trip time in microseconds |
smtp_rtt_us | int | SMTP banner response RTT in microseconds |
starttls_rtt_us | int | STARTTLS response RTT in microseconds |
tls_rtt_us | int | TLS handshake completion RTT in microseconds |
smtp_ratio | float | SMTP RTT / TCP RTT ratio (detection threshold: >1.5 suggests proxy) |
starttls_ratio | float | STARTTLS RTT / TCP RTT ratio |
tls_ratio | float | TLS RTT / TCP RTT ratio |
max_rtt_diff_us | int | Maximum difference between any two RTT measurements (indicates proxy variance) |
proxy_detected | bool | Whether proxy detection threshold was exceeded |
proxy_type | string | Inferred proxy type if detected: socks5, socks4, http_tunnel, unknown |
confidence | float | Proxy detection confidence (0.0–1.0) |
evidence | string[] | Array of evidence signals supporting the detection (e.g. "smtp_ratio_elevated", "consistent_gradient", "latency_pattern_socks5") |
TLS Record Analysis fields:
| Field | Type | Description |
record_sizes | int[] | Observed TLS Application Data record sizes in bytes (first 6 records captured) |
pattern | string | Classified size pattern: uniform_then_large (OpenSSL), small_constant (Go), large_constant (BoringSSL), varied (rustls/other) |
tls_library | string | Identified TLS library from record pattern (OpenSSL, Go, BoringSSL, rustls, etc.) |
confidence | float | Pattern match confidence (0.0–1.0) |
avg_record_size | float | Average record size across captured records |
first_record | int | First record size (distinctive for some libraries) |
growth_detected | bool | Whether records grow over time (OpenSSL pattern) |
evidence | string[] | Array of pattern-matching evidence (e.g. "size_pattern_openssl", "record_distribution_match") |
Envelope (envelope)
SMTP session metadata captured when the email arrived.
| Field | Type | Description |
client_ip | string | Sender's IP address |
client_port | int | Sender's TCP port |
helo | string | HELO/EHLO hostname from SMTP session |
mail_from | string | MAIL FROM address |
rcpt_to | string[] | RCPT TO addresses |
TCP Fingerprint (tcp_fingerprint)
Passive TCP/IP fingerprint from the sender's SYN packet.
| Field | Type | Description |
ttl | int | Observed Time-To-Live |
initial_ttl | int | Estimated initial TTL (64/128/255) |
window_size | int | TCP window size |
mss | int | Maximum Segment Size |
mtu | int | Calculated MTU (MSS + 40) |
window_scale | int | Window scale factor |
calculated_window | int | Effective window (window_size << window_scale) |
options_layout | string | TCP options order (e.g. mss,sack,ts,nop,ws) |
df_bit | bool | Don't Fragment flag set |
sack_permitted | bool | SACK permitted option present |
timestamps | bool | TCP timestamps present |
ecn | bool | ECN flags set |
dscp | int | IP DSCP value |
ip_id | int | IP Identification field (0=Linux, incremental=Windows) |
p0f_signature | string | p0f-compatible fingerprint string |
os_guess | string | OS guess from TCP stack behavior |
ja4t_hash | string | JA4T TCP fingerprint hash |
| Network & Tunnel Detection |
hop_count | int | Number of network hops (initial_ttl - ttl) |
network_type | string | Detected network type: Direct Ethernet, WireGuard, OpenVPN, PPPoE, GRE, Mobile LTE/5G, etc. |
network_detail | string | Additional detail about network type |
tunnel_overhead | int | Tunnel overhead in bytes (1500 - MTU), 0 = no tunnel |
window_mss_ratio | string | Window/MSS ratio: mss×20 (Linux default), 65535 (max), or custom |
| Timestamp Analysis |
timestamp_mode | string | TCP timestamp mode: Randomized per-connection (Linux 4.10+), Not supported (Windows), Disabled, Present (mode unknown) |
tcp_timer_hz | int | Detected TCP timer frequency in Hz (250/1000 typical for Linux) |
tcp_timer_reliable | bool | Whether Hz detection is reliable (false for Linux with randomized timestamps) |
server_uptime | string | Estimated server uptime or "N/A (randomized)" for Linux |
uptime_source | string | How uptime was determined |
| Consistency Checks |
ttl_os_consistent | bool | Whether TTL matches expected OS (Linux=64, Windows=128) |
ttl_os_detail | string | TTL/OS consistency detail with explanation |
os_consistency | object | Cross-module OS comparison: consistent (bool), sources (array of {source, os}), detail (string) |
| Stack Hardening |
ip_id_behavior | string | IP ID classification: Zero with DF (RFC 6864), Non-zero (per-destination counter), etc. |
stack_hardening | object | Hardening assessment: rating (Default/Moderate/Hardened), score (0-3), indicators (array of {name, present, detail}) |
| Active TCP Probes |
isn_analysis | object | ISN analysis: gcd, isr (counter rate), sp (predictability 0-300+), responses (out of 6), assessment (Secure/Moderate/Predictable/Indeterminate) |
ip_id_sequence | string | IP ID sequence classification: Z (zero), I (incremental), BI (broken), RD (random), RI (random incremental) |
ecn_probe | object | ECN probe result: cc (Y/N/S/O = ECN support), quirks (R=reserved bits, U=urgent pointer), window, df, ttl |
tcp_probes | array | T2-T4 behavior probes: name, responded, df, window, flags, seq_test (Z/S/O), ack_test (Z/S/S+/O), quirks |
SMTP Dialog (smtp_dialog)
Recorded SMTP session between sender and our server.
| Field | Type | Description |
ehlo_hostname | string | Hostname from EHLO command |
pipelining_used | bool | Whether SMTP pipelining was used |
sends_quit | bool | Whether client sent QUIT before disconnecting |
re_ehlo_after_tls | bool | Whether client re-sent EHLO after STARTTLS |
ehlo_count | int | Total EHLO commands sent |
mail_from_params | string | MAIL FROM parameters (SIZE, BODY, etc.) |
entries | object[] | Dialog entries: is_from_client, command, full_line, timestamp, delta_ms |
raw_transcript | object[] | Raw transcript lines: dir (C/S), text, ts, delta_ms |
PTR / rDNS (ptr)
Reverse DNS lookup and forward-confirmed verification.
| Field | Type | Description |
client_ip | string | IP address looked up |
ptr_record | string | Primary PTR hostname |
all_ptr_records | string[] | All PTR records |
forward_confirmed | bool | PTR hostname resolves back to client_ip (FCrDNS) |
matches_helo | bool | PTR hostname matches EHLO hostname |
DNS Queries (dns_queries)
DNS queries that arrived to our authoritative server for the trace subdomain.
| Field | Type | Description |
timestamp | string | Query timestamp (RFC3339) |
source_ip | string | IP of the DNS resolver |
query_name | string | Queried domain name |
query_type | string | DNS query type (A, MX, TXT, etc.) |
response_code | string | Response code (NOERROR, NXDOMAIN, etc.) |
Email Headers (headers)
Key email headers extracted from the received message.
| Field | Type | Description |
subject | string | Email subject line |
from | string | From header |
to | string | To header |
date | string | Date header |
message_id | string | Message-ID header |
x_mailer | string | X-Mailer or User-Agent header (if present) |
content_type | string | Content-Type header |
Client Verdict (client_verdict)
Aggregated fingerprinting verdict from all signal sources.
| Field | Type | Description |
os | string | Detected operating system |
mta | string | Detected mail transfer agent |
tls_library | string | Detected TLS library |
mail_client | string | Detected mail client (if identifiable) |
server_uptime | string | Estimated server uptime |
confidence | float | Overall confidence (0.0–1.0) |
ja4t_hash | string | JA4T hash used for verdict |
signals | object[] | All signals: source, key, value, confidence, evidence |
Domain DNS Audit (domain_dns)
DNS records and configuration audit for the sender's domain.
| Field | Type | Description |
domain | string | Sender domain |
mx_state | string | Mail routing: explicit, implicit, none, dns_error |
mx_records | object[] | MX records: host, priority, ips |
a_records | string[] | A records for the domain |
aaaa_records | string[] | AAAA records |
ns_records | string[] | NS records |
soa | object | SOA record: primary_ns, admin_email, serial, refresh, retry, expire, min_ttl |
txt_records | string[] | TXT records (SPF, DKIM, DMARC, etc.) |
cname_record | string | CNAME record (if any) |
all_ptr_records | string[] | All PTR records for the domain's IPs |
ptr_matches_domain | bool | PTR matches the sender domain |
issues | object[] | DNS issues found: type, category (ns/mx/spf/ptr/soa), description |
NS Servers (ns_servers)
Nameserver software detection for the sender's domain.
| Field | Type | Description |
hostname | string | NS hostname |
ips | string[] | Resolved IP addresses |
software | string | Detected software (BIND, PowerDNS, etc.) |
version | string | Software version (if exposed) |
detection_method | string | Method used: chaos_query, behavioral, etc. |
WHOIS (whois)
Domain registration and age information.
| Field | Type | Description |
created_date | string | Domain creation date |
updated_date | string | Last updated date |
expiry_date | string | Expiry date |
domain_age | string | Human-readable age (e.g. "2 years") |
registrar | string | Domain registrar |
domain_status | string[] | EPP status codes |
warnings | string[] | Warnings (e.g. young domain) |
error | string | WHOIS lookup error (if any) |
GeoIP / ASN (geoip)
Geographic and network information for the sender's IP. Displayed in the report UI but the geoip JSON field may be absent in older reports.
| Field | Type | Description |
country | string | Country name |
country_code | string | ISO country code |
city | string | City |
asn | int | Autonomous System Number |
as_org | string | AS organization name |
is_hosting | bool | Whether the IP belongs to a hosting provider |
Mail Security Policies (mail_security)
Advanced mail security policy checks for the sender's domain.
| Field | Type | Description |
| mta_sts — MTA-STS policy |
mta_sts.has_policy | bool | Whether an MTA-STS policy exists |
mta_sts.mode | string | Policy mode: enforce, testing, none |
mta_sts.mx_hosts | string[] | Authorized MX hosts |
mta_sts.max_age | int | Policy max age in seconds |
| dane — DANE TLSA records |
dane.has_tlsa | bool | Whether TLSA records exist |
dane.dnssec_valid | bool | Whether DNSSEC validates |
dane.records | object[] | TLSA records: mx_host, usage, selector, matching_type, cert_hash |
| bimi — Brand Indicators |
bimi.has_record | bool | Whether a BIMI record exists |
bimi.logo_url | string | Logo URL (SVG) |
bimi.authority_url | string | VMC authority URL |
| tls_rpt — TLS Reporting |
tls_rpt.has_record | bool | Whether a TLS-RPT record exists |
tls_rpt.report_uri | string | Report URI (mailto: or https:) |
Port Scan (port_scan)
SMTP port scan results with STARTTLS, cert, and JARM per port.
| Field | Type | Description |
ports | object[] | Array of port scan results (see below) |
Each port object:
| Field | Type | Description |
port | int | Port number (25, 465, 587, 993, 4190, etc.) |
service | string | Service name (SMTP, SMTPS, IMAP, etc.) |
open | bool | Whether the port is open |
banner | string | Service banner |
starttls_supported | bool | Whether STARTTLS is supported |
tls_version | string | TLS version negotiated |
jarm_hash | string | JARM fingerprint for this port |
cert_subject | string | TLS certificate subject |
cert_issuer | string | Certificate issuer |
cert_fingerprint | string | Certificate SHA-256 fingerprint |
ehlo_caps | string[] | EHLO capabilities (SMTP ports) |
auth_methods | string[] | Advertised AUTH methods |
raw_transcript | string | Raw SMTP transcript (C:/S: format) |
DNS Leak Detection (dns_leak)
Detects if the sender's DNS resolver differs from their SMTP IP (ASN mismatch).
| Field | Type | Description |
detected | bool | Whether a DNS leak was detected |
severity | string | Severity: info, warning, critical |
smtp_client_ip | string | SMTP sender IP |
description | string | Human-readable description |
resolvers | object[] | DNS resolvers seen: ip, is_public_dns, public_dns_name, classification |
Mail Route & Timeline (received_analysis)
Parsed Received header chain with timing analysis and anomaly detection.
| Field | Type | Description |
total_hops | int | Number of mail hops |
total_time | string | Total transit time (human-readable) |
total_time_ms | int | Total transit time in milliseconds |
hops | object[] | Each hop (see below) |
anomalies | object[] | Anomalies: type, severity, hop_index, detail |
ip_match | object | received_ip, client_ip, match (bool) |
Each hop:
| Field | Type | Description |
index | int | Hop number (0-based) |
from_host | string | Sending host |
from_ip | string | Sending IP |
by_host | string | Receiving host |
protocol | string | Protocol (SMTP, ESMTP, ESMTPS, etc.) |
tls_used | bool | Whether TLS was used for this hop |
timestamp | string | Timestamp from Received header |
delta_ms | int | Time since previous hop (ms) |
delta_class | string | fast (≤5s), medium (5–30s), slow (>30s) |
Anomaly types: tls_downgrade, clock_skew, private_ip, hostname_mismatch, large_delay, ip_mismatch.
Network Route Trace (route_trace)
The route_trace field contains traceroute results from our server to the sender's IP, enriched with GeoIP, ASN, node classification, and cross-referenced with Received headers and TCP TTL analysis.
Example:
curl "https://mailcheck.biz/api/report/xyzabc-abcdefgh?fields=route_trace"
Response (route_trace object):
{
"route_trace": {
"hops": [
{
"number": 1,
"nodes": [
{
"ip": "10.0.0.1",
"hostname": "gw.isp.net",
"avg_rtt_ms": 1.2,
"min_rtt_ms": 0.8,
"max_rtt_ms": 1.5,
"country": "FR",
"city": "Paris",
"asn": 16276,
"as_name": "OVH",
"lat": 48.8566,
"lon": 2.3522,
"node_type": "isp_router",
"provider": "OVH"
}
],
"loss_pct": 0,
"timeout": false
}
],
"target_ip": "1.2.3.4",
"target_reached": true,
"total_hops": 12,
"total_time_ms": 28.5,
"duration": "6.2s",
"correlation": {
"estimated_hops": 12,
"traceroute_hops": 12,
"hops_delta": 0,
"ttl_anomaly": "none",
"matched_hops": [
{
"received_hop_index": 0,
"traceroute_hop_num": 12,
"ip": "1.2.3.4",
"hostname": "mail.example.com"
}
],
"findings": [
{
"type": "ttl_consistent",
"severity": "info",
"detail": "TTL estimation (12 hops) matches traceroute (12 hops)"
}
],
"asn_overlap": "1 shared (Google)",
"transit_countries": "FR → NL → DE → US"
}
}
}
Top-level fields:
| Field | Type | Description |
hops | array | Traceroute hops from our server to sender IP |
target_ip | string | Sender's IP address (traceroute destination) |
target_reached | bool | Whether traceroute reached the target |
total_hops | int | Number of hops to reach target |
total_time_ms | float | RTT to final responding hop (ms) |
duration | string | Wall-clock time of traceroute run |
correlation | object | Cross-reference with Received headers and TCP TTL |
Hop fields (hops[]):
| Field | Type | Description |
number | int | Hop number (1-based) |
nodes | array | Responding nodes (multiple = ECMP load balancing) |
loss_pct | float | Packet loss percentage (0-100) |
timeout | bool | All probes timed out at this hop |
Node fields (hops[].nodes[]):
| Field | Type | Description |
ip | string | IP address of responding node |
hostname | string | Reverse DNS hostname |
avg_rtt_ms | float | Average round-trip time (ms) |
min_rtt_ms | float | Minimum RTT (ms) |
max_rtt_ms | float | Maximum RTT (ms) |
country | string | Country code (GeoIP) |
city | string | City (GeoIP) |
asn | int | Autonomous System Number |
as_name | string | AS organization name |
node_type | string | Classification: isp_router, ixp, cdn, cloud, enterprise, endpoint, unknown |
provider | string | Detected provider name (e.g. Cloudflare, AWS, Cogent) |
Correlation fields (correlation):
| Field | Type | Description |
estimated_hops | int | Estimated hops from TCP SYN TTL (initial_ttl − observed_ttl) |
traceroute_hops | int | Actual hops from traceroute |
hops_delta | int | Absolute difference between estimated and actual |
ttl_anomaly | string | TTL analysis result: none, nat_likely, vpn_likely, proxy_likely, unknown, not_available |
matched_hops | array | IPs found in both SMTP Received chain and traceroute |
findings | array | Correlation findings: type, severity, detail |
asn_overlap | string | Shared ASNs between SMTP and network routes |
transit_countries | string | Countries traversed in order (e.g. "FR → NL → DE → US") |
Finding types:
direct_route, ttl_consistent, ttl_mismatch_minor, ttl_mismatch_major, vpn_detected, proxy_detected, ip_match_found, ip_match_none, asymmetric_route, relay_chain, internal_relay, high_loss_path, country_transit.
Node type classifications:
| Type | Description |
isp_router | ISP or backbone router |
ixp | Internet Exchange Point |
cdn | CDN edge node |
cloud | Cloud provider infrastructure |
enterprise | Corporate gateway |
endpoint | Destination server |
unknown | Unclassified node |
Web Presence (web_probe)
HTTP/HTTPS probing of the sender's domain with deep content analysis.
Basic fields:
| Field | Type | Description |
has_website | bool | Whether the domain has a working website |
url | string | Final URL after redirects |
status_code | int | HTTP status code |
server_header | string | Server response header |
x_powered_by | string | X-Powered-By header |
cdn | string | Detected CDN (Cloudflare, Akamai, etc.) |
tls_version | string | TLS version of HTTPS connection |
cert_issuer | string | HTTPS certificate issuer |
cert_expiry | string | Certificate expiry date |
has_hsts | bool | HSTS header present |
cms | string | Detected CMS (WordPress, etc.) |
website_verdict | string | Overall website assessment |
error | string | Probe error (if any) |
robots.txt analysis:
| Field | Type | Description |
has_robots_txt | bool | Whether robots.txt exists |
robots_valid | bool | Whether robots.txt parses correctly |
robots_rule_count | int | Number of Disallow rules |
robots_sitemap_urls | string[] | Sitemap URLs referenced |
robots_crawl_delay | int | Crawl-Delay value (seconds) |
robots_hosting_panel | string | Hosting panel detected (cPanel, Plesk, etc.) |
security.txt analysis:
| Field | Type | Description |
has_security_txt | bool | Whether security.txt exists |
security_txt_valid | bool | Whether it complies with RFC 9116 |
security_txt_contact | string | Contact URI |
security_txt_expires | string | Expiration date |
security_txt_expired | bool | Whether it has expired |
security_txt_signed | bool | PGP signed |
security_txt_verdict | string | Assessment verdict |
security_txt_fields | object | All parsed fields (Encryption, Policy, Acknowledgments, etc.) |
sitemap.xml analysis:
| Field | Type | Description |
has_sitemap | bool | Whether sitemap.xml exists |
sitemap_valid | bool | Whether sitemap parses correctly |
sitemap_url_count | int | Number of URLs in sitemap |
sitemap_newest_mod | string | Newest lastmod date |
sitemap_oldest_mod | string | Oldest lastmod date |
sitemap_is_index | bool | Whether it's a sitemap index |
Favicon analysis:
| Field | Type | Description |
favicon_valid | bool | Whether favicon was found |
favicon_hash | string | SHA-256 hash of favicon |
favicon_format | string | Image format (ico, png, svg) |
favicon_size | int | File size in bytes |
favicon_default | bool | Whether it matches a known default favicon |
HTML page analysis:
| Field | Type | Description |
page_title | string | HTML <title> content |
page_generator | string | Generator meta tag (WordPress, Joomla, etc.) |
page_language | string | Page language (html lang attribute) |
page_link_count | int | Number of links on page |
page_css_count | int | Number of CSS files |
page_js_count | int | Number of JS files |
page_has_forms | bool | Whether page contains forms |
page_size | int | Page size in bytes |
page_verdict | string | Page quality assessment |
Network Anomalies (network_anomalies)
TCP/IP anomalies detected from passive fingerprinting (VPN/proxy/tunnel indicators).
| Field | Type | Description |
type | string | Anomaly type (ttl_mismatch, mss_anomaly, etc.) |
detail | string | Human-readable description |
confidence | float | Detection confidence (0.0–1.0) |
evidence | string | Supporting evidence |
POST /api/compare?parentID={traceID}
Create a comparison child trace for OPSEC server comparison. Returns a new email address to send from a second server. The parent report must exist (on disk or in memory). Rate limited separately from /api/trace.
Query Parameter:
| Parameter | Type | Description |
parentID | string | Trace ID of the first (parent) report. Required. |
Example:
curl -X POST "https://mailcheck.biz/api/compare?parentID=xyzabc-abcdefgh"
Response:
{
"email": "newlocal@newsub.mailcheck.biz",
"traceID": "newsub-newlocal"
}
Errors:
| Code | Body | Meaning |
| 400 | {"error":"parentID required"} | Missing parentID query parameter |
| 400 | {"error":"parent report not found"} | Parent report does not exist |
| 400 | {"error":"comparison already in progress"} | A child trace already exists for this parent |
| 429 | {"error":"rate limit exceeded"} | Rate limit (1 req / 30s per IP) |
GET /api/compare-status/{parentID}
Poll the status of a comparison trace. Returns the child trace ID and its processing state.
Example:
curl https://mailcheck.biz/api/compare-status/xyzabc-abcdefgh
Response:
{
"status": "ready",
"childTraceID": "newsub-newlocal"
}
| Status | Meaning |
pending | Waiting for email from second server |
analyzing | Email received, analysis in progress |
ready | Child report ready — fetch via GET /api/report/{childTraceID} |
notfound | No comparison trace for this parent |
GET /api/eml/{id}
Returns the raw .eml email file as text/plain. Contains full email headers and body as received by the server.
Example:
curl https://mailcheck.biz/api/eml/xyzabc-abcdefgh
Response:
Raw email content (headers + body) as text/plain; charset=utf-8. Returns 404 if email file not found.
GET /health
Server health check.
Example:
curl https://mailcheck.biz/health
Response:
{
"status": "ok",
"uptime": "2d 5h 30m",
"version": "go1.23.6"
}