New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@domaincanary/dmarc-rua

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@domaincanary/dmarc-rua

DMARC aggregate (RUA) report parser: XML, .xml.gz, and .zip reports into typed records, plus MIME attachment extraction for report email. Runs on Node.js, Deno, Bun, and Cloudflare Workers.

latest
Source
npmnpm
Version
0.4.0
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
 
Created
Source

dmarc-rua

Parse DMARC aggregate (RUA) reports in TypeScript. @domaincanary/dmarc-rua is a DMARC RUA parser library for JavaScript and TypeScript that turns the XML, gzipped XML, and zip report files mailbox providers such as Google, Microsoft, and Yahoo send into typed records. It runs on Deno, Node.js, Bun, and Cloudflare Workers.

The parser tolerates malformed records and applies shared decompression and record budgets, so hostile or oversized input cannot exhaust memory or CPU. The @domaincanary/dmarc-rua/email subpath extracts candidate report attachments from the small MIME subset used by DMARC report email, so raw RFC 5322 bytes can go in one end and parsed reports come out the other. It is not a general email parser.

If you want the reports read for you, DomainCanary is the hosted DMARC monitoring service built on this parser. Your first domain is free.

  • Parses DMARC aggregate report XML into typed ParsedReport and ParsedRecord objects
  • Accepts raw XML, .xml.gz, and .zip payloads, detecting the format from magic bytes
  • Surfaces SPF and DKIM authentication results, adkim and aspf alignment modes, and policy override reasons
  • Bounded decompression and capped record counts guard against zip bombs
  • Runs in Cloudflare Email Workers: no Node-only dependencies apart from node:net isIP

What is a DMARC aggregate report?

When a domain publishes a DMARC record with a rua= tag, receiving mail servers send periodic XML reports describing the mail they saw claiming to come from that domain: source IPs, message counts, SPF and DKIM results, and the policy they applied. These RUA reports arrive as email attachments, usually a gzipped or zipped XML file. This library parses those files into TypeScript objects, and the email subpath pulls them out of the message first.

Install

With Deno:

deno add jsr:@domaincanary/dmarc-rua

For a Node.js or Bun project, straight from npm:

npm install @domaincanary/dmarc-rua

Either way, the bare @domaincanary/dmarc-rua imports below work unchanged on every runtime. The npm package is built from the same source, ships ESM with type declarations, and has two runtime dependencies, both with zero transitive dependencies and no install scripts.

Parse a DMARC report file

Pass the bytes of an .xml, .xml.gz, or .zip file to parsePayload. A zip can contain more than one report, so the result is always an array.

import { type ParsedReport, parsePayload } from "@domaincanary/dmarc-rua";

const bytes = await Deno.readFile("google.com!example.com!report.xml.gz");
const reports: ParsedReport[] = await parsePayload(bytes, "report.xml.gz");

for (const report of reports) {
  console.log(report.orgName, report.reportId, report.records.length);
}

Receive DMARC reports with a Cloudflare Email Worker

Cloudflare Email Workers do not provide Node mail libraries. The email subpath works directly on message.raw, and the package has no Node-only dependencies apart from node:net isIP. Workers support that API when the nodejs_compat compatibility flag is enabled.

import { ParseBudget, type ParsedReport, ParseError, parsePayload } from "@domaincanary/dmarc-rua";
import { extractRecipient, parseEmailAttachments } from "@domaincanary/dmarc-rua/email";

export default {
  async email(message: ForwardableEmailMessage): Promise<void> {
    const raw = new Uint8Array(await new Response(message.raw).arrayBuffer());
    const recipient = extractRecipient(raw);
    const attachments = parseEmailAttachments(raw);
    const budget = new ParseBudget();
    const reports: ParsedReport[] = [];

    for (const attachment of attachments) {
      try {
        reports.push(
          ...await parsePayload(attachment.bytes, attachment.filename, budget),
        );
      } catch (e) {
        // A candidate that is not a report, such as a logo image, throws ParseError. Skip it.
        if (!(e instanceof ParseError)) throw e;
      }
    }

    console.log({ recipient, reports });
  },
};

Add the compatibility flag to wrangler.jsonc:

{
  "compatibility_flags": ["nodejs_compat"]
}

Parse reports yourself, or have them read for you

This library gives you parsed records. You still have to receive the mail, store the records, work out which service sits behind each IP address, and tell someone when a sender starts failing. DomainCanary is the hosted service we built on this parser, and it does those parts.

dmarc-ruaDomainCanary
Who runs itYou, in your own Worker, server or scriptWe do
Receiving reportsYour mailbox or Email WorkerA reporting address for each domain
What you getTyped records from each reportEvery sending source matched to the service behind it, with pass and fail trends
AlertsThe ones you writeA Monday digest, and an email when your DMARC record changes
HistoryWhatever you store90 days of report detail on the free plan, 12 months on the Starter plan
PriceFree, MITFree for your first domain with no card, then from $19 a month for 3 domains

Start free with one domain.

Hardening against hostile input

DMARC report addresses are published in public DNS, so anything can mail them anything. Gzip and deflate data are decompressed as bounded streams. Every expanded byte is charged to a shared budget before it is retained, including nested gzip members inside zip archives. Zip entry counts and parsed record counts are capped to constrain CPU and memory use.

Malformed records are skipped while usable sibling records are returned. skippedRecords reports how many record elements were rejected or left unparsed, and truncatedFields reports bounded metadata fields. These honesty counters let callers distinguish a complete report from a partially recovered one.

API

parsePayload(bytes, filename?, budget?)

Parses raw XML, gzip, or zip bytes and returns Promise<ParsedReport[]>. Format detection uses magic bytes first and the optional filename second. XML is decoded as UTF-8 unless the document declares another encoding or opens with a UTF-16 byte order mark. It throws ParseError when no usable report can be extracted, which includes every attachment that is not a report, so catch it per attachment when parsing email.

ParseBudget

Tracks the shared decompressed-byte and record budgets for one email or ingest operation. The defaults are 64 MiB and 50,000 records. Pass custom limits to new ParseBudget(decompressedBytes, records), and reuse the same instance across every attachment from one message. After parsing, skippedPayloads, decompressionExceeded and recordLimitReached on the budget say whether anything was left unparsed, such as trailing zip members. A report whose XML has far more elements than the remaining record budget could account for is rejected before it is parsed, because the element tree costs much more memory than the bytes it came from.

parseEmailAttachments(raw)

Returns EmailAttachment[] from raw RFC 5322 bytes. It handles the MIME forms needed for DMARC reports, including nested multipart bodies, reports forwarded as message/rfc822 attachments, base64, quoted-printable, RFC 2231 filenames, and bare XML or compressed bodies. Malformed input returns whatever can be recovered and does not throw.

extractRecipient(raw)

Returns the lowercased envelope recipient when available, preferring X-Original-To, then Delivered-To, then To. It returns null when no valid address can be recovered.

Types

The root export provides ParsedReport, ParsedRecord, DkimAuthResult, PolicyReason, and ParseError. ParsedReport includes report metadata, published policy (including the domain the report is about and the adkim and aspf alignment modes), parsed records, and partial-recovery counters. ParsedRecord contains the source IP, message count, evaluated DMARC results, identifiers (domains are lowercased), authentication results (the mfrom SPF result when a reporter lists several), the complete ordered DKIM auth results in dkimAuthResults (capped at MAX_DKIM_AUTH_RESULTS_PER_RECORD), and policy override reasons in reasons (capped at MAX_POLICY_REASONS_PER_RECORD). Entries dropped by either cap are counted in truncatedFields. The email subpath exports EmailAttachment, whose fields are bytes and an optional filename.

This library powers DMARC monitoring at DomainCanary, which alerts on authentication failures and DMARC record changes.

Keywords

dmarc

FAQs

Package last updated on 21 Sep 2026

Related posts