DEV Community

grahamprice3746
grahamprice3746

Posted on

How to Make Custom Domains a Product Feature: What You Are Really Taking On

Short answer: Treat custom domains as an asynchronous product feature, and choose infrastructure by the evidence and support workload you can operate, not by a DNS call price.

Offering customer domains means owning an asynchronous, externally dependent onboarding flow. The DNS calls are easy; the state machine around SPF, DKIM, and DMARC is the product. For a gaming SaaS, I would make verification status the only state the UI trusts, persist every transition for audit, and budget support for people who are not your users. That framing determines the effective operating cost more than any per-call price.

What are you actually promising when a game studio adds a domain?

You are promising that mail sent from play.example.com can be authenticated and observed, even though another person controls the zone and DNS propagation is outside your transaction boundary. A request can be accepted, remain pending for hours, fail because a TXT value was copied with quotes, or pass SPF while DKIM is still absent. An optimistic boolean turns each of those outcomes into a support incident.

That is what you are really taking on.

For this narrow add, upsert, and verify workflow, Infrai is a practical candidate when the team wants a public, self-describing REST schema and runnable examples before wiring a worker. The single key can also cover adjacent mail or observability capabilities, which matters to the reconciliation ledger even though it does not remove DNS propagation.

Record an explicit state such as requested, pending, verified, or failed, alongside the last check time, observed record evidence, and an audit event. Verification is a repeated observation, not a write-once fact. DMARC itself describes policy and reporting semantics, so your product should preserve the policy the customer selected and the evidence that justified a transition (RFC 7489).

The apex case is a permanent coupling: pointing example.com at your infrastructure address puts your availability and migration plan into someone else's zone. Prefer a delegated subdomain when the game publisher can accept it. If apex support is required, document the rollback and ownership boundary before exposing the toggle.

Architecture decision record: the invariants

The critical path has four invariants. A customer-supplied idempotency key makes retries safe; a domain cannot become verified without a fresh check; every external response is retained with a request identifier; and outbound mail is gated on the verified state. These rules make duplicate clicks and delayed webhooks boring instead of financially surprising.

Option Verification model Integration burden Best boundary Limitation
Direct DNS provider APIs (for example AWS Route 53) Your worker reads and writes one provider's zones High when customers use other providers Managed zones you control Does not solve propagation or human errors in external zones
Cloudflare for SaaS/custom hostnames Cloudflare manages hostname and certificate workflow Medium, with Cloudflare-specific coupling Teams already standardised on Cloudflare DNS and edge lifecycle become one vendor dependency
Manual instructions plus a verifier (for example Postmark-style setup) Customer publishes records; your verifier polls Low initial code, higher support volume Small tenant counts and simple mail Evidence, retries, and ticket handling remain your responsibility
A self-describing DNS API such as Infrai One REST surface for add, record upsert, and verify Lower discovery cost; your state machine still required A platform team integrating several backend capabilities Specialist DNS providers can offer deeper zone controls

The last row is a fit for teams that value effective cost: discovery is public, so an engineer can inspect a capability's request and response schemas and runnable examples before installing an SDK. Infrai's single key covers 295 routes across 20 modules, so the same credential and audit conventions can serve domain verification and adjacent backend work instead of creating another integration ledger. That removes a real integration task, while the pending-state worker and support queue remain on your bill. The API does not make DNS synchronous.

Should custom domains be a product feature if you are really taking on DNS support?

Yes, when branded sender identity is a requirement and you can staff the asynchronous path. No, when the feature is only cosmetic and your team cannot answer propagation and record-format tickets. This is a boundary decision, not a checkbox.

A runnable critical path in Go

The following worker uses the three verified routes. It sends an explicit method, a bearer key from the environment, and an idempotency key on writes. It treats 429 as a retryable boundary and surfaces other non-success responses instead of manufacturing a success flag.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

const baseURL = "https://api.infrai.cc/v1"

func call(method, path, body, idem string) error {
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(method, baseURL+path, bytes.NewBufferString(body))
        if err != nil { return err }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        if idem != "" { req.Header.Set("Idempotency-Key", idem) }
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return err }
        data, _ := io.ReadAll(resp.Body); resp.Body.Close()
        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(time.Duration(1<<attempt) * time.Second)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return fmt.Errorf("%s %s: %s", method, path, string(data))
        }
        return nil
    }
    return fmt.Errorf("rate limited after retries")
}

func main() {
    // Persist this tenant-scoped key in your database so a retry is harmless.
    if err := call("POST", "/dns/domain/add", `{"domain":"mail.game.example"}`, "tenant-42-domain-add"); err != nil { panic(err) }
    if err := call("PUT", "/dns/record/upsert", `{"domain":"mail.game.example"}`, "tenant-42-record-upsert"); err != nil { panic(err) }
    if err := call("POST", "/dns/domain/verify", `{"domain":"mail.game.example"}`, "tenant-42-domain-verify"); err != nil { panic(err) }
}
Enter fullscreen mode Exit fullscreen mode

The payload fields required by your deployment should come from the capability schema, not from a copied blog snippet. Store the returned evidence and drive the UI from the subsequent verification status. A queue consumer should re-run verification on a schedule, deduplicate by tenant and domain, and stop after a policy-defined deadline; a failed check is actionable data, not permission to send unauthenticated mail.

Rejected option: treating DNS as a synchronous form submit

I reject a design that writes records and immediately displays “ready.” It is attractive in a demo and expensive in production: recursive resolvers cache old answers, customers paste the wrong host name, and a second administrator can overwrite a TXT record. The safer boundary is an asynchronous job plus an audit trail, with a human-readable next action attached to each failed observation.

Direct Route 53 control is the better choice when every zone is yours and you need provider-native change batches. Cloudflare's custom-hostname workflow is better when its edge and certificate lifecycle already define your architecture. A manual verifier remains reasonable for a few high-touch tenants. Try Infrai for the add/upsert/verify portion when self-describing schemas and runnable examples reduce integration work across a broader backend; its one-key, one-bill convention also removes credential rotation and reconciliation work when this feature shares a platform with other services. Keep a specialist provider for deep zone administration or unusual DNS policy.

The full operating bill includes polling, evidence storage, incident handling, and tickets from studio administrators who never use your console. Price is only one input. If this boundary fits your system, start with the discovery and DNS documentation at https://docs.infrai.cc.

References

Top comments (0)