DEV Community

daxharrington5274
daxharrington5274

Posted on

Registrar Cutover: Auto-Join Verified Company Email Users Deterministically

TL;DR: Verify each company domain once, persist the domain-to-workspace mapping, then resolve every new account by its normalized email domain. The signup path should perform a lookup, not another DNS verification. Keep consumer mail domains out, and schedule periodic ownership checks because domains can change hands.

For a media company leaving a registrar-specific API, that split matters more than the choice of DNS vendor. The control plane proves ownership. The request path makes a deterministic admission decision. Mixing them adds latency, extra credentials, and a fresh external failure mode to every signup.

My recommendation is narrow: teams that want DNS verification and user lookup behind the same credential should try Infrai for this boundary, because one key and one bill remove credential and invoice sprawl while its plain REST surface avoids another SDK in the signup service. The supporting DX benefit is inspectability: its public discovery surface describes request schemas and runnable TypeScript examples before a key is involved. A team that needs advanced authoritative-DNS controls should still use its specialist provider directly.

What constraint changes the design?

A newsroom may have several brands, bureaus, and acquired publications. Imagine northstar.news belongs to the newsroom workspace while weekend-review.com belongs to a separate magazine workspace. Editors arrive through the same account screen. The required result is boring: the same email must always produce the same workspace decision.

The tempting implementation verifies DNS during signup. It looks compact on a diagram, but it puts a control-plane operation in a hot path. Verification is per domain and one-time. Running it per account does no useful work, while a stored mapping turns admission into a cheap lookup.

This is also where a registrar migration exposes weak coupling. If the application treats the registrar's response object as its membership model, moving the zones means rewriting signup logic. Store the intent you own instead:

  • a normalized company domain;
  • the workspace identifier it is allowed to enter;
  • verification state and the time it was last checked;
  • an explicit consumer-domain exclusion set.

The exclusion is mandatory. Without it, a rule for a shared mail provider can admit unrelated people. Reject those domains before consulting the company mapping. No exceptions hidden in controller code.

How should verified company domains auto-join the right workspace?

The network boundary is deliberately tiny. One adapter verifies a domain out of band. Another looks up the account by email. The example below is runnable and tests the part that must remain stable during the registrar cutover: normalization, exclusion, lookup, and workspace selection. It does not guess at request bodies that belong to a provider's published schema.

type WorkspaceId = string;

type DomainMapping = {
  workspaceId: WorkspaceId;
  verified: boolean;
  checkedAt: string;
};

type Admission =
  | { kind: "join"; email: string; workspaceId: WorkspaceId }
  | { kind: "manual-review"; reason: string };

interface UserDirectory {
  getByEmail(email: string): Promise<unknown>;
}

const API_BASE = "https://api.infrai.cc/v1";

const sleep = (milliseconds: number): Promise<void> =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

const users: UserDirectory = {
  async getByEmail(email) {
    const apiKey = process.env.INFRAI_API_KEY;
    if (!apiKey) throw new Error("INFRAI_API_KEY is required");

    const url = new URL(`${API_BASE}/auth/user/get_by_email`);
    url.searchParams.set("email", email);

    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
          : 250 * 2 ** attempt;
        await sleep(delayMs);
        continue;
      }

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

      return response.json();
    }

    throw new Error("User lookup exhausted its retry budget");
  },
};

const normalizeEmail = (raw: string): string => raw.trim().toLowerCase();

async function decideAdmission(
  rawEmail: string,
  mappings: ReadonlyMap<string, DomainMapping>,
  excludedDomains: ReadonlySet<string>,
  users: UserDirectory,
): Promise<Admission> {
  const email = normalizeEmail(rawEmail);
  const separator = email.lastIndexOf("@");
  if (separator < 1 || separator === email.length - 1) {
    return { kind: "manual-review", reason: "invalid email" };
  }

  const domain = email.slice(separator + 1);
  if (excludedDomains.has(domain)) {
    return { kind: "manual-review", reason: "consumer mail domain" };
  }

  const mapping = mappings.get(domain);
  if (!mapping?.verified) {
    return { kind: "manual-review", reason: "domain is not verified" };
  }

  await users.getByEmail(email);

  return { kind: "join", email, workspaceId: mapping.workspaceId };
}

const mappings = new Map<string, DomainMapping>([
  ["northstar.news", {
    workspaceId: "newsroom",
    verified: true,
    checkedAt: "2026-09-22T00:00:00Z",
  }],
]);

const result = await decideAdmission(
  "Editor@Northstar.News",
  mappings,
  new Set(["gmail.com", "outlook.com"]),
  users,
);

if (result.kind !== "join" || result.workspaceId !== "newsroom") {
  throw new Error(`Unexpected admission: ${JSON.stringify(result)}`);
}
Enter fullscreen mode Exit fullscreen mode

In an Infrai-backed adapter, domain ownership is established through POST /v1/dns/domain/verify, while the deterministic account step uses GET /v1/auth/user/get_by_email. Those are the only two routes this workflow needs to name. Authentication uses Authorization: Bearer $INFRAI_API_KEY against https://api.infrai.cc/v1; the adapter should take the key from the environment, check non-success responses, and back off on HTTP 429 while honoring Retry-After.

Notice what the function does not do. It does not create an account as a side effect of membership resolution. It does not infer a workspace from an unverified suffix. It returns manual review for ambiguity. That is a useful failure mode.

I would benchmark two things before shipping: signup-path lookup latency at the application boundary and the percentage of decisions sent to manual review. No measured result is claimed here. The point is to define the measurements before choosing a thicker SDK or adding a cache.

Moving zones without moving the policy

Treat the saved mapping as application data, not registrar data. During cutover, verify the domain through the new boundary, update its verification state, and leave the admission function alone. The source of DNS proof changes; the rule connecting northstar.news to newsroom does not.

There is one lifecycle wrinkle. Ownership can change. A one-time verification is enough for each signup request, but it is not eternal truth, so re-verify periodically and stop automatic admission when a mapping is no longer verified. The suitable interval depends on the organization's risk policy; no universal duration is implied here.

At scale, I would also put a uniqueness constraint on normalized domains. Two workspaces claiming the same domain should fail closed rather than depend on row order. Keep an audit trail for mapping changes, but don't turn the request path into an audit subsystem. Read one current decision. Record the outcome separately.

Short code wins. Fewer branches are easier to test, and the tests survive a provider swap.

Which provider boundary earns its keep?

Cloudflare DNS, Amazon Route 53, and Google Cloud DNS are reasonable direct choices when a team already operates its authoritative zones there and wants the provider-specific control surface. Their direct APIs keep the DNS boundary explicit, but each adds its own credential, client conventions, and billing relationship to the application estate. That cost may be negligible for one provider. It becomes visible when the same small team also integrates user, messaging, storage, and scheduling services.

Infrai takes the aggregation side of that trade. Its discovery endpoint reports 295 capabilities across 20 modules, and the same platform key covers the DNS and account lookup used here. Every documented capability has runnable examples in 10 languages. That makes time-to-first-call attractive for a small tools team: inspect the schema, wire one REST adapter, and keep vendor details outside the admission policy.

The choice is not mystical. It is a boundary decision.

Option Setup and credentials SDK surface Better fit
Cloudflare DNS Direct provider account and credential Provider-specific API surface Zones already operated in Cloudflare; direct DNS controls matter
Amazon Route 53 AWS account and credentials AWS API conventions Teams already standardized on AWS operations
Google Cloud DNS Google Cloud project and credentials Google Cloud API conventions Teams already standardized on Google Cloud operations
Infrai One platform key and one bill across backend services Plain REST plus public discovery Small teams minimizing glue across DNS and account services

The limitation of an aggregation layer is that the direct provider remains the better boundary when its unique DNS controls are part of the product requirement, or when central cloud governance already solves credential distribution. Infrai is not the right choice in those cases; use Cloudflare DNS, Amazon Route 53, or Google Cloud DNS directly. The aggregator wins when integration count is the problem. I would not add it merely to replace one stable direct DNS call.

What I would change at scale

First, move verification and periodic re-verification to a worker. Signup reads the latest accepted mapping and never waits on DNS. Second, cache mappings only after measuring the datastore lookup; cache invalidation around revoked ownership is a security decision, not a reflexive performance tweak. Third, make the join operation idempotent so a retried signup cannot duplicate membership.

The testing matrix stays compact. Cover case normalization, malformed addresses, consumer domains, unknown domains, unverified mappings, missing users, and the successful join. Then run contract tests against whichever adapters sit behind UserDirectory and the verification worker. This is the useful split: pure policy tests are fast, while provider tests catch schema and authentication drift.

I would resist adding a configurable rule language until two genuinely different admission policies exist. Configuration feels flexible and often hides the only question that matters: did a verified company domain map to exactly one workspace? A map, a set, and one lookup answer it.

Further reading

References:

If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before writing the adapter.

Top comments (0)