Customer-owned zones preserve control over a customer's mail identity; platform-owned zones give the application a place where it can automate changes directly. TL;DR: commit the tenant reservation and a durable provisioning intent together, then reconcile DNS outside the signup transaction. Treat CNAME upsert and rollback as recoverable operations, and track SPF, DKIM, and DMARC separately before declaring the mail setup ready.
For a hypothetical B2B SaaS signup, I would put cedar.app.example in a platform-owned zone while the customer keeps customer.example and its mail policy. Suppose the CNAME write times out after the signup has committed. My first postmortem question would be: what page fired? A failed request counter does not establish whether the record exists, and a green signup dashboard does not establish that the customer's mail authenticates. This is a worked scenario, not an account of a production incident.
Preserve the evidence.
The invariant I would extract is that an unknown DNS outcome must retain an owner and a recovery path. An HTTP response should not decide who owns the next attempt.
Should a tenant subdomain be provisioned inside the signup transaction?
The reader asking for a Node.js example that provisions a tenant subdomain inside the signup transaction is asking for a useful guarantee: either the tenant gets its name or the system can explain and recover the incomplete signup. Implement that guarantee as explicit application state. The minimal implementation below uses Go; the transaction boundary is the same design concern in a Node.js service.
Use one local transaction to reserve the tenant label, record its generation, and insert a provisioning job. Require uniqueness on the reserved name. If the transaction fails, neither reservation nor job should become visible; if it commits, a worker has durable work even when the client disconnects. Those are requirements for the storage implementation, not guarantees supplied by calling a function named Commit.
The tempting alternative is to hold the database transaction open while making the DNS request. That couples signup latency to the external request without providing a shared commit protocol: a timeout still leaves the caller unable to distinguish a rejected write from an accepted write whose response was lost. Rolling back the local row at that moment would throw away useful recovery information. This is a design failure worth preventing before it appears in a postmortem.
Return a provisioning state.
A practical proposed lifecycle is reserved, dns_pending, dns_ready, and mail_pending, followed by ready only when the product's documented checks pass. Record terminal rejection separately from retryable uncertainty. Keep the status visible to the customer so that another click on signup does not become an accidental second provisioning request.
Zone ownership determines who can finish the job
With a platform-owned application zone, the worker can own the desired CNAME and its repair process. With a customer-owned zone, the application needs either authorized automation or an explicit customer action; issuing instructions is not evidence that those instructions have been applied. Keep that distinction in the state model, because a worker cannot retry its way into authority over someone else's zone.
For the example tenant, the application route and the customer's mail identity are separate resources. A successful CNAME upsert for cedar.app.example must not satisfy the mail-readiness check for customer.example. DMARC evaluates alignment with the domain in the message's visible From field, using authenticated SPF or DKIM identifiers; a web-routing record is not that evidence. RFC 7489 describes this distinction and the policy-discovery process.
For customer-domain mail, make the requested SPF authorization and DKIM signing configuration specific to the sending arrangement, then inspect DMARC policy and alignment with that customer's intended From domain. Avoid publishing universal TXT examples that claim to fit every sender. An SPF-authenticated identifier can differ from the visible From domain, and a DKIM signature can verify without its signing domain aligning with that From domain. Either aligned authentication path can satisfy DMARC; requiring both to pass would be a separate product policy, not the DMARC rule.
There is a consequential trade-off here. A platform-controlled mail identity can reduce the number of customer DNS actions, but it changes the identity under which messages are sent; preserving the customer's identity requires coordinating records and verification in the customer's zone. Choose that boundary deliberately. It cannot be settled by a generic successful-provisioning badge.
Authentication is not an inbox guarantee. RFC 7489 leaves message disposition subject to receiver policy, so report what was checked rather than promising delivery from a DNS result.
Make an uncertain upsert safe to repeat
The following is a compilable boundary sketch for a reconciliation worker, not a DNS provider SDK. Its storage and DNS adapters must implement the stated contracts. The signup handler must already have committed the reservation and job atomically, and the adapter must restrict writes to the zone and records this application is authorized to manage.
package provisioning import ( "context" "fmt" ) type Intent struct { TenantID string Generation int64 Name string Target string } type Store interface { // Load the current committed intent, never an old queue payload. Current(context.Context, string) (Intent, error) // Compare generation atomically; a stale result must not mark ready. MarkDNSReady(context.Context, string, int64) error } type DNS interface { // Serialize with replacement and cancellation for this tenant. // Repeating the same intent must converge without duplicate records. // Reject a generation older than the latest accepted generation. // A nil result means the desired authoritative record was verified. EnsureCNAME(context.Context, Intent) error } func Reconcile(ctx context.Context, s Store, dns DNS, tenantID string) error { intent, err := s.Current(ctx, tenantID) if err != nil { return fmt.Errorf("load DNS intent: %w", err) } if err := dns.EnsureCNAME(ctx, intent); err != nil { return fmt.Errorf("reconcile DNS generation %d: %w", intent.Generation, err) } if err := s.MarkDNSReady(ctx, intent.TenantID, intent.Generation); err != nil { return fmt.Errorf("record DNS verification: %w", err) } return nil } The interface comments carry the difficult part.
A database comparison before an external write is insufficient if cancellation or replacement can race that write. Route those operations through the same serialized controller, or implement equivalent fencing in the adapter; do not assume the DNS service understands the generation field. If that contract cannot be enforced, this sketch is incomplete for concurrent replacement. Consider generation 7 waiting on a DNS call while generation 8 requests cancellation: checking a database flag before the first call would not stop its late completion. The controller must finish or resolve the earlier operation before applying the newer intent, and it must retain uncertain work across restart. A generation number in a log line cannot provide that ordering by itself.
Don't discard uncertainty.
A read timeout or an upsert timeout returns an error and leaves the job recoverable. If the record was written but MarkDNSReady failed, the next attempt loads the current intent and verifies the desired record again. Schedule retries with bounded backoff, retain attempt history, and expose persistent failure for investigation. The code intentionally defines no arbitrary retry count that would quietly turn an uncertain outcome into abandonment.
Test the boundaries that change recovery decisions: an accepted write followed by a lost response, a failed readiness update after verification, and a cancellation racing a worker. Also test an unexpected existing record. Treat it as an ownership conflict requiring investigation unless the controller can establish that it owns the record; an upsert must not become permission to overwrite unrelated configuration.
Rollback is another desired state
Do not make the timeout handler issue an unconditional delete. In the hypothetical incident, that delete could remove the record the first request successfully created; after a replacement, it could also remove the newer tenant configuration. Model cancellation as a new generation with an explicit cleanup intent, and keep the reservation until the controller has established the cleanup outcome.
For a newly allocated record, cleanup means removing only configuration whose ownership is established. For an update to an existing managed record, compensation may instead mean restoring the prior desired value. Preserve that value before the change. Customer-owned mail policy deserves its own boundary: cancelling a web signup is not sufficient reason to delete the customer's SPF or DMARC policy.
Operationally, measure the age of committed intents awaiting verification, recovery attempts, and ownership conflicts. Define pages around an actionable breach of the signup promise, with the tenant, name, generation, and last observed result attached. A dashboard counting successful API calls would miss the precise gap this design is meant to expose: accepted work without a verified outcome.
The smallest useful incident timeline has three separate observations. Preserve their timestamps:
| Observation | What it establishes | What remains open |
|---|---|---|
| Local commit | The reservation and job were saved together | DNS outcome |
| Authoritative record verification | The desired routing record was observed | Customer mail authentication |
| Mail-authentication check | The tested message's authentication and alignment results | Receiver disposition |
Diagnose from those observations before assigning blame to propagation or telling the customer to repeat setup.
This workflow is unnecessary when signup only reserves an application identity and no DNS change is required. If customer administrators must publish all records manually, use a pending-verification workflow with clear instructions rather than pretending the worker can perform the write. If the platform already uses a preconfigured routing arrangement, first establish whether a per-tenant CNAME is required at all.
The operational acceptance rule remains modest: retain enough state to recover uncertain writes, establish who controls each record, and make readiness describe the checks actually completed. Mail authentication and application routing should each have evidence of their own.
References
RFC 7489 supplies the DMARC authentication-alignment, policy-discovery, and receiver-policy details used here, especially Sections 3 through 6. The transaction lifecycle, controller contracts, and recovery tests are proposed implementation requirements for this example.

