Validation results cached with TTL
Why it matters
DNS MX lookups and external validation API calls have measurable latency — typically 50–500ms per call — and most providers impose rate limits (e.g., 100 calls/minute on free tiers). Without caching, every signup and every re-validation check triggers a live network round-trip for the same domain. At 1,000 signups/hour from domains like gmail.com or company.com, redundant lookups waste budget, add latency to the user-facing signup flow, and risk triggering rate-limit errors that silently degrade validation coverage when the lookup fails open.
Severity rationale
Low because missing caching adds latency and cost rather than corrupting data, but at scale the rate-limit failures can silently disable validation for popular domains.
Remediation
Cache MX and disposable-check results at the domain level using Redis with a TTL of at least 1 hour:
import { redis } from '@/lib/redis'
async function getCachedMxResult(domain: string): Promise<boolean | null> {
const cached = await redis.get(`mx:${domain}`)
return cached === null ? null : cached === '1'
}
async function setCachedMxResult(domain: string, valid: boolean): Promise<void> {
await redis.setex(`mx:${domain}`, 6 * 60 * 60, valid ? '1' : '0')
}
// In the ingest handler:
const domain = email.split('@')[1]
let valid = await getCachedMxResult(domain)
if (valid === null) {
valid = await hasValidMx(email)
await setCachedMxResult(domain, valid)
}
If Redis is not available, a DB table with an expires_at column works as a fallback. A TTL of 1–24 hours is standard — MX records change infrequently.
Detection
-
ID:
validation-cache -
Severity:
low -
What to look for: Count all validation-related external calls (DNS MX lookup, API validation, disposable check) and for each, check whether results are cached. Performing a live DNS lookup or an external API call for every email on every operation is slow and unnecessary. Look for a cache layer (Redis, in-memory Map, or a
domain_validationstable with anexpires_atcolumn). -
Pass criteria: Domain-level validation results (MX, catch-all status, disposable status) are cached with a TTL of at least 1 hour. Repeated checks for the same domain within the TTL do not trigger a new lookup.
-
Fail criteria: Every email validation triggers a live DNS lookup or API call with no caching, or cache TTL is less than 5 minutes.
-
Skip (N/A) when: Validation is performed exclusively via a third-party bulk validation service during import (not in the real-time ingest path).
-
Detail on fail: Example:
"Every signup triggers a live DNS lookup with no result caching — high latency and rate limit risk" -
Remediation: Cache domain validation at a separate domain level:
import { redis } from '@/lib/redis' async function getCachedMxResult(domain: string): Promise<boolean | null> { const cached = await redis.get(`mx:${domain}`) return cached === null ? null : cached === '1' } async function setCachedMxResult(domain: string, valid: boolean): Promise<void> { // Cache for 6 hours await redis.setex(`mx:${domain}`, 6 * 60 * 60, valid ? '1' : '0') }
External references
- iso-25010:2011 · performance-efficiency — Performance Efficiency
Taxons
History
- 2026-04-18·v1.0.0·Initial import from data-quality-list-hygiene·automated