diff --git a/.agents/PRD.md b/.agents/PRD.md new file mode 100644 index 0000000..7fa3248 --- /dev/null +++ b/.agents/PRD.md @@ -0,0 +1,483 @@ +# πŸ“„ 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** (5–10 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)** | 80–100/day, ~200/week | User clicks 'Send' (HIL). Max **300 characters**. | +| **Randomized Delays (Critical)** | Required | Spaced calls (30–120s+ 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: +> +> ```sql +> 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. | + +``` + +```