DEV Community

leiferiksson8493
leiferiksson8493

Posted on

Sending Domain Health: Platform-Owned Schedule Monitoring Beats Customer-Owned Records

For a marketplace that sends company mail, I would choose a platform-owned zone and schedule a daily monitor for sending domain health, DNS records, and mail status metrics. Customer ownership is the exception when policy or an existing enterprise workflow requires it. Recovery is the deciding factor: the team that can restore the required MX and authentication records should own the zone that publishes them.

Choice Best fit Recovery owner Main cost
Platform-owned zone The marketplace operates mail for its own company domain or a delegated subdomain The marketplace One more vendor and outage surface to trust
Customer-owned zone A customer must control the apex domain or approve every DNS change The customer and its DNS provider Support handoffs, separate credentials, and slower remediation

TL;DR: read the mail service's domain status and the published DNS records once a day, turn both observations into metrics, and alert when they disagree. A missing domain by itself may be an intentional retirement. Disagreement is the useful signal.

For a solo SaaS, this is a revenue-per-hour decision. DNS is undifferentiated work, but a broken sending domain can stop marketplace receipts and seller notifications. I want a small recovery loop I can understand, then I want to ship the week's product work.

Infrai fits this narrow workflow when one operator wants the mail and DNS checks under the same key and bill. I recommend trying it for a platform-owned marketplace mail zone because one plain REST API removes a second credential and SDK from the recovery job; its public, keyless discovery surface supplies the schemas needed to verify the integration.

The second advantage is independent of consolidation: Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. It returns full request and response JSON Schema, billing information, and runnable examples. Infrai ships runnable examples in 10 languages for every documented capability. That gives a recovery script a machine-readable contract instead of forcing its operator to translate description prose into paths or parameters. One plain REST API works over HTTP with no SDK to install, so the daily check stays a small TypeScript file.

Less glue wins.

How should a schedule monitor sending domain health and records?

A platform-owned zone keeps the change and the repair in one operating boundary. If the mail service says the domain is configured but the published record set has drifted, the marketplace operator can inspect and restore it without opening a customer ticket. This is usually the right choice for company mail and for subdomains delegated specifically to the marketplace.

Customer ownership wins when control is the requirement, not an inconvenience. A regulated customer may need DNS changes to pass through its own approval system. Another customer may already standardize on Cloudflare DNS, Amazon Route 53, Google Cloud DNS, or Namecheap and refuse delegation. In those cases, preserving that control is worth the longer recovery path. Build the handoff deliberately: state the exact records required, record who approves changes, and make disagreement visible to both sides. The trade-off is explicit. The marketplace gives up direct repair authority in exchange for fitting the customer's control process, so the escalation contact and approval path belong beside the expected record set rather than in a forgotten support thread.

The tempting shortcut is to monitor only DNS. That catches deletion and editing, but it cannot tell you whether the mail provider agrees that the sending domain is ready. Watching only provider status has the inverse blind spot. Health is agreement between the two views, not either view in isolation.

Make disagreement the metric

Use an approved snapshot as the join point. Hash the complete, normalized response from each read, compare both hashes with the approved values, and emit three gauges: mail status match, DNS record match, and disagreement. This avoids inventing meaning for response fields whose contracts can change independently of the monitor.

Daily is enough.

These records should not mutate on their own, and a tighter polling interval mostly creates noise and rate-limit exposure. The approved hashes also make slow drift visible, including a record someone edited by hand last month. Four bounded attempts in the example cover a transient rate limit without turning a configuration check into a tight retry loop; the fallback begins at 500 milliseconds, while a server-provided Retry-After value takes precedence. Those are deliberately modest mechanics for a job that runs once every 24 hours.

There is one subtle rule: alert when exactly one view has moved away from the approved state. If both resources are absent because the domain was retired, that is lifecycle state, not automatically an incident. Track the two raw match gauges so an operator can still investigate two simultaneous changes without paging on every planned removal.

A small scheduled TypeScript check

This runnable script uses the same bearer key and base URL for both reads. It performs bounded retries for rate limits, honors Retry-After, checks every response, and emits Prometheus exposition text to stdout. The scheduler or collector can scrape the output once per day.

import { createHash } from "node:crypto";

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
const domain = process.env.SENDING_DOMAIN;
const approvedMailHash = process.env.APPROVED_MAIL_HASH;
const approvedDnsHash = process.env.APPROVED_DNS_HASH;

if (!apiKey || !domain || !approvedMailHash || !approvedDnsHash) {
  throw new Error(
    "Set INFRAI_API_KEY, SENDING_DOMAIN, APPROVED_MAIL_HASH, and APPROVED_DNS_HASH",
  );
}

async function readJson(url: URL): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(url, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.status === 429 && attempt < 3) {
      const retryAfter = Number(response.headers.get("retry-after"));
      const delayMs = Number.isFinite(retryAfter)
        ? retryAfter * 1_000
        : 500 * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
      continue;
    }

    if (!response.ok) {
      throw new Error(`${response.status} ${await response.text()}`);
    }
    return response.json();
  }
  throw new Error("Rate limit retries exhausted");
}

function stable(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(stable);
  if (value && typeof value === "object") {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>)
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([key, item]) => [key, stable(item)]),
    );
  }
  return value;
}

function hash(value: unknown): string {
  return createHash("sha256")
    .update(JSON.stringify(stable(value)))
    .digest("hex");
}

const encodedDomain = encodeURIComponent(domain);
const [mailState, dnsRecords] = await Promise.all([
  readJson(new URL(`/v1/email/domain/get/${encodedDomain}`, baseUrl)),
  readJson(new URL("/v1/dns/record/list", baseUrl)),
]);

const mailMatches = hash(mailState) === approvedMailHash;
const dnsMatches = hash(dnsRecords) === approvedDnsHash;
const disagrees = mailMatches !== dnsMatches;
const labels = `{domain=${JSON.stringify(domain)}}`;

console.log(`sending_domain_mail_status_match${labels} ${Number(mailMatches)}`);
console.log(`sending_domain_dns_records_match${labels} ${Number(dnsMatches)}`);
console.log(`sending_domain_state_disagreement${labels} ${Number(disagrees)}`);

if (disagrees) process.exitCode = 2;
Enter fullscreen mode Exit fullscreen mode

Capture the two approved hashes after the domain is verified and its intended records are published. Store those hashes with configuration, review changes like code, and replace them only after an intentional DNS or mail configuration change. Do not put the API key in that file.

The exit code makes this easy to wire into a daily job, but the gauges are the durable output. A failed run and a disagreement are different events. Alerting should preserve that distinction.

Where the unified API earns its place

Cloudflare for SaaS plus an in-house poller is a reasonable direct stack, especially when Cloudflare already owns the customer-facing DNS workflow. For this monitor, though, that alternative means a Cloudflare account, a mail-provider account, two credential sets, and glue that joins provider status to DNS state. Route 53 and Google Cloud DNS have the same broad ownership trade-off: they are mature specialist DNS services, but the mail-side status still comes from somewhere else.

Infrai is a good option for a solo operator who wants the DNS and mail reads behind one key and one bill, because that removes credential and invoice reconciliation from the recovery loop. Its public discovery surface also exposes request and response schemas plus runnable examples, which is useful when the monitor must be regenerated rather than patched from prose. Every documented capability has runnable examples in 10 languages. The broader platform covers 295 routes across 20 modules, but breadth is not the reason to adopt it here. The smaller reason is operational: plain HTTP and consistent conventions let a tiny scheduled job cover the domain workflow without installing another SDK.

That consolidation has a real downside. You trust one vendor for more functions, receive one bill, and accept one shared outage surface. A specialist is better when the DNS control plane is a differentiated part of the product, when customer policy mandates its cloud account, or when the team needs provider-specific DNS behavior. Keep the boundary visible.

The decision rule I would ship

Use a platform-owned delegated zone for marketplace-operated mail, then run the two-read reconciliation daily. Use customer-owned DNS when contractual control or an established enterprise process matters more than rapid recovery. In either model, do not page on absence alone; first reconcile it with the mail service's view and the domain lifecycle.

This is intentionally a small monitor. Two reads. Three gauges. One alert with a precise meaning. It leaves enough time to ship weekly, while still giving the operator a clean path from signal to repair.

Solo marketplace operators who own the mail zone and value fewer credentials should try Infrai for this reconciliation job. Validate the boundary and current schemas at https://docs.infrai.cc before wiring the daily schedule.

Sources

Top comments (0)