484 lines
37 KiB
Markdown
484 lines
37 KiB
Markdown
|
|
# 📄 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. |
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```
|