SEO Readiness Portal — Self-Serve Intake (Architecture + Operator Runbook)

SEO Readiness Portal — Self-Serve Intake (Architecture + Operator Runbook)

Shipped: 2026-06-04
Image: alairegistry.azurecr.io/seo-readiness-portal:20260604-selfserve-intake
Validation: MC #102923 Proveo PASS — /tmp/alai/seo-wsv-evidence/VALIDATION-REPORT.md
Deploy Evidence: /tmp/alai/seo-deploy-102929/FLOWFORGE-DEPLOY-REPORT.md


Overview

The self-serve intake feature enables Asmir (SnowIT sales) to send zero-effort referrals to potential SEO clients. Asmir creates a client record in the SEO Portal /partners workspace, the system automatically sends a branded magic-link email, and the client fills out a web-based intake form that triggers an automated SEO audit pipeline — all without Asmir lifting a finger after the initial client creation.

Asmir Zero-Effort Workflow

  1. Create client: Asmir visits https://seo-tools.snowit.ba/partners/clients/new, enters client name + email.
  2. System auto-sends: Intake magic-link email sent immediately (SnowIT-branded, subject "SEO Readiness Assessment for {clientName}").
  3. Client completes intake: Client clicks link → fills web form (or chats with AI assistant) → submits.
  4. Auto-pipeline runs: Live SEO audit + findings + draft report generated automatically.
  5. Asmir reviews: Asmir sees client status change from "link_sent" → "intake_submitted" → "report_ready" in /partners/referrals dashboard.

Total Asmir effort: Type name + email. Done.


Feature Scope (WS-A/B/C)

Deliverable: HMAC email-bound re-usable intake token + public /intake/[token] route + SnowIT-branded link email on client create.

Evidence: /tmp/alai/seo-ws-a-evidence/
Validation: Playwright 10/10 PASS, validate-magic-link.ts 33/33 PASS.


WS-B: SnowIT White-Label AI Chatbot

Deliverable: SnowIT-branded AI assistant on intake page (Ollama-first tier-router, rate-limited, secret-guarded, forbidden-claim-guarded).

Evidence: /tmp/alai/seo-ws-b-agentforge-evidence/implementation-summary.md
Validation: validate-intake-chat.ts 5/5 PASS, Ollama live turn JSON verified.


WS-C: Auto-Pipeline on Intake Submit

Deliverable: Intake submit triggers audit → findings → draft report (GBP fallback for no-website clients).

Evidence: /tmp/alai/seo-ws-c-evidence/validate-referral-pipeline.txt
Validation: validate-referral-pipeline.ts 32/32 PASS.


Cloudflare Access Carve-Out (Defense-in-Depth)

Problem: SEO Portal is protected by Cloudflare Access (trusted-header SSO). Public intake links (/intake/[token]) must be accessible to unauthenticated clients, but the rest of the app (/partners, /api/health) must remain gated.

Solution: Created a SEPARATE Cloudflare Access application with a BYPASS policy for /intake/* paths ONLY. The edge lets /intake/* through, and the app's own token gate (verifyIntakeToken()) protects the intake pages (defense-in-depth: CF bypass + app token gate).

CF Access App

Bypass Policy

Verification (Live Prod)

Path Expected Result
/intake/bogus-token 200 "Link not valid" (app served, no CF redirect) ✅ PASS
/api/intake-chat 401 from app (origin reached, not CF 302) ✅ PASS
/partners 302 to CF Access login (still gated) ✅ PASS
/api/health 302 to CF Access login (not in bypass) ✅ PASS

Key signal: Header x-seo-portal-access: blocked proved request hit the app, confirming CF Access carve-out working.

CRITICAL DEPLOY STEP: This CF Access carve-out is MANDATORY for the self-serve intake feature. Without it, all /intake/[token] requests are redirected to CF Access login instead of reaching the app's token-gated intake page. The first cutover attempt FAILED because this step was missing. Deploy runbook now documents this as a hard gate.

Evidence: /tmp/alai/seo-deploy-102929/FLOWFORGE-DEPLOY-REPORT.md (Part 1: CF Access carve-out creation + edge verification).


Azure App Service Deploy

Deploy Target

Required Environment Variables

Post-Deploy Verification

Check Expected Result
Image tag 20260604-selfserve-intake ✅ PASS
App state Running ✅ PASS
/intake/bogus → 200 App served (no CF redirect) ✅ PASS
/partners → 302 CF Access login ✅ PASS

Evidence: /tmp/alai/seo-deploy-102929/FLOWFORGE-DEPLOY-REPORT.md (Part 2: deploy + ZAKON PI2 post-deploy verification).


Known Gaps

  1. SnowIT sender mailbox not provisioned: Magic-link emails currently sent via --account alai sender (alai.no). Body is SnowIT-branded; sender is alai.no. Follow-on MC to provision Asmir@snowit.ba sender mailbox.
  2. WS-B4 chat pre-seed from live crawl: Deferred. Chat starts fresh without crawl data. Follow-on MC can add: "if website provided, call runLiveCrawlAudit() and seed assistant context with detected title/description/services."
  3. Rate-limiter resets on container restart: Rate limiter state is in-process Map; resets on Azure container restart. Acceptable for MVP; Postgres migration follow-on.
  4. CF token rotation: MC #102790 paused. The global CF API key was used for this deploy (scoped token was invalid). Rotation task should be resumed.

Operator Runbook

Create Client + Send Magic Link

  1. Visit https://seo-tools.snowit.ba/partners/clients/new (Asmir's Cloudflare Access account).
  2. Enter client name + email.
  3. Submit → magic-link email sent automatically.
  4. Client receives: "SEO Readiness Assessment for {clientName}" with link to https://seo-tools.snowit.ba/intake/{token}.

Monitor Referral Status

  1. Visit https://seo-tools.snowit.ba/partners/referrals.
  2. Client status derived from data:
    • link_sent → email sent, awaiting client action.
    • intake_submitted → client submitted form/chat.
    • report_ready → audit + report draft generated.

Possible causes:

  1. Forged token: Token signature invalid (client manually edited URL). Expected behavior: 200 error page, no data leak.
  2. Expired token: Token expiresAt past (default 30 days). Expected behavior: 200 "Link expired" error page.
  3. INTAKE_TOKEN_SECRET mismatch: Azure env var changed after token was minted. Fix: Re-send magic link from /partners/clients/{clientId} (generates new token with current secret).

Debug command:

az webapp config appsettings list -g rg-seo-readiness-prod -n seo-readiness-alai --query "[?name=='INTAKE_TOKEN_SECRET'].value" -o tsv

If secret changed → re-send magic link from portal.

Troubleshoot Rate Limit (429)

Symptom: Client sees "Too many requests" after 10 chat messages in 60 seconds.

Expected behavior: Rate limiter prevents abuse on public /api/intake-chat route.

Workaround: Wait 60 seconds for sliding window to reset.

Long-term fix: Postgres-backed rate limiter (survives container restarts). Follow-on MC.

Re-Deploy Self-Serve Intake

Pre-flight:

cd /Users/makinja/business/ALAI-Holding-AS/products/SEO-Readiness-Portal
npm run type-check && npm run build && npm run validate:spec

Build image:

az acr build -r alairegistry -t seo-readiness-portal:YYYYMMDD-tag-name .

Deploy:

az webapp config container set \
  --resource-group rg-seo-readiness-prod \
  --name seo-readiness-alai \
  --container-image-name alairegistry.azurecr.io/seo-readiness-portal:YYYYMMDD-tag-name \
  --container-registry-url https://alairegistry.azurecr.io
az webapp restart --resource-group rg-seo-readiness-prod --name seo-readiness-alai

Post-deploy verify (ZAKON PI2):

curl -sI https://seo-tools.snowit.ba/intake/bogus | grep -E "HTTP|content-type"
# Expect: HTTP/2 200, content-type: text/html; charset=utf-8

curl -sI https://seo-tools.snowit.ba/partners | grep -E "HTTP|location"
# Expect: HTTP/2 302, location: https://<CF-Access-login-url>

Rollback:

az webapp config container set \
  --resource-group rg-seo-readiness-prod \
  --name seo-readiness-alai \
  --container-image-name alairegistry.azurecr.io/seo-readiness-portal:20260602-real-audit \
  --container-registry-url https://alairegistry.azurecr.io
az webapp restart --resource-group rg-seo-readiness-prod --name seo-readiness-alai

Previous known-good: 20260602-real-audit. Do NOT open origin IP-lock. Do NOT touch any Bilko domain.


Evidence Ledger

Artifact Location SHA-256
Validation report (Proveo PASS) /tmp/alai/seo-wsv-evidence/VALIDATION-REPORT.md 72e79a31...
Playwright results (10/10 PASS) /tmp/alai/seo-wsv-evidence/playwright-results.json 6d35586e...
WS-A evidence /tmp/alai/seo-ws-a-evidence/
WS-B evidence /tmp/alai/seo-ws-b-agentforge-evidence/
WS-C evidence /tmp/alai/seo-ws-c-evidence/
Deploy report (FlowForge) /tmp/alai/seo-deploy-102929/FLOWFORGE-DEPLOY-REPORT.md
Live probes /tmp/alai/seo-wsv-evidence/live-probes.txt 4e03b886...
Intake page HTML (SSR) /tmp/alai/seo-wsv-evidence/intake-page-content.html 033ad52f...
Screenshots (AC1-AC10) /tmp/alai/seo-wsv-evidence/screenshots/

Status: LIVE in production as of 2026-06-04.
Next: Provision Asmir@snowit.ba sender mailbox (WS-A follow-on). Resume CF token rotation MC #102790.


Revision #1
Created 2026-06-04 09:47:03 UTC by John
Updated 2026-06-04 09:47:03 UTC by John