# Custom Domain Settings UI

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 137  
**Tier:** Enterprise  
**Depends on:** `white-label-api`, `settings-module`, `foundation-auth-rbac`  
**Referenced by:** `white-label-api`, `settings-module`

---

## Overview

Spec 27 (`white-label-api`) defines the `tenant_domains` table, the CF custom hostname provisioning flow, and the `/api/settings/domains` endpoints, but mentions the UI only in one sentence: "Tenant enters domain in `/settings/white-label`." This spec defines the full UI for that page.

---

## Route

`/settings/white-label` — Enterprise tier only. Non-Enterprise tenants see an upsell banner instead of the form.

---

## Page Layout

```
┌──────────────────────────────────────────────────────────────┐
│  Settings > White-Label                                      │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │  Custom Portal Domain                                    │ │
│  │                                                          │ │
│  │  Your customer portal is available at:                   │ │
│  │  zync.is/portal/acme-co                                  │ │
│  │                                                          │ │
│  │  Map your own domain (e.g. portal.acme.com) so clients   │ │
│  │  see your brand, not Zync.                               │ │
│  │                                                          │ │
│  │  Domain    [portal.acme.com_________________________]    │ │
│  │            Only portal subdomain mapping supported.      │ │
│  │                                                          │ │
│  │  [Add domain]                                            │ │
│  └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```

---

## After Adding a Domain — DNS Instructions State

```
┌──────────────────────────────────────────────────────────────┐
│  Custom Portal Domain                                        │
│                                                              │
│  portal.acme.com             ⏳ Pending DNS verification     │
│                                                 [Remove]     │
│                                                              │
│  Add this CNAME record at your DNS provider:                 │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │  Type   Host              Points to                      │ │
│  │  CNAME  portal.acme.com   portal.zync.is                 │ │
│  └─────────────────────────────────────────────────────────┘ │
│                                                              │
│  [Copy CNAME value]  [I've added the record — check now]     │
│                                                              │
│  Verification checks run every 15 minutes. TLS certificate   │
│  is provisioned automatically after DNS resolves (may take   │
│  up to 48 hours for DNS propagation).                        │
└──────────────────────────────────────────────────────────────┘
```

**[I've added the record — check now]** → `POST /api/settings/domains/:id/verify` → triggers immediate check (rate-limited: once per 2 minutes).

---

## Verified / Active State

```
┌──────────────────────────────────────────────────────────────┐
│  Custom Portal Domain                                        │
│                                                              │
│  portal.acme.com             ✓ Active                        │
│                                 TLS: Valid · Expires 2027-05 │
│                                                 [Remove]     │
│                                                              │
│  Your portal is live at portal.acme.com                      │
│  [Open portal →]                                             │
└──────────────────────────────────────────────────────────────┘
```

---

## Error State

```
┌──────────────────────────────────────────────────────────────┐
│  portal.acme.com             ✗ Error                         │
│                                                [Remove]      │
│                                                              │
│  DNS verification failed after 48 hours.                     │
│  CNAME not found: portal.acme.com                            │
│                                                              │
│  Common causes:                                              │
│  · CNAME record not added yet                                │
│  · DNS propagation still in progress                         │
│  · Typo in host name                                         │
│                                                              │
│  [Show DNS instructions]  [Retry verification]               │
└──────────────────────────────────────────────────────────────┘
```

---

## Remove Domain

**[Remove]** → confirmation:

```
Remove portal.acme.com?
Your portal will revert to zync.is/portal/acme-co.
Clients who bookmarked portal.acme.com will get a 404.

[Cancel]    [Remove domain]
```

On confirm: `DELETE /api/settings/domains/:id` (spec 27 — unprovisions CF custom hostname, deletes row).

---

## Non-Enterprise Upsell

For non-Enterprise tenants, the whole page shows:

```
┌──────────────────────────────────────────────────────────────┐
│  Custom Domain — Enterprise feature                          │
│                                                              │
│  Map your own domain (e.g. portal.acme.com) to your client   │
│  portal. Requires Enterprise plan.                           │
│                                                              │
│  [Upgrade to Enterprise →]                                   │
└──────────────────────────────────────────────────────────────┘
```

---

## Outbound Email Whitelabel

Below the domain section, an outbound email section (shown for Enterprise only):

```
┌──────────────────────────────────────────────────────────────┐
│  Outbound Email                                              │
│                                                              │
│  Send from your own domain instead of zync.is.              │
│                                                              │
│  [Configure SMTP settings →]                                 │
│  (opens /settings/integrations/smtp — spec 51)              │
└──────────────────────────────────────────────────────────────┘
```

---

## API

Spec 27 endpoints used:

```
GET /api/settings/domains
    → list tenant's custom domains
      Returns: [{ id, domain, status, verified_at, error_message, cloudflare_hostname_id }]
      Requires: admin

POST /api/settings/domains
     → add new custom domain (spec 27)
       body: { domain: string }
       Returns: { id, domain, status: 'pending', cname_target: 'portal.zync.is' }
       Requires: admin

DELETE /api/settings/domains/:id
       → remove domain + unprovision CF hostname (spec 27)
         Requires: admin

-- New endpoint (not in spec 27):
POST /api/settings/domains/:id/verify
     → trigger immediate DNS check (rate-limited 1/2min)
       Requires: admin
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| One domain per tenant | Not multiple | Enterprise portal has one URL; multiple custom domains add routing complexity without use case |
| Immediate verify trigger | Optional button + auto-poll | Users who just added DNS want instant feedback; auto-poll catches cases where they forget to click |
| Error after 48h | Not indefinite pending | DNS usually propagates in hours; 48h failure is almost certainly a configuration error, not propagation delay |
