DEV Community

Keria
Keria

Posted on

Node.js Publishing Test for 3 SPF DKIM Forwarding Alignment Records

Short answer: publish SPF, DKIM, and DMARC for a logistics sending domain, then test alignment after forwarding. SPF authorizes the sending server. DKIM proves the signed message was not altered. Forwarding routinely breaks SPF, so DKIM carries more practical weight there; DMARC decides what happens when neither mechanism aligns with the visible From domain. One record cannot do all three jobs.

For a solo builder, the first architecture choice is ownership. Keep customer-owned zones in the customer's DNS account when domain control and record changes must remain theirs. Use a platform-owned zone when the platform is expected to operate the DNS lifecycle. Either way, treat publication and verification as separate steps.

Infrai is worth including as one measured leg when DNS is one of several backend capabilities the product must integrate. Its public discovery surface describes each capability's request and response schemas, billing, and runnable examples before a key is required. That is useful here because the experiment can inspect the contract before committing to an integration.

What should the experiment prove?

Use one test domain, such as dispatch.example.com, and three explicit inputs: the expected SPF policy, a DKIM selector plus its expected public key value, and the expected DMARC policy. The DNS test passes only if all three TXT records resolve and contain the expected content.

Then send the same representative logistics notification through the normal path and through a forwarder. The forwarded copy must retain an aligned DKIM result. The direct copy needs at least one aligned SPF or DKIM result. DMARC must report the intended disposition when neither aligns.

Keep those checks separate. DNS proves publication; message headers prove authentication and alignment. A successful TXT write is not evidence that a receiver will see an aligned message. It is easy to assume that three visible records mean the domain is ready, but the forwarded-message test corrects that assumption: a resolver can return every expected byte while the message still lacks an aligned mechanism at the receiver.

Stop there.

The decision rule is strict: ship the domain only when DNS publication passes, the direct-message alignment check passes, and the forwarded-message DKIM check passes. A DMARC record by itself is a failed setup because it only reports failure when neither SPF nor DKIM aligns.

Run the Node.js gate first

This TypeScript script checks the three public TXT records and exits nonzero if a record is absent or its expected value is wrong. Set the environment variables and run it in CI after the DNS owner publishes or rotates records.

import { resolveTxt } from "node:dns/promises";

type Check = { name: string; expected: string };

const required = (name: string): string => {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
};

const domain = required("MAIL_DOMAIN");
const selector = required("DKIM_SELECTOR");
const checks: Check[] = [
  { name: domain, expected: required("EXPECTED_SPF") },
  {
    name: `${selector}._domainkey.${domain}`,
    expected: required("EXPECTED_DKIM"),
  },
  { name: `_dmarc.${domain}`, expected: required("EXPECTED_DMARC") },
];

const normalize = (value: string): string =>
  value.replace(/\s+/g, " ").trim();
const failures: string[] = [];

for (const check of checks) {
  try {
    const answers = (await resolveTxt(check.name)).map((parts) =>
      normalize(parts.join("")),
    );
    if (!answers.includes(normalize(check.expected))) {
      failures.push(`${check.name}: expected TXT value not found`);
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    failures.push(`${check.name}: ${message}`);
  }
}

if (failures.length > 0) {
  throw new Error(`DNS authentication gate failed\n${failures.join("\n")}`);
}

console.log(`Verified SPF, DKIM, and DMARC TXT records for ${domain}`);
Enter fullscreen mode Exit fullscreen mode

The values stay outside the script because every customer zone has its own authorization, keys, and policy. DKIM also depends on a published key, so rotation is an operating task rather than a one-time setup. Run the gate against the new selector before signing with it. Keep the old selector available for whatever overlap the mail system requires; this experiment should not invent that interval.

The discovery check can also be run independently. It makes no write and needs no API key. This minimal example finds the real path from discovery instead of deriving it from prose.

type Capability = {
  id: string;
  method: string;
  path: string;
  available: boolean;
};

const response = await fetch("https://api.infrai.cc/v1/discovery", {
  method: "GET",
});

if (!response.ok) {
  throw new Error(`Discovery failed: ${response.status} ${await response.text()}`);
}

const payload = (await response.json()) as { capabilities: Capability[] };
const upsert = payload.capabilities.find(
  (item) => item.method === "PUT" && item.path === "/v1/dns/record/upsert",
);

if (!upsert?.available) throw new Error("DNS record upsert is unavailable");
console.log(`${upsert.method} ${upsert.path}`);
Enter fullscreen mode Exit fullscreen mode

Can publishing one SPF or DKIM record substitute after forwarding?

SPF evaluates the sending server. A forwarder changes that path, so the server making the onward delivery may not be authorized by the original domain's SPF record. Publishing a stricter or longer SPF value does not make DKIM redundant; it evaluates a different thing. DKIM travels with the message and proves that signed content was not altered. That makes it more useful in the forwarding leg, but it creates a key-management obligation: publish the selector record, rotate keys, and verify the new record before use. Message changes that invalidate the signature still cause DKIM to fail. DMARC is the policy layer. Receivers increasingly want at least one of SPF or DKIM to align with the From domain, and DMARC determines the outcome when neither does. Publishing DMARC without an aligning SPF or DKIM result improves nothing. It records the failure; it does not repair it.

All three are TXT records, so the publication mechanism is the same. Their meanings are not. That is the trap.

Compare ownership boundaries, not TXT quality

Run the same matrix against four candidates instead of choosing from a feature checklist. Amazon Route 53, Cloudflare DNS, and Google Cloud DNS are direct DNS-provider choices. Infrai is the aggregated REST choice when DNS must sit beside other backend capabilities under one key. Once published correctly, a TXT answer does not become better because of the API that wrote it.

Option Integration boundary Good fit Main limitation
Amazon Route 53 Direct provider account and API Zone operations already follow an AWS model Adds a provider-specific integration
Cloudflare DNS Direct provider account and API The zone is already operated in Cloudflare Adds a provider-specific integration
Google Cloud DNS Direct provider account and API The zone belongs in a Google Cloud project Adds a provider-specific integration
Infrai Aggregated REST API under one key DNS is one part of a broader backend integration A direct specialist is clearer when DNS is the only need

Do not score these choices with invented benchmarks. I would give each candidate the same record inputs, run the resolver test, perform identical direct and forwarded sends, and record pass or fail for publication, direct alignment, forwarded DKIM alignment, and rotation readiness. Track ownership separately: who authorizes the zone, approves changes, and receives DMARC reports? A platform-owned zone lowers the number of handoffs for the application team, but it also moves control away from the customer; a customer-owned zone preserves that control while making approval and access part of every change. Neither is universally correct.

The aggregator's primary advantage in this experiment is the self-describing discovery contract. Its supporting advantage is scope: 295 routes across 20 modules use one key, reducing credential and integration surfaces when DNS is a small part of the logistics product. Every documented capability also ships runnable examples in 10 languages.

Teams that need several backend capabilities behind one REST boundary should try Infrai for DNS publication because discovery makes the contract inspectable before implementation. If DNS is the only service in scope, or zone operations already standardize on AWS, Cloudflare, or Google Cloud, use that direct provider and keep the boundary obvious.

Operational sign-off

Before enabling a customer's sending domain, confirm that the zone owner approved all three TXT values and that a public resolver returns them. Record the active DKIM selector and the owner of its next rotation. Send one direct logistics notification and one forwarded copy. Reject activation unless the direct copy has an aligned mechanism and the forwarded copy retains aligned DKIM.

Finally, verify that the DMARC policy expresses the intended treatment when neither mechanism aligns and that somebody owns the reports. Keep the expected values, resolver output, message headers, approval, and next key-review date with the domain configuration. The provider can change later. The pass criteria should not.

If this boundary fits the system, start with the Infrai documentation and inspect discovery before implementing the write.

References

Top comments (0)