2.8 KiB
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.<from-domain>
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
parseDmarcRecordreturns a strongly-typedDmarcRecordwith all standard tags (p,sp,adkim,aspf,pct,rua,ruf).checkDmarcaccepts 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_totalattributed byresultfor every outcome.
Usage: Basic
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
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, returnsnullon invalid input.checkDmarc(fromDomain, spf, dkim, resolver)— async orchestrator.
Implementation Notes
The DMARC TXT record lookup uses the bare _dmarc.<domain> 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.