my-fullstack-ai-platform/.agents/PRD.md

37 KiB
Raw Blame History

📄 Product Requirements Document (PRD)

Teklifsat.com AI-Driven Lead Generation Platform

Version: 1.0 Date: 2026-04-06 Author: Product & Engineering Status: Draft Awaiting Review


1. Executive Summary & Vision

Vision Statement

"To become Turkey's #1 AI-driven lead generation platform by empowering 1,000 industrial companies within 6 months to scale globally via automated LinkedIn prospecting."

Product Overview

Teklifsat.com is a Multi-Tenant SaaS platform that automates the entire B2B ABM (Account-Based Marketing) pipeline from LinkedIn company prospecting to AI-powered qualification and personalized multi-contact outreach enabling Turkish industrial companies to scale into international markets (DE, EN, TR).

Core Value Proposition

  • Fully automated pipeline: Search → Enrich → Qualify → Outreach zero manual research
  • AI Qualification: Intelligent lead scoring based on ICP, company profile, and enriched data
  • Built-in Translation Engine: Real-time translation of chat messages and dynamic content across EN/DE/TR
  • Human-in-the-Loop (HIL): Users retain full control over outreach before sending

Differentiator

Unlike Apollo.io, Lemlist, or Instantly, Teklifsat focuses on end-to-end AI pipeline automation with native multi-language support purpose-built for companies operating across language barriers.


2. Target Audience & User Personas

Primary Market

Turkish industrial companies (manufacturing, export, B2B services) expanding into German-speaking and English-speaking markets.

User Roles

Role Description Permissions
Owner Company admin, creates the tenant Full access. Manages Team Settings (ICP, Company Profile, Permissions). Can configure who may create campaigns (default: Owner only).
SDR / Team Member Sales representative Views all campaigns & leads within the tenant. Can create campaigns if Owner grants permission. Sends outreach (HIL). Manages own LinkedIn account connection.

User Stories

Owner

  • As an Owner, I want to define my company's ICP and profile once, so all campaigns use consistent qualification criteria.
  • As an Owner, I want to control which team members can create campaigns.
  • As an Owner, I want to see all campaign results across my organization.
  • As an Owner, I want to upload my company logo so the platform feels branded for my organization.

SDR / Team Member

  • As an SDR, I want to review AI-qualified companies (leads) and their associated contacts before sending outreach.
  • As an SDR, I want to chat with connected contacts in my preferred language (DE/TR/EN) with automatic translation.
  • As an SDR, I want to see the status of connection requests (pending, accepted, no contact) for each contact.
  • As a Team Member, I want to upload my profile picture so my colleagues can identify me easily in the dashboard.

3. Functional Requirements

3.1 Campaign Management

ID Requirement Priority
F-CM-01 Users can create campaigns with parameters: Industry, City, Country, Company Size (Multiple Selection), Target Lead Count, Name P0
F-CM-02 Campaign parameters are immutable after creation (except Name and Target Lead Count) P0
F-CM-03 Campaigns can be paused and resumed P0
F-CM-04 Campaign has two phases: Pipeline Phase (automated) and Engagement Phase (manual/HIL) P0
F-CM-05 Pipeline Phase auto-completes when target reached OR search results exhausted P0
F-CM-06 Paused campaigns are excluded from job scheduling P0
F-CM-07 Strict Uniqueness: Within a tenant, the combination of industry + country is the unique identifier for a campaign. Users cannot create a second campaign for the same combination; to target more leads, they must use the Top-Up approach (adjusting target_leads on the existing campaign). P0
F-CM-08 Lead Adjustment: Users can adjust target_leads on an existing campaign (increase or decrease). - Increase: The pipeline resumes automatically if it was pipeline_completed and the new target exceeds current_leads. - Decrease: Permitted as long as new_target_leads >= current_leads. If new_target_leads is set equal to current_leads, the campaign transitions to pipeline_completed immediately. P0
F-CM-09 Quota Guard on Top-Up: When increasing target_leads, the system must validate that tenant.current_leads_this_month + (new_target - old_target) does not exceed tenant.monthly_quota. If exceeded, the top-up is rejected with a clear error message. P0
F-CM-10 Quota Guard on Create: When creating a campaign, the system must validate that tenant.current_leads_this_month + target_leads does not exceed tenant.monthly_quota. P0

Campaign Status Model

Pipeline Phase:    pending → running → paused → pipeline_completed
                                        ↑ (resumes on lead top-up if pipeline_completed and new target > current_leads)
Engagement Phase:  ongoing (after pipeline completes, users engage with leads)

Quota Logic

tenant.monthly_quota       = total credits available this month (e.g. 1,000)
tenant.current_leads_this_month = SUM of current_leads across all campaigns of this tenant this month
remaining_quota            = monthly_quota - current_leads_this_month
  • Quota is based on delivered qualified companies (current_leads), not ordered lead counts (target_leads)
  • Monthly quota resets at the start of each calendar month
  • The pipeline auto-stops when tenant.current_leads_this_month >= monthly_quota, even if individual campaign targets are not yet reached
  • Affected campaigns are paused with status quota_reached until the next month or quota upgrade

3.2 Lead Pipeline (Background Jobs)

ID Requirement Priority
F-PL-01 Company Search: Suche nach Unternehmen über Unipile API (LinkedIn Premium) basierend auf Branche, Standort und Firmengröße. P0
F-PL-02 Company Enrichment (Global Cache): Website-Inventur via Firecrawl /map. Ein AI-Agent führt das Scraping durch. Daten werden global in leads gespeichert. Liegt eine Anreicherung vor (< 30 Tage), wird dieser Schritt übersprungen. P0
F-PL-03 Company Qualification (Tenant-Specific): KI bewertet das Unternehmen basierend auf (globalen) Enrichment-Daten, Tenant-ICP und Firmenprofil. Dieser Schritt ist IMMER tenantspezifisch. Billable Event: Sobald qualifiziert, wird 1 Credit abgezogen. P0
F-PL-04 People Search (Conditional): Nur für qualifizierte Unternehmen wird eine Suche nach Personen (Contacts) gestartet (Senioritäts-Level: CXO, Director, etc.). Ein Lead (Company) kann mehrere Kontakte haben. P0
F-PL-05 Outreach Strategy Generation: Automatische Erstellung der personalisierten Outreach-Strategie/Template für das Unternehmen (mit Platzhaltern für Kontaktnamen). P0
F-PL-06 Pipeline runs in batches (510 companies per run), loops until target reached. P0
F-PL-07 Companies (Leads) are deduplicated globally by their LinkedIn Company URL. General enrichment data is shared across all tenants to save costs. P0
F-PL-08 Qualification and Outreach Strategy are strictly campaign-specific (stored on campaign_leads). Enrichment data is globally shared but can be refreshed if TTL expired. P0

3.3 LinkedIn Engagement (V2)

ID Requirement Priority
F-LE-01 Send connection request with name-personalized outreach message via Unipile API (triggered by user click HIL). P1
F-LE-02 Track connection status per Contact via Unipile webhooks: pending → accepted → no_contact. P1
F-LE-03 If no response after 3 weeks: retry connection request once. Still no response → mark contact as no_contact. SDR can then try the next contact in the same company. P1
F-LE-04 After connection accepted: enable chat messaging with the specific contact. P1
F-LE-05 Inbox View: Display LinkedIn conversations per contact. P1
F-LE-06 Track outreach performance (open rates, response rates) per company and per SDR. P1
F-LE-07 Claim Logic: A contact is marked as claimed_by_id when an SDR starts the engagement. P1

3.4 Translation Layer (V2)

ID Requirement Priority
F-TL-01 i18n: Static UI elements in EN, DE, TR P1
F-TL-02 Dynamic Content Translation: Company and contact data, enrichment results, outreach messages translated to user's preferred language P1
F-TL-03 Outreach Language: Outreach messages generated/translated into contact's language (detected from LinkedIn profile) P1
F-TL-04 Chat Translation: Incoming messages translated to user's language. Outgoing messages translated to contact's language before sending. P1

3.5 Team & Tenant Settings

ID Requirement Priority
F-TS-01 ICP Definition: Owner defines Ideal Customer Profile per tenant (used for AI qualification) P0
F-TS-02 Company Profile: Owner defines own company's products/services (used for AI qualification + outreach) P0
F-TS-03 Permissions: Owner configures who can create campaigns (default: Owner only) P0
F-TS-04 Team Management: Invite/remove team members (existing feature) P0
F-TS-05 Organization Branding: Ability to upload an organization image (Company Logo). P1

3.6 User Settings

ID Requirement Priority
F-US-01 User selects preferred language (EN/DE/TR) P0
F-US-02 User connects LinkedIn account via Unipile (existing feature) P0
F-US-03 Profile Picture: Ability for team members to upload their own profile picture. P1

3.7 Onboarding Automation (V3)

ID Requirement Priority
F-OB-01 User provides own website URL → system scrapes and auto-generates company profile via Firecrawl + AI P2
F-OB-02 User provides 3 customer URLs → system scrapes and auto-generates ICP via Firecrawl + AI P2
F-OB-03 Show progress animation during scraping ("Your profile is being created…") P2

4. Non-Functional Requirements

4.1 Rate Limiting & Resource Management

LinkedIn / Unipile (User-Owned Premium Business Accounts)

All search and engagement actions are performed using the user's own LinkedIn Premium Business account. Platform-owned Sales Navigator accounts are no longer used.

Action Premium Business Limit Handling Strategy
Search Profiles (Pipeline Phase) 1,000 total/query Max 1,000 results per query. Use persistent search_cursor (base64).
Daily Search Limit 1,000 profiles/day Unipile recommendation. Safe Mode: Default to 200/day.
Connection requests (with note) 80100/day, ~200/week User clicks 'Send' (HIL). Max 300 characters.
Randomized Delays (Critical) Required Spaced calls (30120s+ delay) rather than fixed intervals to avoid detection.
Account Warmup Required Start new/inactive accounts with 20% of limits, ramping up over 14 days.

Important

Account Safety FIRST: Since users connect their primary business accounts, the platform enforces human-like behavior. Avoid fixed-interval polling. Use the new_relation webhook for status updates instead of polling the invitation list.

Implementation Requirements

Requirement Details
Search Account Each campaign is assigned exactly one search_account_id (the SDR who initiated the search). This account provides the search quota.
Engagement Phase (1:N) While the search is tied to one account, any SDR in the tenant can claim a lead and use their own linkedin_account_id for the outreach/engagement phase.
429 / 500 / 422 Handling 429/500 (Rate limit) → Pause account search for 24h. 422 (Cannot resend) → Mark lead as invitation_limit_reached, retry next day.
Cursor Workflow The Inngest search loop persists the search_cursor to DB after every batch, enabling reliable pause/resume across sessions.

Firecrawl

Resource Constraint Handling Strategy
Concurrency Max 2 concurrent browsers Semaphore pattern: acquire/release slot via atomic DB update. Jobs wait + retry if no slot available.

4.2 Scalability

  • Linear Scaling: Total platform capacity increases automatically as more users connect Premium accounts.
  • Distributed Load: Background jobs are distributed across user accounts, reducing the risk of a single "platform-wide" ban.

4.3 Multi-Tenancy

  • Row Level Security (RLS) on all tenant-scoped tables.
  • Lead (Company) Ownership: Companies found within a tenant are shared.
  • Contact Ownership: To prevent SDRs from messaging the same person, a contact is marked as claimed_by_id (SDR User ID) once an outreach or chat is initiated.

4.4 Reliability

  • Persistent Cursors: Inngest resume logic uses campaigns.search_cursor to prevent duplicate profile fetches and credit waste.
  • Webhook-Driven: Replaces polling for connection acceptance, reducing account activity.

5. Data Model


6. Architecture & Technology Stack

6.1 Unipile Implementation Constraints

  • User-Owned Only: Platform Managed Service is removed. All actions are scoped to user-connected accounts.
  • Search Logic: Uses the standard linkedin/companies or linkedin/people search endpoint with the search_cursor parameter.
  • Rate Limit Resilience: Search accounts tracks daily fetches. If a 429/500 occurs, the account is temporarily blocked in our DB (user.blocked_until).
  • Distributed Outreach: One campaign can have multiple SDRs engaging leads, each using their own linkedin_account_id.
  • Randomized Delays: Minimum 30s-120s delay between profile fetches. Max 100 profiles total per search batch for safety.
  • Filter: Headcount (Company Size): Passed as an array of objects [{"min": x, "max": y}].
    • Valid Min: 1, 11, 51, 201, 501, 1001, 5001, 10001
    • Valid Max: 10, 50, 200, 500, 1000, 5000, 10000
  • Filter: Industries & Locations: These parameters must be retrieved as IDs from the Unipile GET /api/v1/linkedin/search/parameters endpoint before campaign creation.
  • Search UI Constraints:
    • Debounced Input: Minimum 300ms delay/buffer on search inputs to minimize Unipile API calls.
  • Campaign Deduplication:
    • Logic: Server-side check before creation to prevent multiple campaigns with identical industries + locations + headcount within the same organization.
    • Action: If a match is found, prompt the user to use the existing campaign or confirm if they truly want a duplicate.

Stack

Layer Technology Purpose
Frontend Next.js (App Router) UI, Server Actions, API Routes
Database Supabase (PostgreSQL) Data persistence, RLS, Realtime subscriptions
Background Jobs Inngest Step-based workflows, retries, scheduling, rate limiting
LinkedIn API Unipile Search, connection requests, messaging, webhooks
Web Scraping Firecrawl Company website enrichment
AI LLM API (TBD) Lead qualification, outreach generation, translation
Auth Supabase Auth User authentication (existing)

Architecture Diagram


┌─────────────────────────────────────────────────────┐
│ Frontend (Next.js) │
│ Campaign UI │ Lead Table │ Inbox │ Settings │
└───────┬────────────────┬─────────────────┬──────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────┐ ┌──────────────────┐
│ API Routes │ │ Supabase │ │ Supabase │
│ /api/\* │ │ Realtime │ │ Webhooks (Unipile│
└───────┬───────┘ └───────────┘ │ → connection │
│ │ status updates)│
▼ └──────────────────┘
┌───────────────────────────────────────┐
│ Inngest (Job Orchestration) │
│ │
│ campaign/start → campaign/run (loop) │
│ │
│ Steps: │
│ 1. Search (Unipile) │
│ 2. Enrich (Firecrawl) ──► Semaphore │
│ 3. Qualify (AI) │
│ 4. Generate Outreach (AI) │
│ 5. Update DB + Counters │
│ 6. Sleep (rate limit) │
│ 7. Re-trigger if target not met │
└───────────────┬───────────────────────┘
│
▼
┌───────────────────────────┐
│ Supabase (PostgreSQL) │
│ + Row Level Security │
│ + Resource Limits │
└───────────────────────────┘

```

### Key Patterns

- **Event Recursion:** `campaign/run` processes one batch, then re-triggers itself (instead of `while(true)`)
- **Semaphore:** Firecrawl slot acquisition via atomic DB update
- **Circuit Breaker:** LinkedIn 429 → set `blocked_until` → all jobs auto-pause
- **Scheduler:** Central job that checks resources before dispatching campaign runs

---

## 7. Frontend & UX Design System

### 7.1 Design Philosophy & Aesthetics

To wow the user, the platform follows a **Modern Premium Dark UI** with the following characteristics:
- **Visual Style:** Glassmorphism (semi-transparent cards), subtle gradients (HSL-based), and consistent border radii (standardized via Tailwind variables).
- **Typography:** Modern sans-serif (e.g., *Outfit* or *Inter*) with clear hierarchy.
- **Interactivity:** Micro-animations for feedback (e.g., pulsing states during AI qualification, smooth transitions between list and detail views).
- **Responsive:** Mobile-first layout, ensuring SDRs can monitor campaigns and respond to chats on the go.

### 7.2 Core Views & UX Patterns

#### A. Central Dashboard (Overview)
- **At-a-Glance Stats:** Active Campaigns, Total Companies Qualified, This Month's Quota Usage.
- **Activity Stream:** Recent activity (e.g., "SDR A connected with Director B at Company C").

#### B. Campaign Detail View (ABM Dashboard)
This is the core workspace where the 1:N relationship (Company:Contacts) is mastered:
- **Level 1: The Account (Company):**
    - Represented as a Card or Table Row.
    - Includes Company Name, Domain, Industry, and an **AI-Qualification Badge** (High/Med/Low Match).
    - **AI Reasoning Card:** A dedicated UI section (side-drawer or tooltip) that answers "Why does this match?" based on the `qualification_data`.
    - **Enrichment Quick-View:** A summary of the Firecrawl results (Company USP, recent news, etc.).
- **Level 2: The Contacts (People):**
    - Expandable list inside the Company Card.
    - Profile pictures, job titles, and **one-click outreach button**.
    - **Status Indicators:** Color-coded connection states (e.g., Green for Accepted, Yellow for Pending, Red for No Contact).

#### C. Outreach & Engagement Cockpit
- **Strategy Preview:** View the company-specific outreach strategy generated by the AI.
- **Message Personalization Viewer:** Real-time preview of how placeholders ({{firstName}}) are filled before sending.
- **Human-in-the-Loop (HIL) Action Bar:** Prominent "Send Connection Request" button.

#### D. Real-Time Pipeline Tracker
- **Pulse:** Live progress bar during the Search-Batch (5-10 companies). Shows: "Searching companies..." → "Enriching via Firecrawl..." → "AI Qualifying..." → "Finding People...".
- **Visual Feedback:** No static lists during background jobs. Users see the pipeline "working" in real-time via Supabase Realtime subscriptions.

### 7.3 Design Tokens (Mapping to `globals.css`)

- **Primary:** Premium Teal/Blue (instead of default Indigo).
- **Accent:** Amber for "Qualified" but not yet contacted.
- **Neutral:** Slate/Gray for non-interactive backgrounds.
- **Success:** Emerald for accepted connections.

---

## 8. Monetization

| Model                 | Details                                                                                                                              |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Pay-per-Lead**      | Users are billed per **qualified company (lead)**.                                                                                  |
| **Billable Trigger**  | Lead status = `qualified`. Triggered once per company per tenant across campaigns.                                                  |
| **Non-billable**      | Disqualified companies, companies still in enrichment/qualification phase, search for contacts.                                     |
| **Quota Model**       | Each tenant has a `monthly_quota` (credits). Quota tracks `current_leads` (qualified companies), not `target_leads`.                |
| **Quota Enforcement** | Pipeline auto-stops when `current_leads_this_month >= monthly_quota`.                                                               |
| **Quota Upgrade**     | Owner can upgrade their plan to increase `monthly_quota`.                                                                           |

### Tenant Quota DB Fields

```

tenants:
monthly_quota INT -- e.g. 1000 (set by plan)
current_leads_month INT -- delivered qualified leads this month (resets monthly)
quota_reset_date DATE -- date of last monthly reset

Note

Free tier / trial details TBD. Consider offering first N leads free for onboarding conversion.

Important

The uniqueness constraint (industry + country) on active campaigns is enforced at the application layer (before DB insert) AND as a partial unique index in PostgreSQL:

CREATE UNIQUE INDEX campaigns_industry_country_unique
ON campaigns (tenant_id, industry, country);

9. Roadmap & Milestones

Phase 1 MVP (Core Pipeline & ABM Dashboard)

Goal: End-to-end pipeline working. Users can create campaigns and review/engage companies and their contacts in a high-end UI.

Backend & Infrastructure:

  • Database schema (campaigns, leads, lead_contacts) + ABM RLS
  • Inngest setup + campaign/run workflow (the Step-Loop)
  • Search step (Unipile Company Search)
  • Enrichment step (Firecrawl + AI-Agent + Semaphore)
  • Qualification & Outreach Strategy generation (AI)
  • Contact Search step (Unipile People Search for qualified companies)

Frontend & UI (Next.js):

  • Layout System (Modern Dark Navigation, Premium Design Tokens)
  • Campaign Overview UI (List/Add Campaigns)
  • The "ABM Board": Company Card list with expandable Contacts
  • AI-Match Drawer: Showing the "Why?" (AI Reasoning) and Enrichment details
  • Real-time Progress Tracking (Supabase Realtime integration)
  • SDR Claim & Connection Trigger (HIL Mock/V1 Trigger)
  • Organization Logo upload & display
  • User Profile Picture upload & display

Phase 2 LinkedIn Engagement & Translation

Goal: Users can send outreach, track connections, and chat with contacts across languages.

  • Connection request sending via Unipile (HIL user clicks send)
  • Webhook handler for connection status updates (per Contact)
  • Connection retry logic (3-week timeout → retry once)
  • Inbox / Chat view (Contact-centric)
  • Chat translation engine (incoming/outgoing)
  • i18n (Static UI: EN, DE, TR)
  • Dynamic content translation (company data, enrichment results)

Phase 3 Scaling & Onboarding Automation

Goal: Reduce onboarding friction, scale infrastructure.

  • Auto-onboarding: Website → Company Profile (Firecrawl + AI)
  • Auto-onboarding: 3 Customer URLs → ICP generation
  • Multi-account LinkedIn pool (round-robin, per-account limits)
  • Analytics dashboard (conversion rates, outreach performance)
  • Per-tenant quotas and fair scheduling
  • Billing integration (pay-per-lead tracking)

10. Risks & Mitigations

Risk Likelihood Impact Mitigation
LinkedIn blocks / 429 per account High Critical Circuit breaker per account, blocked_until in DB, random call spacing, multi-account pool (V3)
LinkedIn 422 cannot_resend_yet (invitation cap) High High Dedicated error handler, mark lead as invitation_limit_reached, retry next day
Outreach message >300 chars truncated Medium Medium Enforce 300-char limit in AI prompt and UI
LinkedIn fake-account detection Low (if using real accounts) Critical Policy: only real accounts allowed, gradual ramp-up for new accounts
Firecrawl latency (up to 5 min/lead) Medium Medium Parallelization within semaphore limit (max 2), cache already-scraped domains
AI qualification accuracy Medium High Manual override in UI, iterative ICP refinement, log qualification reasoning
Multi-tenant data leakage Low Critical RLS on all tables, automated tests for tenant isolation
Unipile webhook delivery failure Medium Medium Idempotent webhook handler, periodic light polling as fallback (randomized intervals)

11. Open Questions

# Question Impact
1 Which LLM provider for qualification + outreach? (OpenAI, Anthropic, local?) Cost, latency, quality
2 Free tier / trial offering details? Onboarding conversion
3 GDPR/DSGVO compliance scope for V1? (Lead opt-out, data deletion) Legal, EU market
4 Resolved: All search and engagement actions use the user's connected LinkedIn Premium account (search_account_id). Platform accounts are not used.
5 Outreach message tone/style configurable per tenant? (See Section 12) Feature scope

12. Future Roadmap & Feature Requests (ICE Box)

The following items were identified during architecture review and are deferred for post-MVP implementation:

Feature Description Reason for Delay
Phase 0: Pre-Qualification Lightweight AI check (LinkedIn-only data) before expensive Firecrawl/Deep-Scraping enrichment. Optimization for unit economics.
Tone of Voice (DACH) Configurable "Formal (Sie)" vs "Informal (Du)" setting for outreach generation. Critical for conversion in German B2B context.
Advanced Filtering Add job_title to campaign filters. (Note: Seniority levels implemented in V1). Requires more complex search query mapping in Unipile.