npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@domaincanary/dmarc-rua

v0.4.0

Published

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.

Readme

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-rua | DomainCanary | | ----------------- | ----------------------------------------- | -------------------------------------------------------------------------------- | | Who runs it | You, in your own Worker, server or script | We do | | Receiving reports | Your mailbox or Email Worker | A reporting address for each domain | | What you get | Typed records from each report | Every sending source matched to the service behind it, with pass and fail trends | | Alerts | The ones you write | A Monday digest, and an email when your DMARC record changes | | History | Whatever you store | 90 days of report detail on the free plan, 12 months on the Starter plan | | Price | Free, MIT | Free 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.