# Zoho Form → Domain Config — Design

## Goal

Allow customer service reps to configure new customer domains for the SMS
directions and click-to-call services by submitting a Zoho Form, without
needing server access or manual file edits.

## Architecture

Add one new route `POST /update-domain` to the existing Flask app
(`webresponder/app.py`). Zoho Forms POSTs to it on submission. Flask validates
a shared secret, merges the payload into `domains.json` (upsert by domain
key), and writes the file back atomically. Since `domains.json` is re-read on
every request, no service restart is needed — the new config is live
immediately.

## Zoho Form fields

One form covering both services:

| Field | Label shown to CSR | Required |
|-------|--------------------|----------|
| `domain` | Customer Domain (e.g. acme.com) | ✓ |
| `api_domain` | NS API Domain | ✓ |
| `user` | NS User/Extension | ✓ |
| `from_number` | SMS From Number (digits only, no +) | ✓ |
| `message` | SMS Message Text | ✓ |
| `queue` | Call Queue (format: EXT@DOMAIN) | ✓ |
| `button_text` | Call Button Label | ✓ |
| `button_color` | Button Color (hex, e.g. #1a73e8) | ✓ |
| `company_name` | Company Name | ✓ |
| `logo_url` | Logo URL | ✗ |
| `update_secret` | Hidden/prepopulated — not shown to CSR | ✓ |

`update_secret` is configured as a hidden pre-filled field in Zoho Forms so
CSRs never see or touch it.

If a domain already exists in `domains.json`, the submission overwrites it
(upsert). CSRs can update a domain's config by resubmitting the form with
corrected values.

## `POST /update-domain` endpoint

1. Accept form-encoded body (Zoho Forms POSTs as `application/x-www-form-urlencoded`)
2. Check `update_secret` field matches `UPDATE_SECRET` env var → 403 if not
3. Validate required fields are present and `domain` matches the existing
   `_DOMAIN_RE` pattern → 400 with list of missing/invalid fields if not
4. Load `domains.json`, upsert the domain entry, write back atomically:
   write to a `.tmp` file then `os.replace()` — prevents corruption if two
   requests arrive simultaneously
5. Return `{"status": "ok", "domain": "<domain>"}` on success, or
   `{"status": "error", "message": "..."}` on failure
6. Log every attempt (success, auth failure, validation error, write error)

## `.env` addition

```
UPDATE_SECRET=<random string, generate once with: python3 -c "import secrets; print(secrets.token_hex(32))">
```

Same `.env` file on the server — no new config files needed.

## Error handling & testing

- Wrong/missing secret → 403
- Missing required fields → 400 with field list
- Invalid domain format → 400
- File write failure → 500
- Tests mirror existing `tests/test_app.py` pattern: mock file I/O, cover
  happy path + each error branch
