# Planning — Ad → OTP → LLM Qualification → Report Funnel

**Owner:** Fruxinfo (hr@fruxinfo.com)
**Created:** 2026-06-30
**Status:** Planning — research-based, not yet approved
**Language:** Landing page in English (matches the ads). Chat + report localise to the user's language automatically. Plan written in English.

---

## 1. User journey (one-line view)

`Google Ad → Landing page → Mobile + OTP verify → (if number is flagged VOIP/disposable, end with hr@fruxinfo.com message) → LLM chat (mostly selection-based, auto-detects which Fruxinfo service the user needs) → Personalised report shown in-page → Copy 5-line summary or share on WhatsApp → Regenerate gated behind admin approval → Lead lands in Fruxinfo admin panel grouped by phone, with every generated report listed.`

---

## 2. Stage-by-stage detail

### Stage 1 — Landing page
- Single CTA: "Get my free business report in 2 minutes."
- Above the fold: country-code dropdown + mobile field + "Send OTP" button.
- **No captcha** (per user decision). Bot defense uses: per-IP rate-limit, hidden honeypot field, browser-fingerprint signal, and SMS-pumping monitoring on spend. See Section 7.
- No name / email / website asked at this stage — friction kills conversion. We collect those silently inside the chat.

### Stage 2 — OTP verify + history check
- Before sending OTP, run carrier-lookup. If number is VOIP / disposable / premium-rate / shared-cost → **end the flow** with a polite message: *"This number type isn't supported here. For business enquiries please email hr@fruxinfo.com."* No OTP is sent.
- If number is clean: send 6-digit OTP, 5-minute TTL.
- User enters OTP → server verifies → issue short-lived signed session cookie (JWT, httpOnly, secure, sameSite=lax).
- **History check** (immediately after verify):
  - If this number has **no prior report** → go to Stage 3 (chat begins).
  - If this number **already has a report**, show: existing report + a single **"Request a new report"** button. Clicking it opens a small form (one optional reason field) and submits a regen request to admin. User sees: *"Request received — we'll notify you when it's available."*
  - If admin has **approved** a pending request for this number → user sees: *"Your new report is unlocked — start fresh"* button → on click, a new chat session starts (Stage 3).
- OTP is required on every revisit. No cookie-skip.

### Stage 3 — LLM chat (selection-first)
- Opening message from Claude: *"Hi! I'm Fruxinfo's strategist. Tell me what you need help with — I'll prepare a free report in 2 minutes."*
- **Language:** Claude detects the user's language (from browser locale + first reply) and switches the conversation, chips, and final report into that language automatically. Landing-page hero stays English (matches the ads); chat localises from first turn.
- First question shown as **6 quick-pick chips**, not free text:
  - Build a website
  - Build a CRM / internal tool
  - Digital marketing / SEO
  - Mobile app
  - E-commerce store
  - Something else
- Each branch has its own 5–7 question tree (selection chips + ranges + multi-select). Free text only when "Something else" or when user clicks "Other → tell me more".
- For ambiguous answers, Claude asks a clarifying **counter-question** instead of forcing free text. Example: user picks "Build a CRM" but earlier said "we have Zoho" → Claude asks: *"Replace Zoho or extend it?"* (chips: Replace / Extend / Not sure yet).
- During the conversation Claude silently collects:
  - Business website URL (asked early: "What's your website?" — single text field, validated)
  - Name (asked mid-flow: "Who am I preparing this report for?")
  - Industry, team size, monthly revenue band, biggest pain — all via chips/ranges.

### Stage 4 — Report (inline only, no PDF)
- After 5–8 turns Claude calls a `generate_report` tool. Server takes collected data + chat transcript and renders a structured report.
- Report is shown **inline** when the chat closes — same screen, styled HTML view. **No PDF download.**
- Report content depends on branch:
  - **No CRM →** estimated monthly leads lost + revenue lost (calculated from "leads/month × value × leak rate"), top 3 systemic risks, recommendation.
  - **Existing CRM →** gap analysis (what's missing vs their stated pain), benefit of switching/extending, recommendation.
  - **Website branch →** quick verdict on their current site (uses business URL captured earlier), competitor benchmark angle, opportunity sizing.
  - **Digital marketing branch →** channel gap (SEO / Ads / Social / Content), wasted-spend risk, opportunity sizing.
- Report ends with a soft CTA: *"Want Fruxinfo to fix this? Our team will call you."* Call-window preference is captured (chip: Morning / Afternoon / Evening + timezone) and stored against the lead — no calendar widget.

### Stage 5 — Share + Regenerate
- **Share — copy + WhatsApp only. No public URLs.**
  - Alongside the inline report, Claude generates a **5-line summary** (one short paragraph).
  - Two buttons:
    - **"Copy summary"** → copies the 5-line text to clipboard.
    - **"Share on WhatsApp"** → opens `https://wa.me/?text=<encoded summary>` (works on mobile + WhatsApp Web).
  - No share slug, no public URL, no report image. Only the short text travels outside.
- **Regenerate = Restart, admin-gated.**
  - User sees a "Restart / Request new report" button on the report screen.
  - Click → small form (optional reason) → request goes to admin panel.
  - Admin approves → next time user OTP-verifies with the same number, a fresh chat session is unlocked (see Stage 2 history check).

### Stage 6 — Lead handoff + admin panel
- Lead stored in DB with: verified phone, name, business URL, industry, all chip answers, full transcript, generated report(s), call-window preference, UTM params from ad click.
- **Admin panel** (new screen, internal use only — login-protected):
  - **Leads list** — grouped by phone number. Each row: phone, name, business URL, last activity, # of reports, status (new / contacted / qualified / lost).
  - **Lead detail** — opens to show every report this number has generated, listed separately with timestamp, branch (CRM / website / marketing / etc.), and a link to view each.
  - **Regen requests** — pending list with one-click Approve / Reject. Approve unlocks the next chat session for that number.
  - **Call-window** field displayed prominently — e.g., *"Call this lead: Afternoon, IST"* — so sales picks the right slot.
- Slack/email notification to Fruxinfo sales team for every completed report and every new regen request.

---

## 3. Tech stack (recommendation, decide in Section 11)

| Layer | Recommended | Why |
|------|------------|-----|
| Frontend | Existing Next.js app, new route `/get-report` | Reuse stack, fast |
| SMS gateway | **User's existing SMS provider** | Already in use — reuse to save cost |
| OTP logic | **Custom server-side** (code generation + Redis storage + TTL + attempt counter) | Built in-house since raw SMS provider, not a managed verify service |
| Bot defense | **No captcha** — IP rate-limit + honeypot + behavioral signals + SMS-spend monitoring | Per user decision; see Section 7 for risk note |
| LLM | **Claude API — `claude-opus-4-7` for chat turns, `claude-sonnet-4-6` for the final report** | Opus = best question-asking quality (user's choice); Sonnet = cheaper for long report generation. Both natively multilingual. |
| Prompt caching | Enabled on the system prompt + question-tree config | Drops Opus per-turn cost ~80–90% on repeat input |
| State | **Redis** (self-hosted on existing VPS) for session + OTP TTL + rate-limit counters | Hot path, low latency, no extra cost |
| DB | **MySQL on existing VPS** for leads + reports + transcripts | Already provisioned, zero extra cost |
| Phone validation | **libphonenumber-js** (format + region) + carrier-lookup API (Numverify / Abstract / similar) | Stops obviously fake numbers; carrier check blocks VOIP/disposable — flagged numbers see hr@fruxinfo.com fallback message |
| Report rendering | **HTML only, in-page** (no PDF) | Per user decision — simpler, faster, no Puppeteer |
| Share | **`navigator.clipboard` copy** + **`wa.me/?text=` deep link** | No public share URL — short summary text only |
| Booking | None — call-window chip (Morning/Afternoon/Evening + timezone) stored in admin panel | Per user decision — sales calls from CRM, no Calendly |
| Admin panel | New internal route in same Next.js app (login-protected) — leads grouped by phone, regen approvals | New requirement |
| Notifications | Slack webhook + transactional email (Resend / SES) | TBD |

---

## 4. LLM architecture (Claude API)

**Pattern:** Server-side state machine that drives Claude via **tool use**, not free-form prompting. This makes the flow safe, deterministic, and cheap.

**Tools Claude is given:**
1. `ask_question` — args: `{ id, text, type: 'single_select' | 'multi_select' | 'range' | 'text', options[], reason }`. Server renders chips/inputs from this.
2. `collect_data_point` — args: `{ key: 'business_url' | 'name' | 'industry' | ..., value }`. Server validates and stores.
3. `branch_to_service` — args: `{ service: 'website' | 'crm' | 'marketing' | 'app' | 'ecommerce' | 'other' }`. Locks the question tree.
4. `ready_to_generate_report` — signals server has enough to render.

**Each turn:**
- Server sends Claude: system prompt (Fruxinfo services + question-tree templates + rules) + conversation history + collected_data so far.
- Claude returns either a tool call (ask next question / collect / branch) or `ready_to_generate_report`.
- Server renders chip UI in the chat → user picks → next turn.

**Token cost protection:**
- Hard cap: max 12 chat turns per session, max ~6K input tokens per turn, max ~400 output tokens.
- Hard cap: 1 free report generation per verified number per 30 days. Regen is request-only.

**Prompt injection defense:**
- User never types free text into a field that becomes part of the system prompt.
- Free-text answers are wrapped: `<user_answer trust="low">…</user_answer>` so model treats them as data, not instructions.
- All structured data (URLs, numbers) is validated server-side before being shown back to Claude.

---

## 5. Question trees (CRM branch — fully spec'd; others stubbed)

### 5.1 CRM branch (detailed)
1. **"Do you currently use a CRM?"** → Yes / No / Just spreadsheets / Not sure
2. *If No / Spreadsheets:*
   - "How do you track leads today?" → Excel / WhatsApp / Email / Nothing systematic
   - "Leads per month?" → <50 / 50–200 / 200–1000 / 1000+
   - "Average deal value (USD)?" → <$100 / $100–1K / $1K–10K / $10K+
   - "Conversion rate?" → <2% / 2–5% / 5–10% / 10%+ / Don't know
   - "Team size handling leads?" → Solo / 2–10 / 10–50 / 50+
   - "Biggest pain?" (multi-select) → Forgetting follow-ups / Lost leads / No reporting / Team confusion / No automation
3. *If Yes:*
   - "Which CRM?" → HubSpot / Zoho / Salesforce / Pipedrive / Custom / Other
   - "Biggest pain?" (multi-select) → Cost / Limited customisation / Slow / Bad UX / Bad integrations / Reporting weak
   - "Missing features?" (multi-select) → WhatsApp/SMS / Custom workflows / Role-based access / Analytics / Mobile app / Integrations
   - "Considering a switch?" → Yes / No / Maybe

**Loss math (No-CRM branch):**
`monthly_loss = leads × avg_value × estimated_leak_rate (default 0.25 if "no systematic tracking", 0.15 if "Excel only")`
→ Annualised, shown with a 1-paragraph plain-English explanation.

**Gap math (Existing-CRM branch):**
Scored 0–10 across 5 axes (UX, automation, reporting, integrations, cost). Anything ≤4 becomes a "gap" bullet in the report. Recommendation: extend / migrate / build custom — based on top 2 axes.

### 5.2 Other branches (stub — to be finalised in Phase 1)
- **Website** → current URL / type (info / lead-gen / e-com) / age / traffic band / biggest complaint
- **Digital marketing** → channels active / monthly ad spend / current ROI / biggest channel pain
- **Mobile app** → platform / current state (idea / live / broken) / users / monetisation
- **E-commerce** → platform / monthly orders / AOV / cart abandonment band / biggest pain
- **Other** → free text → Claude reframes into best-fit branch or marks as "consultative lead"

---

## 6. Report structure (template)

1. **Cover** — business name, business URL, prepared on, prepared for.
2. **Executive summary** — 4–5 lines, plain English.
3. **Current state assessment** — bullet list of what they told us.
4. **Loss / gap analysis** — numbers from Section 5 math, in their currency band.
5. **Top 3 risks if nothing changes** — projected forward 12 months.
6. **Recommended next steps** — 3–5 actions, ranked by impact/effort.
7. **How Fruxinfo solves this** — short, references their exact pain points.
8. **CTA + share row** — "Our team will call you" + Copy summary + Share on WhatsApp + Restart-request button.
9. **Signature footer** — fixed on every report:
   ```
   Prepared by Fruxinfo.com
   karshan.zala@fruxinfo.com
   ```

Rendered as: in-page HTML view only. Stored against the lead record in MySQL as HTML + structured fields. No PDF.

---

## 7. Security plan

| Threat | Mitigation |
|--------|-----------|
| OTP request flood | Max 3 OTPs per number per 24h, max 10 OTP requests per IP per day. |
| Fake / VOIP numbers | libphonenumber-js format check + carrier-lookup API before OTP send; block VOIP, "premium-rate", and "shared-cost" line types. Flagged users see hr@fruxinfo.com fallback (no OTP sent). |
| SMS-pumping fraud | Cap OTPs per IP/day and per number/day; block known premium-rate prefixes globally; daily alert on SMS spend spike; manual deny-list of high-risk country prefixes if abuse detected. Note: with worldwide geo and no captcha, this risk is higher — monitor closely in first 30 days. |
| OTP brute-force / replay (server-side) | OTP is 6-digit, generated with crypto.randomBytes, stored hashed in Redis with 5-min TTL, single-use, max 5 wrong attempts before lock. |
| Bot signup | Per-IP rate limit (10 OTP requests/day); hidden honeypot field in OTP form; simple JS challenge token bound to session; behavioral checks (typing speed, mouse movement). No captcha per user decision — accept higher bot-noise risk. |
| Session hijack | Short-lived (30 min) JWT in httpOnly + secure cookie; rotated on each chat turn. |
| Prompt injection via free-text answers | User free-text wrapped in `<user_answer trust="low">`; tools restrict what Claude can do; outputs schema-validated. |
| Token-cost abuse | Hard caps: 12 turns/session, 1 free report/number/30 days, total cost capped per number. |
| Data leak via Share | No public share URL exists. Only a 5-line summary text leaves the app (copy / WhatsApp). |
| GDPR / CCPA | Privacy policy + consent checkbox at OTP screen; data-deletion endpoint; 12-month retention. |
| PII at rest | Phone encrypted with KMS-managed key; reports stored with row-level security. |
| Replay / CSRF | CSRF tokens on every POST; sameSite cookies; origin check. |
| DDoS | Cloudflare proxy in front of Next.js. |
| Internal access | Only sales-team Google accounts via SSO can view lead records; audit log on every read. |

---

## 8. Data model (sketch)

- **users** — `id, phone (encrypted), country, name, business_url, first_seen_at, last_otp_at, marketing_consent`
- **sessions** — `id, user_id, started_at, ended_at, status (active/completed/abandoned), branch, call_window`
- **answers** — `id, session_id, question_id, answer_value (json), asked_at, answered_at`
- **collected_data** — `id, session_id, key, value, source (chip/text), validated`
- **reports** — `id, session_id, user_id, generated_at, html, summary_5_lines, branch` (one row per generated report — multiple per user_id is normal)
- **regen_requests** — `id, user_id, requested_at, reason, status (pending/approved/rejected), decided_at, decided_by` (admin approves → unlocks next session for this user_id)
- **admins** — `id, email, password_hash, role`
- **events** — `id, user_id, session_id, type, payload, ts` (audit + analytics)

---

## 9. Cost estimate (per qualified lead)

| Item | Cost |
|------|------|
| SMS via existing provider | as per current plan |
| Carrier-lookup API (required — flags VOIP/disposable) | ~$0.003–0.01 per check (or free if existing SMS provider already includes carrier info) |
| Claude Opus — ~12 chat turns (with prompt caching) | ~$0.30–0.80 |
| Claude Sonnet — 1 report generation | ~$0.04–0.10 |
| Hosting / DB / Redis | $0 — runs on existing VPS + MySQL |
| **Total per completed report (excl. SMS)** | **~$0.35–0.90** |

**Cost note:** Opus for chat is ~10× the cost of Haiku, but gives much sharper, more contextual questions — user's explicit choice for lead quality. Prompt caching on the system prompt + question tree config cuts repeat input cost by 80–90%. If at scale this becomes painful, we can route easy turns to Sonnet and keep Opus only for branching decisions.

Plus Google Ads spend (separate campaign planning).

---

## 10. Anti-friction rules (UX guard-rails)

- Default to chips, ranges, and multi-select. Free text only when unavoidable.
- Never ask two questions in one bubble.
- When user picks "Other", surface 3 deeper chips before falling back to free text.
- Auto-advance after selection — no "Next" button.
- Show progress bar (e.g. *"Step 4 of 7"*) so users feel the end is near.
- Maximum 8 visible questions before report — anything more = abandonment risk.
- Inactivity > 90s → gentle nudge ("Still there?"). >5 min → save & email link to resume.
- Mobile-first: chip targets ≥ 44px, single-column layout, sticky chat input.

---

## 11. Open decisions (need answers before build)

**All decisions locked. Build can proceed.**

| Topic | Decision |
|-------|---------|
| Captcha | None — IP rate-limit + honeypot + behavioral signals only |
| LLM (chat turns) | Claude Opus (`claude-opus-4-7`) for question-asking quality |
| LLM (report) | Claude Sonnet (`claude-sonnet-4-6`) for cost efficiency on long output |
| Geo at launch | Worldwide |
| Chat language | Multilingual — Claude matches user's language |
| Landing-page language | English |
| Hosting / DB / Redis | Existing VPS + MySQL + self-hosted Redis |
| SMS gateway | User's existing SMS provider |
| Carrier-lookup | Required. Flagged numbers (VOIP / disposable / premium-rate / shared-cost) → end with hr@fruxinfo.com message |
| Report rendering | HTML inline only — no PDF |
| Share | "Copy 5-line summary" + "Share on WhatsApp" (`wa.me`) only — no public share URLs |
| Booking on CTA | None — call-window chip (Morning/Afternoon/Evening + timezone) captured into admin panel |
| Free reports per number | 1 — additional reports gated by admin approval via Restart button |
| Revisit flow | OTP required every time — on verify, history check shows existing report + Restart-request, or unlocks new chat if admin approved |
| Admin panel | New screen — leads grouped by phone, every report listed separately, regen approve/reject, call-window visible |
| Data retention | 12 months for transcripts + reports |
| Report signature | `Prepared by Fruxinfo.com / karshan.zala@fruxinfo.com` |

---

## 12. Phases

### Phase 1 — Design lock (1 week)
- Finalise all 6 service question trees (only CRM is spec'd here).
- Lock report template per branch.
- Finalise chip copy + microcopy (English + supported languages list).
- Wireframe admin panel screens.

### Phase 2 — OTP + landing + history check (1 week)
- Build `/get-report` route with phone field + honeypot.
- API: `POST /api/otp/send`, `POST /api/otp/verify`.
- Integrate existing SMS provider for delivery.
- Server-side OTP generation (crypto-random 6-digit), Redis storage with TTL + attempt counter.
- libphonenumber-js validation + carrier-lookup; VOIP/disposable → hr@fruxinfo.com fallback screen.
- Post-OTP history check: existing report view + Restart-request button, or unlock new session if admin approved.
- Rate-limit + abuse rules.
- Session cookie issuance.

### Phase 3 — Chat engine (1.5 weeks)
- API: `POST /api/chat/turn` with Claude Opus tool-use loop.
- Server-side state machine + question-tree configs.
- Chip-based chat UI with progress bar.
- Silent data capture (URL, name, call-window etc.).
- Language detection + Claude prompt to respond in detected language.

### Phase 4 — Report + share + restart request (1 week)
- API: `POST /api/report/generate` (Sonnet call + template render).
- Inline HTML report view with signature footer.
- 5-line summary generation + Copy button + WhatsApp `wa.me` share button.
- Restart-request form → writes to `regen_requests` table.

### Phase 5 — Admin panel (1 week)
- Login-protected route.
- Leads list grouped by phone.
- Lead detail with all reports listed.
- Regen-requests inbox with Approve / Reject.
- Call-window visible on every lead.

### Phase 6 — Security + observability + launch (1 week)
- Penetration pass: OTP abuse, prompt injection, session hijack, carrier-lookup bypass.
- Slack webhook + email alerts for new leads + new regen requests.
- GA4 + Microsoft Clarity + cost-per-lead dashboard.
- Privacy policy + consent + data-deletion endpoint.
- Soft launch on a low-budget ad campaign.

---

## 13. Next step

All Section 11 decisions are locked. Next action: build the **demo** (scope confirmed separately) to prove the core flow — OTP fake, single CRM branch, Claude Opus tool-use chat, inline report, copy + WhatsApp share. On demo approval, move into Phase 1 → 6.
