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)
|
||||
- [`documenso-integration-audit.md`](./documenso-integration-audit.md) — Documenso integration spec (drives §A)
|
||||
- [`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
|
||||
`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
|
||||
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`,
|
||||
`proxy_read_timeout 300`, buffering off. **HTTP-only first** (no `ssl\__`
|
||||
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
|
||||
at `private/documenso-dryrun/docker-compose.yml` for a re-run. Prod
|
||||
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
|
||||
on Init 1b (website UTM/inquiry cutover) and Init 1c (invoices-module
|
||||
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