refactor: derive hosts from env so the root domain is configurable

Closed
KI
1mo ago
+380-227

What

This routes hardcoded production hosts through NEXT_PUBLIC_ROOT_DOMAIN, so changing the root domain later only needs env vars. With no new env vars set, every derived value equals today's literal. Defaults stay superset.sh.

  • packages/shared/src/constants.ts
    • Adds CANONICAL_URLS (MARKETING, API, APP, DOCS), derived only from the root domain. It deliberately ignores NEXT_PUBLIC_MARKETING_URL/NEXT_PUBLIC_DOCS_URL, so preview and local deployments keep printing the same production URLs they print today.
    • Adds COMPANY.SUPPORT_EMAIL. MAIL_TO now builds from it.
  • packages/email: adds EMAIL_SENDING_DOMAIN and NOREPLY_FROM (@superset/email/sender). All 16 transactional from: fields use it: auth (9), support (2), marketing forms (3), YC deals (1).
  • Call sites: literal hosts and support addresses are replaced with the constants above in:
    • API discovery, OpenAPI, MCP and OAuth metadata
    • Slack links
    • docs llms.txt, MCP and source links
    • marketing next.config (API origin, /mcp redirect, Link header), install commands, llms, contact/enterprise inbox
    • desktop docs links, billing mailtos, sample prompts, cookie-protected host
    • web billing mailto
    • mobile terms/privacy/share URL
    • CLI hints
    • email template links
  • Builds:
    • Desktop defineEnv fallbacks and the renderer CSP API origin derive from NEXT_PUBLIC_ROOT_DOMAIN.
    • The CLI build bakes NEXT_PUBLIC_ROOT_DOMAIN in.
  • i18n: two support-address messages now take the address as a placeholder ({email}, {SUPPORT_EMAIL}). The existing translations were carried over in all 16 locales.
  • Workflows and templates:
    • NEXT_PUBLIC_ROOT_DOMAIN is passed to the api/web/marketing/admin/docs deploys (production and preview), build-desktop.yml and build-cli.yml.
    • EMAIL_SENDING_DOMAIN is passed to every deploy that has RESEND_API_KEY.
    • RELAY_BACKUP_URL is passed to the web deploy. next.config already read it, but nothing supplied it.
    • Unset secrets arrive as empty strings, and every reader falls back on empty (||, or emptyStringAsUndefined).

Env vars that control each host

Host / valueEnv varDefault
Root domain (marketing, api., app., docs., status, trust, support@, did:web)NEXT_PUBLIC_ROOT_DOMAINsuperset.sh
Marketing URL per deploymentNEXT_PUBLIC_MARKETING_URLhttps://${root}
Docs URL per deploymentNEXT_PUBLIC_DOCS_URLhttps://docs.${root}
API / Web per deploymentNEXT_PUBLIC_API_URL, NEXT_PUBLIC_WEB_URL (desktop fallbacks now api./app.${root})unchanged
Desktop streamsNEXT_PUBLIC_STREAMS_URL (fallback now streams.${root})https://streams.superset.sh
CLISUPERSET_API_URL, SUPERSET_WEB_URL, NEXT_PUBLIC_ROOT_DOMAIN (baked at build)unchanged
Relay / relay backup / realtimeRELAY_URL, RELAY_BACKUP_URL, REALTIME_URL / NEXT_PUBLIC_REALTIME_URLstay on superset.sh
Usercontent / sandbox gateUSERCONTENT_URL, SANDBOX_GATE_ORIGINstay on supersetusercontent.com
Transactional senderEMAIL_SENDING_DOMAIN (new)superset.sh

Why the sending domain is separate

A new sending domain has no reputation. It needs weeks of gradual volume before inbox providers trust it. If the sender followed NEXT_PUBLIC_ROOT_DOMAIN, flipping the root domain would move all transactional mail (sign-in, invitations, billing) to a cold domain overnight. Keeping it on its own variable lets mail stay on the warmed-up domain until warm-up is done.

New env var checklist (docs/environment-variables.md):

  • EMAIL_SENDING_DOMAIN has a schema in packages/email/src/lib/env.ts with a real default.
  • NEXT_PUBLIC_ROOT_DOMAIN, EMAIL_SENDING_DOMAIN and RELAY_BACKUP_URL are in .env.example.
  • .env.local.example gets empty values for the first two, so they stay unset locally.
  • The deploy workflows get both the env: entry and --env.
  • No secrets need setting now: unset means today's behaviour.

Intentionally out of scope

  • Cloudflare wrangler.jsonc routes and vars (relay, relay2, realtime, usercontent, gate): these stay on superset.sh.
  • packages/sdk/src/client.ts published defaults, and packages/mcp/server.json (registry manifest).
  • Slack manifest.json (static, applied by hand). apps/mobile/store.config.js, podspecs, stories.
  • Demo, tape and shell scripts (review-host, cli/scripts, realtime/scripts, relay/scripts/e2e-probe.ts, email/scripts/sync-automations.ts).
  • Brand name, deep-link schemes, iOS bundle IDs, social and GitHub URLs.
  • Plugin catalog MCP URL and SUPERSET_API_URL in packages/shared/src/plugins/index.ts. These match against generated, published plugin manifests, so changing them needs a manifest republish.
  • cdn.superset.sh sandbox asset base: asset infrastructure, not a product host.
  • Commit author identities (noreply@superset.sh in sandbox claim/release), personal addresses, and the apps/desktop/package.json author email.
  • Growth analytics host filters, sitemap source, and Search Console property (packages/trpc/src/lib/growth, GSC_SITE_URL default). These describe historical traffic data.
  • Copy that names a domain in marketing content: contact page, the SOC 2 FAQ answers, admin growth descriptions, web design showcase. They are content, like MDX, and need an editorial pass anyway.
  • packages/cli-framework pricing hint: the framework has no @superset/shared dependency.
  • Discord triage bot footer and fly.toml vars.
  • Comments, tests, the desktop dev-only preview notices, and seed data.

Known gap: the mobile bundle only inlines EXPO_PUBLIC_*, so it does not receive NEXT_PUBLIC_ROOT_DOMAIN. Its COMPANY/CANONICAL_URLS values stay on the default until the root domain is wired into mobile (EXPO_PUBLIC_WEB_URL etc. already work).

Verification

  • bun run check:i18n: passes. Only the two re-keyed messages changed, and all locales are filled.
  • bun run lint:fix: clean; it only reformatted files in this diff.
  • bun run typecheck on shared, email, auth, trpc, i18n, cli, api, web, marketing, docs, desktop, mobile:
    • shared, email, i18n and docs pass.
    • The others fail with better-auth plugin type-inference errors: addMember, createOrganization, activeOrganizationId, plan and similar missing from inferred auth types.
    • The same errors happen with these changes reverted (packages/auth: 5 errors before and after), so this is local install drift, not this change.
    • None of the errors point at a line this PR changes.
  • bun test passed, 0 failures:
    • packages: shared (1311), email (3), i18n (165), cli (374), auth (8), trpc user/connectors (59)
    • apps: api slack/openapi/mcp/lib (163), marketing src (158), web src (55), desktop chrome-cookie-import (24)
  • Not run end to end in a real app.

Summary by cubic

Makes the root domain configurable via env vars so a rebrand needs no code changes; with no new vars set, every derived URL matches today's superset.sh defaults.

  • Adds NEXT_PUBLIC_ROOT_DOMAIN, EXPO_PUBLIC_ROOT_DOMAIN, EMAIL_SENDING_DOMAIN, and COMPANY.SUPPORT_EMAIL; api, docs, marketing, desktop, web, mobile, cli, auth, and email templates now derive hosts and support addresses from them.
  • Keeps the sending domain on its own variable so transactional mail stays on the warmed-up domain after a root-domain flip; the CLI and desktop builds bake NEXT_PUBLIC_ROOT_DOMAIN in, and mobile reads EXPO_PUBLIC_ROOT_DOMAIN.
  • Desktop cookie import protects both the configured root domain and the legacy superset.sh domain so old session cookies stay unimportable.
  • Two i18n messages take the support address as a placeholder, with a support@superset.sh fallback for old API responses; translations carried over in all 16 locales.

Summary by CodeRabbit

  • Improvements
    • Links across the app, emails, documentation, and integrations now use consistent, centrally configured website addresses.
    • Support contact details and outgoing email sender addresses can be configured for each deployment.
    • Error messages display the configured support contact across supported languages.
    • Desktop, CLI, and mobile apps, along with deployment builds, can use a configurable root domain while preserving existing defaults.
    • Workspace sharing links and published-page addresses also follow the configured app and company domains.