Files
skills/docs/communication/emails/email-processing/src/dmarc-checker/dmarc-checker.md
2026-05-29 08:20:56 +01:00

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

  • 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

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, returns null on 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.