docs(launch): website-integration env vars + cutover sequence
deployment-plan.md gains a full env-var reference (CRM + website) and the cutover env-flip sequence; launch-readiness.md gets the 2026-06-02 closeout; BACKLOG.md adds the deferred integration-health-panel idea (section L). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -435,3 +435,17 @@ not surfaced in §C above were resolved via the `fix(audit): …` commits
|
|||||||
- [`berth-recommender-and-pdf-plan.md`](./berth-recommender-and-pdf-plan.md) — berth recommender + per-berth PDF plan (Phases 0–8 shipped)
|
- [`berth-recommender-and-pdf-plan.md`](./berth-recommender-and-pdf-plan.md) — berth recommender + per-berth PDF plan (Phases 0–8 shipped)
|
||||||
- [`documenso-integration-audit.md`](./documenso-integration-audit.md) — Documenso integration spec (drives §A)
|
- [`documenso-integration-audit.md`](./documenso-integration-audit.md) — Documenso integration spec (drives §A)
|
||||||
- [`website-refactor.md`](./website-refactor.md) — public website cutover plan
|
- [`website-refactor.md`](./website-refactor.md) — public website cutover plan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## L. Website "integration health" panel (post-launch idea)
|
||||||
|
|
||||||
|
**Raised 2026-06-02 (Matt); verdict: NOT worth building pre-launch.**
|
||||||
|
|
||||||
|
A CRM admin "control panel" to watch website log events / activity. Reasons to defer:
|
||||||
|
|
||||||
|
- Inquiry visibility already exists: `website_submissions` + its triage inbox (open / assigned / converted / dismissed).
|
||||||
|
- Website traffic/events are already covered by Umami; app + error logs belong in pino/server logs (or a log tool), not a hand-rolled CRM page. Shipping website logs into the CRM is net-new bidirectional plumbing plus a security surface for marginal gain.
|
||||||
|
- Not launch-blocking; classic pre-launch scope creep.
|
||||||
|
|
||||||
|
**If revisited post-launch:** scope it to ONE small "integration health" card on the admin dashboard — last website submission received, count today, intake 4xx/5xx rate, inquiry email-send success. Pure read, no new ingestion pipeline. Build only if a real operational need surfaces; during cutover this is observable via logs + the submissions inbox.
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ nginx vhost exists yet (fresh setup). Template: `portnimara_dev.conf`
|
|||||||
1. Create `/etc/nginx/sites-available/crm_portnimara.conf` modelled on
|
1. Create `/etc/nginx/sites-available/crm_portnimara.conf` modelled on
|
||||||
`portnimara_dev.conf`: port-80 → 443 redirect + `.well-known/acme-challenge`
|
`portnimara_dev.conf`: port-80 → 443 redirect + `.well-known/acme-challenge`
|
||||||
location; port-443 server `proxy_pass http://127.0.0.1:7100` with the same
|
location; port-443 server `proxy_pass http://127.0.0.1:7100` with the same
|
||||||
header block (Host, X-Real-IP, CF-Connecting-IP, X-Forwarded-_, websocket
|
header block (Host, X-Real-IP, CF-Connecting-IP, X-Forwarded-\_, websocket
|
||||||
`Upgrade`/`Connection` for socket.io), `client_max_body_size 64M`,
|
`Upgrade`/`Connection` for socket.io), `client_max_body_size 64M`,
|
||||||
`proxy_read_timeout 300`, buffering off. **HTTP-only first** (no `ssl\__`
|
`proxy_read_timeout 300`, buffering off. **HTTP-only first** (no `ssl\__`
|
||||||
lines yet) so Certbot can complete the challenge.
|
lines yet) so Certbot can complete the challenge.
|
||||||
@@ -236,3 +236,74 @@ successfully applied`, 140→157, none unfinished), app boots (home 302,
|
|||||||
removed; restored clone gone, off-box dump retained). Compose file kept
|
removed; restored clone gone, off-box dump retained). Compose file kept
|
||||||
at `private/documenso-dryrun/docker-compose.yml` for a re-run. Prod
|
at `private/documenso-dryrun/docker-compose.yml` for a re-run. Prod
|
||||||
still untouched.
|
still untouched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment variables — initial deployment + cutover
|
||||||
|
|
||||||
|
> Single source of truth for the env each instance needs for the
|
||||||
|
> website<->CRM integration (added 2026-06-02). **Every website-side CRM
|
||||||
|
> var is a no-op when unset**, so the marketing site behaves exactly as
|
||||||
|
> today until these are filled at cutover. Full CRM schema: `src/lib/env.ts`.
|
||||||
|
|
||||||
|
### CRM instance (`crm.portnimara.com`)
|
||||||
|
|
||||||
|
| Var | Value | Notes |
|
||||||
|
| ------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `APP_URL` | `https://crm.portnimara.com` | Absolute URLs + email links (the inquiry sales-alert "Open in CRM" button). |
|
||||||
|
| `WEBSITE_INTAKE_SECRET` | shared secret | **MUST equal** the website's `CRM_INTAKE_SECRET`. If unset, `/api/public/website-inquiries` returns **503** and refuses all intake. |
|
||||||
|
| `EMAIL_REDIRECT_TO` | **unset in prod** | Dev-only reroute; the prod build guard fails if it is set. |
|
||||||
|
| `DATABASE_URL`, `REDIS_*`, storage/MinIO, `DOCUMENSO_*`, `SMTP_*`, better-auth secret | per `.env` | Standard (see Phase 1 Pre-flight). |
|
||||||
|
|
||||||
|
Per-port **settings** (stored in `system_settings`, set via Admin UI — NOT env):
|
||||||
|
|
||||||
|
- `website_intake_email_enabled` — boolean, **default OFF**. Flip ON at
|
||||||
|
cutover so the CRM sends the registrant confirmation + staff alert for
|
||||||
|
website inquiries (berth / residence / contact), reusing the branded
|
||||||
|
templates + per-port From. Keep OFF until the website's own sending is
|
||||||
|
turned off (see `WEBSITE_INQUIRY_EMAILS_DISABLED`) to avoid double-sends.
|
||||||
|
- `inquiry_notification_recipients` (JSON string[]) — staff who receive
|
||||||
|
berth + contact-form inquiry alerts.
|
||||||
|
- `residential_notification_recipients` (JSON string[]) — staff who receive
|
||||||
|
residence inquiry alerts.
|
||||||
|
- `inquiry_contact_email` (string) — fallback alert recipient + reply-to.
|
||||||
|
|
||||||
|
### Website instance (Nuxt marketing site — repo `ron/website.git`)
|
||||||
|
|
||||||
|
New vars for the CRM integration (read via `process.env` in Nitro;
|
||||||
|
**all no-op when unset → site unchanged**):
|
||||||
|
|
||||||
|
| Var | Value | Enables | Set when |
|
||||||
|
| --------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| `CRM_INTAKE_URL` | `https://crm.portnimara.com` (bare host, no trailing slash) | Inquiry dual-write delivery + base URL for the berth feed | Cutover (safe earlier; just starts populating `website_submissions`) |
|
||||||
|
| `CRM_INTAKE_SECRET` | shared secret | Auth for the dual-write (`X-Webhook-Secret`); **MUST equal** CRM `WEBSITE_INTAKE_SECRET` | With `CRM_INTAKE_URL` |
|
||||||
|
| `CRM_BERTHS_ENABLED` | `1` (or `true`/`yes`) | Switches the public berth map/list to read from CRM `/api/public/berths` instead of NocoDB (requires `CRM_INTAKE_URL`) | Cutover, after CRM berth data is migrated + verified |
|
||||||
|
| `WEBSITE_INQUIRY_EMAILS_DISABLED` | `1` | Turns OFF the website's own Gmail confirmation + alert emails, handing email ownership to the CRM | Cutover, flipped **together** with CRM `website_intake_email_enabled = ON` |
|
||||||
|
|
||||||
|
UTM: **no env var** — cookieless; the client plugin reads `utm_*` from the
|
||||||
|
landing URL and forwards them via an `x-utm` header.
|
||||||
|
|
||||||
|
Existing website env (keep, unchanged): NocoDB url/token, SMTP user/pass,
|
||||||
|
`alertRecipientsBerths/Residences/Contact`, `RECAPTCHA_SECRET`,
|
||||||
|
`NUXT_PUBLIC_RECAPTCHA_SITE_KEY`, Directus url. NocoDB stays as the berth
|
||||||
|
fallback + the dual-write's primary target until the old system is retired;
|
||||||
|
SMTP + alert recipients stay until `WEBSITE_INQUIRY_EMAILS_DISABLED` is set.
|
||||||
|
|
||||||
|
### Cutover env-flip sequence (website)
|
||||||
|
|
||||||
|
1. Confirm CRM is up, berth data migrated, and `WEBSITE_INTAKE_SECRET` set on the CRM.
|
||||||
|
2. Set website `CRM_INTAKE_URL` + `CRM_INTAKE_SECRET` → verify a test inquiry lands in `website_submissions`.
|
||||||
|
3. Flip CRM `website_intake_email_enabled = ON` **and** website `WEBSITE_INQUIRY_EMAILS_DISABLED = 1` together → CRM is the single email owner.
|
||||||
|
4. Set website `CRM_BERTHS_ENABLED = 1` → public map reads from the CRM.
|
||||||
|
5. Watch errors; rollback = unset the website vars (instant revert to NocoDB + website email).
|
||||||
|
|
||||||
|
## Progress log (cont.)
|
||||||
|
|
||||||
|
- 2026-06-02: **Website integration prep (local only; no prod changes, nothing pushed).**
|
||||||
|
Website repo (`main`, uncommitted): env-gated berth feed (`CRM_BERTHS_ENABLED`),
|
||||||
|
cookieless UTM forwarding (no env), inquiry dual-write (pre-existing). Website
|
||||||
|
email kill-switch added (`WEBSITE_INQUIRY_EMAILS_DISABLED`). CRM repo: flag-gated
|
||||||
|
email ownership (`website_intake_email_enabled`, default OFF) reusing the inquiry
|
||||||
|
- residential templates plus a new contact-form alert template, hooked into
|
||||||
|
`/api/public/website-inquiries`. New website env vars documented above. CRM
|
||||||
|
tsc-clean + unit test added; website berth/UTM vue-tsc-clean. Nothing deployed.
|
||||||
|
|||||||
@@ -644,3 +644,75 @@ unproven full builds onto a same-day prod launch.
|
|||||||
- **Marketing + Financial reports** — remain unbuilt + now hidden; gated
|
- **Marketing + Financial reports** — remain unbuilt + now hidden; gated
|
||||||
on Init 1b (website UTM/inquiry cutover) and Init 1c (invoices-module
|
on Init 1b (website UTM/inquiry cutover) and Init 1c (invoices-module
|
||||||
decision) respectively.
|
decision) respectively.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-06-02 (session) — Pre-deployment closeout
|
||||||
|
|
||||||
|
Driving toward cutover. Decisions + state captured this session.
|
||||||
|
|
||||||
|
### Decisions locked
|
||||||
|
|
||||||
|
- **Email ownership = the new CRM, flag-gated (option A).** Registrant
|
||||||
|
confirmations + staff alerts move from the website to the CRM. The CRM
|
||||||
|
intake path sends them behind a per-port flag (default OFF); the website
|
||||||
|
keeps sending until cutover, then flips off in one coordinated step (no
|
||||||
|
gap, no double-send). Preserve asymmetry: berth/residence = confirmation
|
||||||
|
- alert; contact form = alert only. Prove end-to-end before the flip.
|
||||||
|
Cross-ref: `project_email_ownership_at_cutover` memory.
|
||||||
|
- **`feature/video-headers` is excluded from this launch** (per Matt) — a
|
||||||
|
separate workstream, do not touch. Launch base = `main`.
|
||||||
|
- **Berth-read swap is a cutover-time flip, not now.** Until cutover NocoDB
|
||||||
|
is the live source staff update; the public map must mirror it.
|
||||||
|
- **UTM capture is cookieless** (no consent banner exists). In-memory only;
|
||||||
|
no cookie / localStorage / sessionStorage (ePrivacy/PECR). Session-scoped.
|
||||||
|
|
||||||
|
### Marketing website (repo: code.portnimara.com/ron/website.git, branch `main`)
|
||||||
|
|
||||||
|
Local `main` was 1 unpushed commit ahead (the CRM dual-write) + an uncommitted
|
||||||
|
Umami refactor; nothing to pull from the remote. Reconciled:
|
||||||
|
|
||||||
|
- Committed the Umami refactor (`7e111b3`, `d03fcee`) to set a clean base.
|
||||||
|
`main` is NOT yet pushed (unpushed since 2026-05-04).
|
||||||
|
- **Inquiry dual-write**: pre-existing, dormant (no-ops without env). Contract
|
||||||
|
verified matching CRM `/api/public/website-inquiries` (header, payload,
|
||||||
|
`kind` enum, idempotent UUID).
|
||||||
|
- **Berth-read swap**: SHIPPED locally, default-OFF. `server/utils/berths.ts`
|
||||||
|
reads CRM `/api/public/berths` (+ single) when `CRM_BERTHS_ENABLED` truthy
|
||||||
|
and `CRM_INTAKE_URL` set; else NocoDB. `register.ts` skips the NocoDB
|
||||||
|
interest->berth link in CRM mode (UUID ids; CRM links via dual-write).
|
||||||
|
`PublicBerth` confirmed a verbatim superset of the website `Berth` type
|
||||||
|
incl. `Map Data` {path,x,y,transform,fontSize} -> the CRM fully backs the
|
||||||
|
website berth map. vue-tsc: type-clean.
|
||||||
|
- **UTM forwarding**: SHIPPED locally, cookieless. `plugins/utm.client.ts`
|
||||||
|
reads utm\_\* from the landing URL into memory and adds an `x-utm` header on
|
||||||
|
/api/register + /api/contact POSTs; `server/utils/utm.ts#readUtm` parses it;
|
||||||
|
`crmIntake.ts` forwards the five fields (CRM schema already accepts them).
|
||||||
|
Zero form-component changes; a misfire degrades to null UTM (no breakage).
|
||||||
|
vue-tsc: type-clean.
|
||||||
|
- **Uncommitted**: the berth-swap + UTM changes are in the working tree, not
|
||||||
|
yet committed (awaiting go).
|
||||||
|
- Pre-existing bugs noted (not fixed): `pages/berths/[mooringNumber].vue:123`
|
||||||
|
uses `.MooringNumber` (should be `["Mooring Number"]`);
|
||||||
|
`berths-item/introduction.vue:32` mis-indexes `"Water Depth"`. The website
|
||||||
|
repo has no typecheck/lint step (recommend wiring one pre-deploy).
|
||||||
|
|
||||||
|
### Env flips required at cutover (website)
|
||||||
|
|
||||||
|
- `CRM_INTAKE_URL` + `CRM_INTAKE_SECRET` (turn on inquiry dual-write delivery)
|
||||||
|
- `CRM_BERTHS_ENABLED=1` (switch the public berth map/list to the CRM)
|
||||||
|
- website email sending OFF + CRM email flag ON (single owner)
|
||||||
|
|
||||||
|
### Closeout sequence
|
||||||
|
|
||||||
|
1. Local website (no prod risk): berth swap DONE, UTM DONE.
|
||||||
|
2. CRM-side email ownership build (flag-gated) + website email-off toggle;
|
||||||
|
prove end-to-end. NEXT.
|
||||||
|
3. [GATED] Documenso v1.13.1 -> v2.11.0 prod upgrade (dry-run passed
|
||||||
|
2026-06-01; Phases A-E sober/scheduled, per-step approval).
|
||||||
|
4. [GATED] Prod CRM deploy (Phase 1: nginx/certbot/compose/.env). Inputs
|
||||||
|
needed: Postgres own/shared, deploy dir, registry token, Documenso API
|
||||||
|
token.
|
||||||
|
5. [GATED] Data migration + cutover (MinIO EOI backfill, final NocoDB
|
||||||
|
reconcile, freeze + website env flips) in a maintenance window, explicit go.
|
||||||
|
6. Post-launch: M23/M25 migrations; e2e in CI; website typecheck/lint.
|
||||||
|
|||||||
Reference in New Issue
Block a user