← back to Email Deliverability Agent
lib/data.js
112 lines
'use strict';
/**
* Shared data loaders for the email-deliverability-agent.
*
* The Purelymail MCP tools cannot be called directly from a plain Node
* script, so the agent reads two fixture files captured from the MCP:
*
* data/purelymail-snapshot.json <- mcp__purelymail__purelymail_list_domains
* data/purelymail-routing.json <- mcp__purelymail__purelymail_list_routing_rules
*
* To refresh them, re-run those MCP tools and overwrite the JSON files
* (see README "Refreshing the snapshot"). Each fixture records capturedAt
* so audit.js can warn when the data is stale.
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const DATA_DIR = path.join(ROOT, 'data');
const OUTPUT_DIR = path.join(ROOT, 'output');
function readJson(file) {
const full = path.join(DATA_DIR, file);
if (!fs.existsSync(full)) {
throw new Error(
`Missing fixture: ${full}\n` +
`Re-run the Purelymail MCP tools and save the output here. See README.`
);
}
return JSON.parse(fs.readFileSync(full, 'utf8'));
}
/** Returns { capturedAt, domains: [{ name, dnsSummary: {passesMx,...} }] } */
function loadDomainSnapshot() {
const snap = readJson('purelymail-snapshot.json');
if (!Array.isArray(snap.domains)) {
throw new Error('purelymail-snapshot.json: expected a "domains" array');
}
return snap;
}
/** Returns { capturedAt, rules: [{ domainName, matchUser, catchall, targetAddresses }] } */
function loadRoutingRules() {
const r = readJson('purelymail-routing.json');
if (!Array.isArray(r.rules)) {
throw new Error('purelymail-routing.json: expected a "rules" array');
}
return r;
}
/** Index routing rules by domain -> array of rules. */
function routingByDomain(rules) {
const map = new Map();
for (const rule of rules) {
const d = (rule.domainName || '').toLowerCase();
if (!map.has(d)) map.set(d, []);
map.get(d).push(rule);
}
return map;
}
/**
* Per-domain health verdict from the snapshot + routing data.
* A domain is "deliverable" only when MX passes AND at least one routing
* rule exists to actually deliver the mail somewhere.
*/
function domainHealth(snapshot, rules) {
const byDomain = routingByDomain(rules);
return snapshot.domains.map((d) => {
const dns = d.dnsSummary || {};
const domainRules = byDomain.get((d.name || '').toLowerCase()) || [];
const hasRouting = domainRules.length > 0;
const passesMx = dns.passesMx === true;
const issues = [];
if (!passesMx) issues.push('NO_MX');
if (!hasRouting) issues.push('NO_ROUTING');
if (dns.passesDkim !== true) issues.push('NO_DKIM');
if (dns.passesDmarc !== true) issues.push('NO_DMARC');
if (dns.passesSpf !== true) issues.push('NO_SPF');
return {
domain: d.name,
passesMx,
passesSpf: dns.passesSpf === true,
passesDkim: dns.passesDkim === true,
passesDmarc: dns.passesDmarc === true,
hasRouting,
routingRuleCount: domainRules.length,
// The hard gate: can a lead emailed here actually be received & routed?
deliverable: passesMx && hasRouting,
issues,
};
});
}
function ensureOutputDir() {
if (!fs.existsSync(OUTPUT_DIR)) fs.mkdirSync(OUTPUT_DIR, { recursive: true });
return OUTPUT_DIR;
}
module.exports = {
ROOT,
DATA_DIR,
OUTPUT_DIR,
loadDomainSnapshot,
loadRoutingRules,
routingByDomain,
domainHealth,
ensureOutputDir,
};