# dmarc-checker _(no submodules)_ ## Purpose Implements DMARC (RFC 7489) policy evaluation on top of an upstream SPF result and DKIM verification. Looks up the `_dmarc.` TXT record through an injectable resolver, parses its policy and alignment tags, then determines whether at least one of SPF or DKIM passes **and** aligns with the From header's organisational domain. ## Features - `parseDmarcRecord` returns a strongly-typed `DmarcRecord` with all standard tags (`p`, `sp`, `adkim`, `aspf`, `pct`, `rua`, `ruf`). - `checkDmarc` accepts pre-computed SPF and DKIM outcomes so callers can compose authentication pipelines without re-running expensive DNS or crypto work. - Both alignment modes — strict (exact domain match) and relaxed (organisational-domain match) — are supported. - DNS resolver is async and injectable; the package itself never calls into `dns.*` directly. - Records `email_processing.dmarc.result_total` attributed by `result` for every outcome. ## Usage: Basic ```ts import { checkDmarc } from "@suaveplan/email-processing/dmarc"; const out = await checkDmarc(fromDomain, spfResult, dkimResult, async (d) => { const recs = await dns.resolveTxt(d); return recs.map((p) => p.join("")); }); if (out.result === "fail" && out.policy === "reject") { rejectMessage(); } ``` ## Usage: Advanced ```ts import { checkDmarc, parseDmarcRecord } from "@suaveplan/email-processing/dmarc"; const recs = await dnsResolver(`_dmarc.${domain}`); const dmarcText = recs.find((r) => r.startsWith("v=DMARC1")) ?? ""; const parsed = parseDmarcRecord(dmarcText); const aggregateUri = parsed?.reportUri; if (aggregateUri) { await scheduleReport({ domain, uri: aggregateUri }); } const result = await checkDmarc(domain, spf, dkim, dnsResolver); metrics.recordDmarc(domain, result.result, result.policy); ``` ## API Reference - `DmarcPolicy` — `"none" | "quarantine" | "reject"`. - `DmarcAlignment` — `"strict" | "relaxed"`. - `DmarcResult` — `"pass" | "fail" | "none"`. - `DmarcRecord` — full parsed record. - `DmarcCheckResult` — `{ result, policy?, spfAligned, dkimAligned, record?, error? }`. - `parseDmarcRecord(txt)` — strict parser, returns `null` on invalid input. - `checkDmarc(fromDomain, spf, dkim, resolver)` — async orchestrator. ## Implementation Notes The DMARC TXT record lookup uses the bare `_dmarc.` form per RFC 7489 §6.6.3 (organisational-domain fallback is left to a higher layer — callers who want PSL-aware fallback should chain a second `checkDmarc` against the parent domain when the first returns `result: "none"` with `error: "No DMARC record found"`). The `reportDmarc` helper centralises OTel emission so every return path records the outcome exactly once.