import {FAQSection, Footer, Navbar, SectionHeader} from '../../components'; import {motion} from 'framer-motion'; import {DASHBOARD_URI} from '../../lib/constants'; import React, {useState} from 'react'; import {NextSeo} from 'next-seo'; import {AlertTriangle, ArrowRight, CheckCircle, Shield, XCircle} from 'lucide-react'; import {Button, Input} from '@plunk/ui'; import {Bricolage_Grotesque, Hanken_Grotesk, JetBrains_Mono} from 'next/font/google'; import Link from 'next/link'; import type {FAQ} from '../../components/FAQSection'; const display = Bricolage_Grotesque({ subsets: ['latin'], variable: '--font-display', display: 'swap', weight: ['400', '500', '600', '700', '800'], }); const body = Hanken_Grotesk({ subsets: ['latin'], variable: '--font-body', display: 'swap', weight: ['400', '500', '600', '700'], }); const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--font-mono', display: 'swap', weight: ['400', '500'], }); interface SpfMechanism { qualifier: string; type: string; value: string; } interface SpfIssue { type: 'error' | 'warning' | 'pass'; label: string; detail: string; } interface ParsedSpf { mechanisms: SpfMechanism[]; allMechanism: string | null; issues: SpfIssue[]; lookupCount: number; grade: 'pass' | 'warning' | 'fail'; } const DNS_LOOKUP_MECHS = new Set(['include', 'a', 'mx', 'ptr', 'exists', 'redirect']); function parseSpfRecord(record: string): ParsedSpf { const parts = record.split(/\s+/); const mechanisms: SpfMechanism[] = []; let allMechanism: string | null = null; const issues: SpfIssue[] = []; let lookupCount = 0; for (const part of parts.slice(1)) { const lower = part.toLowerCase(); if (lower === '+all' || lower === '-all' || lower === '~all' || lower === '?all' || lower === 'all') { allMechanism = lower === 'all' ? '+all' : lower; continue; } let qualifier = '+'; let mech = part; const firstChar = part[0] ?? ''; if (['+', '-', '~', '?'].includes(firstChar)) { qualifier = firstChar; mech = part.slice(1); } const colonIdx = mech.indexOf(':'); const slashIdx = mech.indexOf('/'); const endIdx = colonIdx > -1 ? colonIdx : slashIdx > -1 ? slashIdx : mech.length; const type = mech.slice(0, endIdx).toLowerCase(); const value = colonIdx > -1 ? mech.slice(colonIdx + 1) : ''; if (DNS_LOOKUP_MECHS.has(type)) lookupCount++; mechanisms.push({qualifier, type, value}); } // Analyze if (!allMechanism) { issues.push({type: 'warning', label: 'No "all" mechanism', detail: 'SPF records should end with -all, ~all, or ?all to define default behaviour for unlisted senders.'}); } else if (allMechanism === '+all') { issues.push({type: 'error', label: '+all allows any server to send', detail: '+all means any mail server in the world can send email claiming to be from your domain, completely defeating SPF protection.'}); } else if (allMechanism === '?all') { issues.push({type: 'warning', label: '?all is too permissive', detail: '?all (neutral) gives no protection. Consider upgrading to ~all (softfail) or -all (hard fail).'}); } else if (allMechanism === '~all') { issues.push({type: 'warning', label: '~all softfail is acceptable but not optimal', detail: 'Softfail marks unauthorised senders as suspicious but still delivers them. Prefer -all for maximum protection once your legitimate senders are configured.'}); } else if (allMechanism === '-all') { issues.push({type: 'pass', label: '-all hard fail is the strongest policy', detail: 'Mail servers are instructed to reject email from any server not listed in your SPF record.'}); } if (lookupCount > 8) { issues.push({type: 'error', label: `DNS lookup limit exceeded (${lookupCount}/10)`, detail: `SPF is limited to 10 DNS lookups. Exceeding this causes a PermError, making SPF fail permanently. Consolidate includes or use IP ranges.`}); } else if (lookupCount > 6) { issues.push({type: 'warning', label: `Approaching DNS lookup limit (${lookupCount}/10)`, detail: 'Adding more senders could push you over the 10 lookup limit. Monitor and consolidate where possible.'}); } else { issues.push({type: 'pass', label: `DNS lookups within limit (${lookupCount}/10)`, detail: 'Your SPF record uses an acceptable number of DNS lookups.'}); } const ptrMechs = mechanisms.filter(m => m.type === 'ptr'); if (ptrMechs.length > 0) { issues.push({type: 'warning', label: 'ptr mechanism is deprecated', detail: 'The ptr mechanism is slow and unreliable. RFC 7208 recommends avoiding it. Use ip4/ip6 or include instead.'}); } const grade = issues.some(i => i.type === 'error') ? 'fail' : issues.some(i => i.type === 'warning') ? 'warning' : 'pass'; return {mechanisms, allMechanism, issues, lookupCount, grade}; } function qualifierLabel(q: string) { if (q === '+') return {label: 'PASS', cls: 'bg-green-50 text-green-700 border-green-200'}; if (q === '-') return {label: 'FAIL', cls: 'bg-red-50 text-red-700 border-red-200'}; if (q === '~') return {label: 'SOFTFAIL', cls: 'bg-amber-50 text-amber-700 border-amber-200'}; return {label: 'NEUTRAL', cls: 'bg-neutral-100 text-neutral-600 border-neutral-300'}; } function GradeBadge({grade}: {grade: 'pass' | 'warning' | 'fail'}) { const map = { pass: {cls: 'bg-green-50 border-green-200 text-green-700', label: 'Valid', sub: 'SPF is properly configured'}, warning: {cls: 'bg-amber-50 border-amber-200 text-amber-700', label: 'Needs attention', sub: 'SPF has configuration issues'}, fail: {cls: 'bg-red-50 border-red-200 text-red-700', label: 'Action required', sub: 'SPF has critical errors'}, }; const {cls, label, sub} = map[grade]; return (
{label} {sub}
); } const faqs: FAQ[] = [ { question: 'What is an SPF record?', answer: 'An SPF (Sender Policy Framework) record is a DNS TXT record that lists the mail servers authorised to send email on behalf of your domain. Receiving mail servers check this record to verify that incoming email claiming to be from your domain was sent by an authorised server. Without SPF, anyone can spoof your domain in the From address.', }, { question: 'What does -all vs ~all mean?', answer: '-all (hard fail) instructs receiving servers to reject any email not matching your SPF record. ~all (softfail) marks non-matching emails as suspicious but still delivers them. For production domains, -all is recommended once all your legitimate sending sources are added.', }, { question: 'Why is there a 10 DNS lookup limit?', answer: 'RFC 7208 limits SPF to 10 DNS lookups to prevent denial-of-service attacks and excessive DNS load. Each include, a, mx, ptr, and exists mechanism counts as one lookup. Exceeding 10 lookups causes a PermError, which effectively makes SPF fail for your domain.', }, { question: 'Can I have multiple SPF records?', answer: 'No. Having more than one SPF (v=spf1) TXT record on your domain causes a PermError and breaks SPF authentication. If you need to authorise multiple senders, combine everything into a single SPF record using multiple mechanisms.', }, { question: 'Does SPF alone protect against spoofing?', answer: 'SPF alone is not enough. SPF only validates the envelope sender (the "Return-Path" address), not the visible "From" header. DMARC is required to connect SPF (and DKIM) validation to the From header and actually prevent spoofing of your visible sender address.', }, ]; interface DnsAnswer { data: string; } interface SpfLookupResult { domain: string; found: boolean; multiple: boolean; records: string[]; error?: string; } function cleanTxt(raw: string): string { return raw.replace(/^"|"$/g, '').replace(/"\s*"/g, ''); } async function lookupSpf(domain: string): Promise { const clean = domain.trim().toLowerCase().replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/^www\./, ''); try { const res = await fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(clean)}&type=TXT`, { headers: {Accept: 'application/dns-json'}, }); if (!res.ok) return {domain: clean, found: false, multiple: false, records: [], error: 'DNS lookup failed'}; const data = await res.json() as {Answer?: DnsAnswer[]}; const records = (data.Answer ?? []).map((a: DnsAnswer) => cleanTxt(a.data)).filter(r => r.startsWith('v=spf1')); return {domain: clean, found: records.length > 0, multiple: records.length > 1, records}; } catch { return {domain: clean, found: false, multiple: false, records: [], error: 'DNS lookup failed'}; } } export default function SpfCheckerPage() { const [domain, setDomain] = useState(''); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [parsed, setParsed] = useState(null); const handleCheck = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setResult(null); setParsed(null); try { const data = await lookupSpf(domain); setResult(data); if (data.found && data.records[0]) { setParsed(parseSpfRecord(data.records[0])); } } finally { setLoading(false); } }; return ( <>
{/* ========== HERO ========== */}
§ T-04  —  Tool ← All tools

SPF record
checker

Look up and validate your domain's SPF record. Get a full breakdown of your sending policy and catch misconfigurations before they hurt deliverability.

{/* ========== TOOL ========== */}
SPF record lookup
setDomain(e.target.value)} placeholder="example.com" required className={'w-full'} />

Enter the domain without http:// or www.

{result && ( {!result.found ? (

No SPF record found

{result.error ? 'DNS lookup failed. Please check the domain and try again.' : `No SPF record was found for ${result.domain}. Without SPF, anyone can send email impersonating your domain, and legitimate emails are more likely to land in spam.`}

Add a TXT record to {result.domain}:

v=spf1 include:your-email-provider.com -all
) : ( <> {result.multiple && (

Multiple SPF records detected

Having more than one SPF record causes a PermError, breaking SPF for your domain. Merge all mechanisms into a single v=spf1 record.

)} {/* Raw record */}

Raw record — {result.domain}

{result.records[0]}
{parsed && ( <> {/* Grade */}
{/* Mechanisms */}

Sending mechanisms

{parsed.mechanisms.map((m, i) => { const {label, cls} = qualifierLabel(m.qualifier); return (
{label} {m.type} {m.value && ( {m.value} )}
); })} {parsed.allMechanism && (
{qualifierLabel(parsed.allMechanism[0] ?? '+').label} all — default for all other senders
)}
{/* Issues */}

Analysis & recommendations

    {parsed.issues.map((issue, i) => (
  • {issue.type === 'pass' ? ( ) : issue.type === 'warning' ? ( ) : ( )}

    {issue.label}

    {issue.detail}

  • ))}
)} )}
)}
{/* ========== EDUCATION ========== */}
{[ { title: 'Authorise senders', body: 'SPF lets you publish a list of mail servers allowed to send on your behalf. Any server not on the list fails SPF validation.', }, { title: 'Prevent spoofing', body: 'Without SPF, anyone can claim to send email from your domain. SPF makes it possible for receiving servers to detect and reject spoofed messages.', }, { title: '10 lookup limit', body: 'SPF allows at most 10 DNS lookups per evaluation. Exceeding this causes a PermError, making SPF permanently fail. Monitor your lookup count carefully.', }, { title: 'SPF is not enough alone', body: 'SPF validates the envelope sender, not the visible From header. You need DMARC to tie SPF (and DKIM) results to the From header and actually block spoofed email.', }, { title: 'One record only', body: 'A domain must have exactly one SPF TXT record. Multiple v=spf1 records cause a PermError. Merge all sending sources into a single record.', }, { title: 'Hard fail vs softfail', body: '-all (hard fail) instructs servers to reject non-matching mail. ~all (softfail) marks it as suspicious. Use -all in production once all senders are listed.', }, ].map((item, i) => (

{item.title}

{item.body}

))}
{/* ========== CTA ========== */}
SPF, DKIM, and DMARC — handled.

Plunk guides you through domain authentication setup and monitors your sending reputation. Start free, no credit card required.

Start with Plunk What is SPF?