A healthtech sender cannot treat a row in tenants as proof that a domain exists, is authenticated, or still belongs to that tenant. The quota decision should use an atomic local admission ledger, while a recurring Node.js reconciler compares that ledger with the authoritative domain inventory and current DNS evidence.
TL;DR: reserve quota before creating a sender domain, record the external domain identifier, and reconcile both directions. Count active and in-flight reservations, not a cached integer on the tenant row. Permit delivery only after SPF, DKIM, and DMARC evidence has been observed; do not confuse that delivery gate with quota admission.
That separation matters for a clinic platform. A duplicate retry must not consume a second slot, two simultaneous onboarding requests must not both claim the last slot, and a domain deleted outside the application must eventually release its slot. The simple approach—read tenant.domain_count, compare it with the limit, then increment—cannot establish any of those properties under concurrency.
How can reconciliation enforce a domain quota without tenant table trust?
A mutable count is a projection. It says what one transaction believed, not which sender domains justify the number. If domain creation happens across a database and a separate control plane, there is no single transaction covering both systems. A timeout makes the ambiguity obvious: the create request may have failed before acceptance, or it may have succeeded while the response was lost.
That ambiguity is the bug.
Retries sharpen the problem. Without a stable idempotency key, the same clinic onboarding operation can create two reservations. Without row locking or an equivalent serializable admission operation, two different operations can each read four used slots against a limit of five and both proceed.
The useful invariant is concrete: for one tenant, the number of non-expired reservations in pending plus records in active must not exceed the configured quota. Every counted item has an identity and a state. A stored counter can still exist as a cache, but it is never the evidence used to repair state.
Count identities.
There is a second boundary. A quota slot answers, “May this tenant register another sender domain?” DNS answers, “Has the operator published the authentication records expected for this domain?” DMARC builds on SPF and DKIM authentication and identifier alignment, and its policy record is published in DNS. Passing admission does not imply passing that delivery evidence check.
Use a ledger for admission
Keep the state machine small: pending, active, releasing, and failed. A pending row needs an expiry so a process crash cannot hold capacity forever. An active row needs the external identifier returned by the authoritative domain system. The tuple (tenant_id, idempotency_key) should be unique, as should the normalized domain where your ownership rules require exclusivity.
The critical section is short. Lock the tenant quota record, return an existing reservation for a repeated idempotency key, count live reservations, and insert one pending row if capacity remains. Commit before making a network call. This is a deliberate trade-off: a short-lived reservation can temporarily reduce available capacity, but the database lock is not held across slow I/O.
Here is the focused TypeScript shape. The database interface is intentionally generic; its transaction and lockQuota methods must provide the documented locking semantics of the database adapter you choose.
type DomainState = "pending" | "active" | "releasing" | "failed";
type Reservation = {
id: string;
tenantId: string;
domain: string;
idempotencyKey: string;
state: DomainState;
expiresAt: Date | null;
externalId: string | null;
};
interface Tx {
lockQuota(tenantId: string): Promise<{ limit: number }>;
findByKey(tenantId: string, key: string): Promise<Reservation | null>;
countLive(tenantId: string, now: Date): Promise<number>;
insertPending(input: {
tenantId: string;
domain: string;
idempotencyKey: string;
expiresAt: Date;
}): Promise<Reservation>;
}
interface Database {
transaction<T>(work: (tx: Tx) => Promise<T>): Promise<T>;
}
async function reserveDomain(
db: Database,
input: { tenantId: string; domain: string; idempotencyKey: string },
now = new Date(),
): Promise<Reservation> {
return db.transaction(async (tx) => {
const quota = await tx.lockQuota(input.tenantId);
const existing = await tx.findByKey(input.tenantId, input.idempotencyKey);
if (existing) return existing;
const used = await tx.countLive(input.tenantId, now);
if (used >= quota.limit) throw new Error("DOMAIN_QUOTA_EXCEEDED");
return tx.insertPending({
...input,
expiresAt: new Date(now.getTime() + 15 * 60_000),
});
});
}
Fifteen minutes is an example lease, not a universal recommendation. Set it longer than the normal create-and-confirm path, then measure expirations and late confirmations. The important mechanism is an explicit deadline interpreted consistently by admission and reconciliation.
Do not mark the row active using the requested domain alone. Persist the returned external identifier and use it for subsequent reads and deletion. If creation fails definitively, mark the reservation failed. If the outcome is ambiguous, leave it pending for reconciliation rather than guessing and issuing another unkeyed create.
Reconcile from identities, not totals
The reconciler needs two inventories: all locally relevant rows for a tenant and all authoritative domains within that same ownership scope. Comparing only localCount with remoteCount hides the exact repair and can report equality while the sets contain different domains.
Consider the ambiguous timeout as a sequence rather than an error label. The application commits reservation r-17, the authoritative system accepts clinic.example, and the connection closes before the response reaches the worker. A retry with the same application idempotency key finds r-17 and does not reserve another slot. The next reconciliation sees a pending local identity and an authoritative domain with the same normalized name; after applying the tenant ownership rule, it attaches the returned external identifier and activates the existing row. By contrast, a retry carrying a new key is a different operation and must compete for quota normally. This is why a total of “one over there and one over here” is weak evidence: only the identity join explains whether there is one completed operation, two operations, or an ownership conflict.
Normalize domain names before comparison: remove a trailing root dot, lowercase the ASCII representation, and keep the normalization function shared between admission and reconciliation. Then join primarily on the stable external identifier. A normalized domain is a useful secondary match for recovering an ambiguous create, but assigning it must follow the application's tenant ownership rule; a name match alone should not silently transfer ownership.
A run can classify differences without making every repair automatic:
| Observation | Interpretation | Safe action |
|---|---|---|
Local pending, authoritative match found |
Create succeeded or completed late | Attach the external ID and activate |
Local active, authoritative item absent |
Deletion or inventory drift | Confirm with another read, then release |
| Authoritative item, no local row | Out-of-band creation or lost write | Import into a review state and count it |
Local expired pending, no match |
Abandoned reservation | Mark failed and free capacity |
| Same normalized name, conflicting owner | Ownership conflict | Quarantine for review; do not reassign |
One missing read should not trigger destructive cleanup. DNS and remote inventory observations can be incomplete because of transient lookup or transport errors. Record the failed observation, retry with bounded backoff, and require a confirmation policy before moving an active record to releasing. The exact threshold is an operational choice; expose it as policy rather than burying it in control flow.
Reconciliation must also be idempotent. Give each run an identifier, update rows with compare-and-set conditions, and make activation or release safe to repeat. Work tenant by tenant with bounded concurrency. That keeps one large account from delaying every clinic and prevents the repair job from creating an avoidable request spike.
Keep authentication evidence separate
For healthtech mail, an inventory match is necessary but insufficient. Store evidence observations alongside the domain record without putting them into the quota count: which record was queried, what normalized result was seen, and when it was observed. Never store a vague dns_verified = true with no timestamp or basis.
SPF and DKIM can produce authenticated identifiers used by DMARC. DMARC then evaluates alignment with the domain in the visible From header and describes receiver handling through its published policy. The reporting mechanism is operational evidence too: aggregate reports can reveal authentication and alignment outcomes that a one-time DNS lookup cannot.
This suggests two independent state transitions. Quota reconciliation moves a domain between reservation lifecycle states. Authentication evaluation moves delivery eligibility between unverified, observed, and policy-ready states according to your explicit rules. Keeping them separate prevents a temporary DNS issue from freeing a quota slot and prevents a remotely present but unauthenticated domain from being treated as ready to send.
Keep those clocks separate.
Be careful with organizational domains and subdomains. DMARC discovery and policy application have defined rules, including a subdomain policy tag. Do not reproduce those rules with casual string splitting. Use a maintained public-suffix implementation where organizational-domain calculation is required, and test parent/subdomain cases against the standard.
Measure the convergence, then choose the interval
The schedule should come from the risk window, inventory cost, and observed drift—not from a round number copied from another system. Start by recording reconciliation lag from first divergence to repair, pending lease expirations, ambiguous creates recovered, authoritative orphans, confirmed missing active domains, DNS evidence age, and per-tenant scan duration.
Also log decisions, not secrets. A useful event carries the tenant ID, local reservation ID, external ID when known, normalized domain, previous and next state, run ID, and reason code. Do not put DKIM private keys, raw credentials, or health data into logs. Domain authentication records are configuration evidence; they are not a reason to mix patient context into the control plane.
Test the failure boundaries before trusting the design. Run two concurrent admissions against one remaining slot. Repeat one idempotency key. Simulate a timeout after authoritative creation but before the local activation write. Remove an active domain out of band, return a partial inventory page, and run the same reconciliation twice. The expected result is stable: no over-admission, no duplicate reservation, and eventual classification of every mismatch without ownership reassignment.
The deciding metric is convergence with explainable evidence. A faster loop that repeatedly misclassifies partial reads is worse than a slower loop with confirmed observations, while an extremely cautious loop leaves stale slots occupied and delays clinic onboarding. Measure both sides before copying the 15-minute lease or any reconciliation cadence from this example.
References
- DMARC semantics, discovery, alignment, policy, and reporting are defined in RFC 7489.
Top comments (0)