← back to Email Deliverability Agent
audit.js
117 lines
#!/usr/bin/env node
'use strict';
/**
* audit.js — Purelymail domain deliverability audit.
*
* Reads the Purelymail MCP snapshot fixtures, evaluates every domain, and
* flags:
* - domains failing MX (cannot physically receive email)
* - domains failing DKIM (sends look unauthenticated -> spam)
* - domains failing DMARC (no alignment policy -> spam / spoofable)
* - domains with no routing (MX may pass but mail goes nowhere)
*
* Writes output/domain-health.json and prints a human summary.
*
* node audit.js
*/
const fs = require('fs');
const path = require('path');
const {
loadDomainSnapshot,
loadRoutingRules,
domainHealth,
ensureOutputDir,
} = require('./lib/data');
function daysSince(iso) {
if (!iso) return null;
const ms = Date.now() - new Date(iso).getTime();
return Number.isFinite(ms) ? Math.floor(ms / 86400000) : null;
}
function main() {
const snapshot = loadDomainSnapshot();
const routing = loadRoutingRules();
const health = domainHealth(snapshot, routing.rules);
const failMx = health.filter((h) => !h.passesMx);
const failDkim = health.filter((h) => !h.passesDkim);
const failDmarc = health.filter((h) => !h.passesDmarc);
const failSpf = health.filter((h) => !h.passesSpf);
const noRouting = health.filter((h) => !h.hasRouting);
const undeliverable = health.filter((h) => !h.deliverable);
const healthy = health.filter(
(h) => h.deliverable && h.passesDkim && h.passesDmarc
);
const report = {
generatedAt: new Date().toISOString(),
snapshotCapturedAt: snapshot.capturedAt || null,
routingCapturedAt: routing.capturedAt || null,
totals: {
domains: health.length,
failMx: failMx.length,
failDkim: failDkim.length,
failDmarc: failDmarc.length,
failSpf: failSpf.length,
noRouting: noRouting.length,
undeliverable: undeliverable.length,
fullyHealthy: healthy.length,
},
failMx: failMx.map((h) => h.domain),
noRouting: noRouting.map((h) => h.domain),
undeliverable: undeliverable.map((h) => ({
domain: h.domain,
issues: h.issues,
})),
domains: health,
};
const outDir = ensureOutputDir();
const outFile = path.join(outDir, 'domain-health.json');
fs.writeFileSync(outFile, JSON.stringify(report, null, 2));
// ---- human summary ----
const t = report.totals;
console.log('\n=== Purelymail Domain Deliverability Audit ===');
console.log(`snapshot captured : ${report.snapshotCapturedAt || 'unknown'}`);
const stale = daysSince(report.snapshotCapturedAt);
if (stale != null && stale > 7) {
console.log(` WARNING: snapshot is ${stale} days old — re-run the MCP tools.`);
}
console.log(`total domains : ${t.domains}`);
console.log(`fail MX : ${t.failMx} (cannot receive email at all)`);
console.log(`no routing rule : ${t.noRouting} (mail accepted but routed nowhere)`);
console.log(`UNDELIVERABLE : ${t.undeliverable} (fail MX OR no routing)`);
console.log(`fail DKIM : ${t.failDkim} (sends unauthenticated)`);
console.log(`fail DMARC : ${t.failDmarc} (no alignment policy)`);
console.log(`fail SPF : ${t.failSpf}`);
console.log(`fully healthy : ${t.fullyHealthy} (MX+routing+DKIM+DMARC all pass)`);
if (failMx.length) {
console.log('\n--- Domains FAILING MX (top 30) ---');
failMx.slice(0, 30).forEach((h) => console.log(` ${h.domain}`));
if (failMx.length > 30) console.log(` ...and ${failMx.length - 30} more`);
}
if (noRouting.length) {
console.log('\n--- Domains with NO routing rule ---');
noRouting.forEach((h) => console.log(` ${h.domain}`));
}
console.log(`\nWrote ${outFile}`);
console.log('Next: run `node contact-audit.js` to find sister sites losing leads.\n');
}
if (require.main === module) {
try {
main();
} catch (err) {
console.error('audit.js failed:', err.message);
process.exit(1);
}
}
module.exports = { main };