Skip to content

mazzyy/LinkAI

Repository files navigation

LinkAI -- Intelligent LinkedIn Automation Platform

LinkAI is a dual-sided LinkedIn automation SaaS that brings together recruiter outreach workflow building and applicant auto-apply functionality under one roof. It is built around AI-driven personalization, anti-detection architecture, and a safety-first philosophy that keeps a human in the loop at every critical step.

The goal is simple: help recruiters and job seekers manage their LinkedIn activity at scale without getting flagged, banned, or lost in manual busywork. Everything from connection requests to follow-up messages can be orchestrated through a visual workflow builder, while the underlying engine handles timing, fingerprinting, and warm-up so the user does not have to think about it.

For the practical local setup and first-run flow, see the usage guide.


Table of Contents


Why LinkAI Exists

LinkedIn outreach is time-consuming, repetitive, and easy to get wrong. Send too many connection requests in a day and LinkedIn restricts your account. Use generic messages and prospects ignore you. Manage multiple accounts or team members and things fall apart fast.

LinkAI was built to solve these problems at their root. Instead of bolting automation on top of a browser and hoping for the best, it takes a different approach: extract session cookies from the user's real browser through a lightweight Chrome extension, run all orchestration server-side with proper anti-detection measures, and gradually warm up each account over 21 days so LinkedIn never sees a sudden spike in activity.

The platform serves two audiences. Recruiters get a full outreach CRM with campaign sequences, template personalization, and team collaboration. Job seekers (in a planned phase) will get AI-powered job matching and automated Easy Apply workflows. Both sides share the same underlying engine for safe, human-like LinkedIn interaction.


How It Works (High Level)

The typical user flow looks like this:

  1. A user signs up through the web app and installs the Chrome extension.
  2. The extension extracts LinkedIn session cookies (li_at and JSESSIONID) from the user's browser and sends them to the backend.
  3. The backend encrypts and stores these cookies, then begins a 21-day warmup period for the account.
  4. During warmup, daily action limits scale gradually using a sigmoid curve, starting at roughly 5 percent of full capacity and reaching 100 percent by day 22.
  5. The user builds outreach workflows using a visual drag-and-drop builder, sets up message templates with dynamic variables, and imports leads via CSV or LinkedIn URLs.
  6. The campaign engine executes sequences step by step: view a profile, wait a day, send a connection request, check if it was accepted, then send a follow-up message or fall back to email.
  7. All interactions are made to look human -- mouse movements follow Bezier curves, typing simulates realistic speed with occasional typos, and actions only happen during business hours in the lead's timezone.
  8. Results flow into the analytics dashboard, where users can track acceptance rates, reply rates, and campaign performance over time.

Architecture Overview

LinkAI uses a hybrid architecture. A lightweight Chrome extension handles authentication by extracting cookies from the user's real browser session. The FastAPI backend handles everything else -- orchestration, AI personalization, encryption, and (in future phases) server-side browser automation via Playwright with residential proxies.

+------------------------------------------------------------------------+
|                          USER'S BROWSER                                |
|  +------------------+    +------------------------------------------+  |
|  | Chrome Extension |    |          React Frontend (SPA)            |  |
|  | - li_at cookie   |    |  - Landing Page (particles, parallax)   |  |
|  | - JSESSIONID     |--->|  - Dashboard, Inbox, Leads, Campaigns   |  |
|  | - User-Agent     |    |  - Workflow Builder (React Flow)         |  |
|  +--------+---------+    |  - Analytics (Recharts)                  |  |
|           |              |  - Templates, Sequences, Settings        |  |
|           |              +-------------------+----------------------+  |
+-----------+-----------------------------+-----------------------------+
            | POST /api/linkedin/connect  | REST API + JWT Auth
            v                             v
+------------------------------------------------------------------------+
|                      FastAPI Backend (Python)                          |
|  +------------+  +----------------+  +-----------------------------+  |
|  | API Routes |  |   Services     |  |       Middleware            |  |
|  | - auth     |  | - linkedin     |  | - TenantMiddleware (RLS)   |  |
|  | - linkedin |  | - encryption   |  | - CORS (frontend + ext)    |  |
|  | - leads    |  | - warmup       |  +-----------------------------+  |
|  | - inbox    |  | - human_behavior|                                  |
|  | - templates|  | - browser_mgr  |  +-----------------------------+  |
|  | - campaigns|  | - analytics    |  |    Models (SQLAlchemy)      |  |
|  | - sequences|  | - sequence_eng |  | - User, Tenant, Invitation |  |
|  | - analytics|  | - inbox        |  | - LinkedInAccount          |  |
|  | - engage   |  | - enrichment   |  | - Lead, Template, Sequence |  |
|  | - enrichment| | - email        |  | - Conversation, ConnReq    |  |
|  | - anti_det |  | - proxy        |  +-----------------------------+  |
|  +------------+  +----------------+                                    |
+------------------------+----------------------------------------------+
                         |
           +-------------+-------------+
           v             v             v
     +-----------+  +---------+  +-------------------+
     | PostgreSQL|  |  Redis  |  | LinkedIn (Ext.)   |
     | 16 + pgv  |  | 7-alpine|  | - Voyager API     |
     | - RLS     |  | - Cache |  | - Authwall        |
     | - Alembic |  | - Queue |  | - Session cookies |
     +-----------+  +---------+  +-------------------+
graph TB
    subgraph Browser ["User's Browser"]
        EXT["Chrome Extension<br/>Cookie Extractor"]
        FE["React Frontend<br/>TypeScript + Vite"]
    end

    subgraph Backend ["FastAPI Backend"]
        API["API Layer<br/>14 Route Modules"]
        SVC["Service Layer<br/>17 Services"]
        MW["Middleware<br/>Tenant Isolation"]
        MDL["Models<br/>SQLAlchemy ORM"]
    end

    subgraph Infra ["Infrastructure"]
        PG[("PostgreSQL 16<br/>+ pgvector")]
        RD[("Redis 7<br/>Cache + Queue")]
    end

    subgraph External ["External"]
        LI["LinkedIn API<br/>Voyager / Profile"]
        PROXY["Residential<br/>Proxies"]
    end

    EXT -->|"li_at + JSESSIONID"| API
    FE -->|"REST + JWT"| API
    API --> SVC
    API --> MW
    SVC --> MDL
    MDL --> PG
    SVC --> RD
    SVC -->|"Validate Session"| LI
    SVC -.->|"Future: Playwright"| PROXY
    PROXY -.-> LI
Loading

Tech Stack

The project is split across three layers -- frontend, backend, and infrastructure -- with each tool chosen for a specific reason.

Layer Technology Version Why It Is Used
Frontend React + TypeScript 19.2 + 5.9 Type-safe single-page application with component reuse
Build Tool Vite 7.3 Fast hot module replacement and ESM-native builds
Routing React Router 7.13 Client-side navigation without full page reloads
Workflow UI React Flow (xyflow) 12.10 Visual drag-and-drop workflow builder, used by Stripe and Typeform
Charts Recharts 3.8 Composable chart components for the analytics dashboard
Icons Lucide React 0.577 Consistent, lightweight icon set across the interface
HTTP Client Axios 1.13 API communication with JWT interceptor for token refresh
Backend FastAPI + Uvicorn 0.115 Async Python API with automatic OpenAPI documentation
ORM SQLAlchemy (async) 2.0.35 Database models and queries with full async support
Migrations Alembic 1.13 Versioned schema migrations that track every database change
Database PostgreSQL + pgvector 16 Relational storage with vector search for future AI features
Cache Redis 7 Session caching, rate limiting, and background job queues
Auth JWT (python-jose) -- Stateless access tokens (15-min) and refresh tokens (30-day)
Encryption AES-256-GCM (cryptography) 43.0 Authenticated encryption for LinkedIn session cookies
Validation Pydantic 2.9 Request and response schema validation with type coercion
HTTP Client (BE) httpx 0.27 Async HTTP client for server-to-server LinkedIn API calls
Extension Chrome MV3 -- Minimal cookie extraction bridge with no content scripts
Container Docker Compose 3.9 One-command local development infrastructure

Project Structure

LinkAI/
|-- docker-compose.yml              # PostgreSQL 16 + pgvector, Redis 7
|-- PRD.md                          # Product Requirements Document
|-- roadmap.md                      # 22 Epics, 79 User Stories, 3 Phases
|-- README.md
|
|-- backend/
|   |-- .env                        # Environment configuration
|   |-- requirements.txt            # Python dependencies
|   |-- alembic.ini                 # Migration configuration
|   |-- alembic/
|   |   |-- env.py                  # Alembic async engine setup
|   |   +-- versions/               # 5 migration files
|   |
|   +-- app/
|       |-- main.py                 # FastAPI app factory + middleware registration
|       |-- config.py               # Pydantic Settings (reads from .env)
|       |-- database.py             # Async SQLAlchemy engine and session maker
|       |-- dependencies.py         # Dependency injection: get_current_user, get_tenant_id
|       |
|       |-- api/                    # 14 route modules
|       |   |-- auth.py             #   signup, login, refresh, password reset
|       |   |-- users.py            #   user profile CRUD
|       |   |-- team.py             #   invitations and role management
|       |   |-- linkedin.py         #   connect, health check, proxy, limits
|       |   |-- lead.py             #   lead CRUD, CSV import, tagging, search
|       |   |-- inbox.py            #   conversations, messages, labels
|       |   |-- templates.py        #   message templates with variables and A/B testing
|       |   |-- analytics.py        #   dashboard metrics and time-series data
|       |   |-- connection_requests.py  # batch scheduling and approval
|       |   |-- sequences.py        #   multi-step sequences with state tracking
|       |   |-- engagement.py       #   profile views, endorsements, likes
|       |   |-- enrichment.py       #   email finder and data enrichment
|       |   +-- anti_detection.py   #   fingerprint, proxy, warmup status
|       |
|       |-- models/                 # 10 SQLAlchemy models
|       |   |-- tenant.py           #   multi-tenant organization
|       |   |-- user.py             #   users with tenant_id and roles
|       |   |-- invitation.py       #   team invitations
|       |   |-- linkedin_account.py #   encrypted sessions, proxy config, limits
|       |   |-- lead.py             #   prospect profiles with tags
|       |   |-- conversation.py     #   LinkedIn message threads
|       |   |-- connection_request.py # outbound connection tracking
|       |   |-- sequence.py         #   outreach sequence definitions
|       |   +-- template.py         #   message templates with variants
|       |
|       |-- schemas/                # 11 Pydantic request/response DTOs
|       |   |-- auth.py, user.py, team.py, linkedin.py, lead.py
|       |   |-- inbox.py, template.py, sequence.py
|       |   |-- analytics.py, connection_request.py
|       |   +-- (all with Config.from_attributes = True)
|       |
|       |-- services/               # 17 business logic modules
|       |   |-- auth_service.py     #   JWT creation/verification, password hashing
|       |   |-- email_service.py    #   transactional email delivery
|       |   |-- linkedin_service.py #   session validation and health checks
|       |   |-- encryption_service.py # AES-256-GCM encrypt and decrypt
|       |   |-- warmup_service.py   #   21-day sigmoid curve limit calculation
|       |   |-- browser_manager.py  #   isolated Playwright browser contexts
|       |   |-- human_behavior.py   #   Bezier mouse, typing simulation, scroll
|       |   |-- proxy_service.py    #   residential proxy rotation
|       |   |-- analytics_service.py
|       |   |-- inbox_service.py
|       |   |-- template_service.py
|       |   |-- sequence_engine.py
|       |   |-- action_scheduler.py
|       |   |-- connection_request_service.py
|       |   |-- engagement_service.py
|       |   +-- enrichment_service.py
|       |
|       +-- middleware/
|           +-- tenant.py           #   extracts tenant_id from JWT on every request
|
|-- frontend/
|   |-- package.json                # React 19, Vite 7, React Flow, Recharts
|   |-- vite.config.ts
|   |-- tsconfig.json
|   |-- index.html
|   |
|   +-- src/
|       |-- main.tsx                # React entry point
|       |-- App.tsx                 # Route definitions
|       |-- index.css               # Global theme with CSS custom properties
|       |
|       |-- api/
|       |   +-- client.ts           # Axios instance with JWT interceptor
|       |
|       |-- contexts/
|       |   +-- AuthContext.tsx      # Auth state management, login/logout, token refresh
|       |
|       |-- components/
|       |   |-- Logo.tsx + Logo.css
|       |   |-- ProtectedRoute.tsx
|       |   +-- ui/
|       |       |-- Button.tsx + .css
|       |       |-- Card.tsx + .css
|       |       +-- Input.tsx + .css
|       |
|       |-- layouts/
|       |   |-- AuthLayout.tsx + .css    # Login and signup page wrapper
|       |   +-- DashboardLayout.tsx + .css # Sidebar navigation and top bar
|       |
|       +-- pages/                  # 16 page components
|           |-- LandingPage.tsx + .css   # Animated landing with particles and parallax
|           |-- Login.tsx, Signup.tsx     # Auth forms
|           |-- ForgotPassword.tsx, ResetPassword.tsx
|           |-- Dashboard.tsx + .css     # Main metrics overview
|           |-- LinkedInAccounts.tsx + .css # Account connection and management
|           |-- Leads.tsx + .css         # Lead table with filtering and tagging
|           |-- Inbox.tsx + .css         # Threaded conversation view
|           |-- Templates.tsx + .css     # Template editor with live preview
|           |-- Campaigns.tsx + .css     # Campaign management
|           |-- Sequences.tsx + .css     # Sequence status tracking
|           |-- WorkflowBuilder.tsx + .css # React Flow visual editor
|           |-- Analytics.tsx + .css     # Charts and time-series metrics
|           |-- Engagement.tsx + .css    # Profile views and endorsements
|           |-- Enrichment.tsx + .css    # Email finder tool
|           |-- TeamSettings.tsx + .css  # Team management and invitations
|           +-- Profile.tsx + .css       # User profile settings
|
+-- extension/
    |-- manifest.json               # Chrome Manifest V3
    |-- background.js               # Service worker for cookie extraction
    |-- popup.html                  # Extension popup interface
    |-- popup.js                    # Connect flow logic
    |-- popup.css                   # Popup styling
    +-- icons/                      # 16, 48, 128px PNG + SVG icons

Core Features

Chrome Extension -- Session Bridge

The Chrome extension is deliberately minimal. It does exactly one thing: extract LinkedIn session cookies from the user's logged-in browser and send them to the backend. There are no content scripts, no DOM injection, and no page modification.

This design matters because LinkedIn's detection systems look for extensions that manipulate page content. By keeping the extension limited to cookie reading, the risk profile drops significantly.

sequenceDiagram
    participant U as User
    participant EXT as Chrome Extension
    participant BG as Background Script
    participant LI as LinkedIn (cookies)
    participant API as FastAPI Backend
    participant DB as PostgreSQL

    U->>EXT: Click "Connect LinkedIn"
    EXT->>BG: sendMessage("extractCookies")
    BG->>LI: chrome.cookies.get("li_at")
    BG->>LI: chrome.cookies.get("JSESSIONID")
    LI-->>BG: cookie values
    BG->>BG: Read navigator.userAgent
    BG-->>EXT: {li_at, jsessionid, user_agent}
    EXT->>API: POST /api/linkedin/connect<br/>(Bearer JWT + cookies)
    API->>API: AES-256-GCM encrypt session
    API->>DB: INSERT linkedin_accounts<br/>(status=WARMING_UP)
    API->>API: Best-effort profile fetch<br/>(may fail from server IP)
    API-->>EXT: 201 Created {account}
    EXT-->>U: "Account Connected"
Loading

A few things worth noting about this flow:

  • The extension only reads cookies. It never writes to the page or injects scripts.
  • Server-side validation of the LinkedIn session is best-effort. If the backend's IP gets blocked by LinkedIn (which is common), the connection still succeeds because the cookies themselves are known to be valid -- they just came from the user's browser.
  • There is a 24-hour grace period after connection. Health checks during this window will not mark an account as expired, even if LinkedIn blocks the server-side validation attempt.

Authentication and Multi-Tenancy

The platform supports teams and organizations out of the box through a multi-tenant architecture.

  • JWT Access Tokens have a 15-minute TTL and carry the user's ID, tenant ID, and role.
  • Refresh Tokens are stored as HttpOnly cookies with a 30-day TTL and rotate on each use.
  • Tenant Middleware runs on every request, extracting the tenant ID from the JWT and scoping all database queries to that tenant.
  • PostgreSQL Row-Level Security ensures that even if a query accidentally omits the tenant filter, data isolation is maintained at the database level.
  • Team Invitations allow admins to bring in team members with specific roles (admin or member).
graph LR
    A["Login Request"] --> B{"Validate Credentials"}
    B -->|"Success"| C["Generate JWT<br/>access_token + refresh_token"]
    C --> D["Set HttpOnly Cookie<br/>refresh_token"]
    C --> E["Return access_token<br/>in JSON body"]
    B -->|"Failure"| F["401 Unauthorized"]

    G["API Request"] --> H{"Extract JWT"}
    H --> I["get_current_user<br/>dependency"]
    I --> J["Extract tenant_id"]
    J --> K["Scope all DB queries<br/>to tenant"]
Loading

LinkedIn Account Lifecycle

Each connected LinkedIn account is treated as a first-class entity with its own state machine. Accounts move between statuses based on real health check results and user actions.

stateDiagram-v2
    [*] --> WARMING_UP: Extension connects
    WARMING_UP --> ACTIVE: 21 days complete
    WARMING_UP --> EXPIRED: Token truly invalid<br/>(401 after grace period)
    ACTIVE --> EXPIRED: Token expired (401/403)
    ACTIVE --> RESTRICTED: LinkedIn restricted (999)
    EXPIRED --> WARMING_UP: Reconnect via extension
    RESTRICTED --> WARMING_UP: Re-auth + reset
    ACTIVE --> DISCONNECTED: User disconnects
    WARMING_UP --> DISCONNECTED: User disconnects
Loading

Available account management endpoints:

Method Endpoint What It Does
POST /api/linkedin/connect Connect a new account via Chrome extension cookies
GET /api/linkedin/accounts List all connected accounts for the current tenant
GET /api/linkedin/accounts/{id} Detailed view including warmup progress
POST /api/linkedin/accounts/{id}/check-health Manually trigger a session health check
PATCH /api/linkedin/accounts/{id}/proxy Attach or update a residential proxy
PATCH /api/linkedin/accounts/{id}/limits Adjust daily action limits
DELETE /api/linkedin/accounts/{id} Disconnect and clean up the browser profile

Session Encryption

LinkedIn session cookies are sensitive credentials. If someone gains access to a user's li_at cookie, they can impersonate that user on LinkedIn. LinkAI encrypts these cookies at rest using AES-256-GCM, which provides both confidentiality and integrity.

graph LR
    subgraph Encrypt
        A["Session Data<br/>li_at + JSESSIONID + UA"] -->|"json.dumps"| B["Plaintext Bytes"]
        B -->|"AES-256-GCM"| C["Ciphertext + Auth Tag"]
        C -->|"base64"| D["encrypted_session"]
        E["Random 96-bit"] -->|"base64"| F["encryption_nonce"]
    end

    subgraph Decrypt
        D -->|"base64 decode"| G["Ciphertext"]
        F -->|"base64 decode"| H["Nonce"]
        G -->|"AES-256-GCM"| I["Plaintext JSON"]
        I -->|"json.loads"| J["Session Dict"]
    end
Loading
  • Algorithm: AES-256-GCM (authenticated encryption with associated data).
  • Key Derivation: A base64-decoded 32-byte key. If the configured key is shorter, it is hashed using SHA-256 to produce a 32-byte key.
  • Nonce: A unique 96-bit random value is generated for each encryption operation and never reused.
  • Storage: Both the ciphertext and nonce are stored as base64-encoded strings in PostgreSQL.

21-Day Warmup Engine

Brand-new LinkedIn accounts (or accounts that have been dormant) tend to get flagged if they suddenly start sending dozens of connection requests. The warmup engine addresses this by gradually increasing daily action limits over a 21-day period using a sigmoid curve.

Scaling Factor vs. Warmup Day

1.0  |                                          *---------*
     |                                     *
0.8  |                                *
     |                           *
0.6  |                      *
     |                 *
0.4  |            *
     |       *
0.2  |   *
     | *
0.05 |*
     +--+--+--+--+--+--+--+--+--+--+--+--
        1  2  4  6  8  10 12 14 16 18 20 22
                    Warmup Day

The formula is factor = 1 / (1 + e^(-0.35 * (day - 10))), which produces a smooth S-curve centered around day 10.

In practice, the daily limits scale like this:

Day Scaling Connections/Day Messages/Day
1 ~5% 1 2
7 ~25% 6 12
10 ~50% 12 25
14 ~75% 18 37
21 ~97% 24 48
22+ 100% 25 50

Full daily limits for a mature account (100 percent scaling):

Action Daily Maximum
Connection Requests 25
Messages 50
Profile Views 75
Easy Apply 35
Endorsements 15
Likes 20

Anti-Detection and Human Behavior Simulation

LinkedIn uses machine learning to detect automated activity. Bots that click at the center of elements, type at a constant speed, or send requests at perfectly regular intervals are easy to catch. The human_behavior.py service generates realistic interaction parameters for every LinkedIn action.

graph TB
    subgraph Mouse ["Bezier Mouse Movement"]
        M1["Start Point"] --> M2["Control Point 1<br/>random offset"]
        M2 --> M3["Control Point 2<br/>random offset"]
        M3 --> M4["End Point"]
        M4 -.-> M5["15% chance:<br/>Overshoot + Correct"]
    end

    subgraph Typing ["Typing Simulation"]
        T1["40-60 WPM base speed"]
        T2["Pause after punctuation"]
        T3["2% thinking hesitation"]
        T4["1.5% typo + backspace"]
    end

    subgraph Delays ["Action Delays (Gaussian)"]
        D1["Minor: mean=5s, std=1.5s"]
        D2["Major: mean=75s, std=25s"]
        D3["Profile View: mean=180s, std=60s"]
        D4["Think Pause: mean=35s, std=15s"]
    end

    subgraph Scroll ["Scroll Behavior"]
        S1["Variable chunks 100-400px"]
        S2["30% reading pauses 1-4s"]
        S3["8% scroll-back"]
        S4["Inertia deceleration"]
    end
Loading

The key techniques:

  • Mouse paths follow cubic Bezier curves with Fitts's Law velocity profiling. The cursor moves slowly near the start and end points and faster in the middle, with random micro-jitter of plus or minus 2.5 pixels per step. Fifteen percent of movements overshoot the target and self-correct.
  • Click targeting avoids element centers. Click positions use a Gaussian offset within the element bounds, and the duration between mousedown and mouseup varies from 50 to 200 milliseconds.
  • Typing is simulated character by character at 40 to 60 words per minute with natural pauses after punctuation. Two percent of characters trigger a "thinking hesitation" and 1.5 percent produce a typo followed immediately by a backspace correction.
  • Delays between actions follow Gaussian distributions rather than uniform random. LinkedIn's detection systems can identify uniformly distributed delays because they look unnatural.
  • Business hours constraint ensures all actions happen between 8 AM and 6 PM in the lead's timezone, which matches how a real person would use LinkedIn.

Browser Fingerprint Manager

Every LinkedIn account gets a deterministic, consistent browser fingerprint derived from its account UUID. The fingerprint never changes between sessions for the same account, which is critical because LinkedIn cross-references fingerprint attributes and flags inconsistencies.

graph LR
    A["Account UUID"] -->|"SHA-256"| B["Seed Integer"]
    B --> C["Viewport<br/>1366x768 to 1920x1080"]
    B --> D["WebGL Renderer<br/>Intel / NVIDIA / AMD"]
    B --> E["Navigator<br/>Platform, RAM, Cores"]
    B --> F["Canvas Noise Seed<br/>Imperceptible pixel noise"]
    B --> G["Audio Context Offset<br/>Tiny float perturbation"]
    B --> H["Screen Config<br/>Resolution, Color Depth"]
    B --> I["Font Set<br/>Subset of common fonts"]
    B --> J["Locale + Timezone"]
Loading

The following properties are spoofed to create a unique but realistic browser identity:

  • Canvas (pixel-level noise invisible to the human eye)
  • WebGL (renderer string and vendor)
  • Navigator (platform, hardwareConcurrency, deviceMemory, maxTouchPoints, languages)
  • Screen (resolution, colorDepth, pixelRatio)
  • Audio Context (tiny floating-point offset)
  • Font enumeration (a consistent subset of system fonts)

Lead Management and CRM

The lead management system is built for teams that work with large prospect lists.

  • CSV import lets users upload lead lists from external tools or from LinkedIn Sales Navigator exports. LinkedIn URL list ingestion is also supported.
  • Tagging and filtering by status, industry, location, or any custom tag. Full-text search works across name, company, and tags.
  • Lead detail panel shows the full interaction history for a given prospect -- when they were contacted, what was sent, whether they replied, and which campaign they belong to.
  • CSV export for moving data into external CRM tools.

Smart Inbox

The inbox provides a unified view of all LinkedIn conversations across connected accounts.

  • All conversations from every connected LinkedIn account appear in a single interface, so users do not need to switch between accounts.
  • Users can reply to messages directly from the inbox without opening LinkedIn.
  • Messages are automatically labeled: new, replied, or follow-up needed.
  • LinkedIn is polled every 5 to 15 minutes per account to keep conversations current.

Campaigns and Sequences

Campaigns in LinkAI are not just bulk message blasts. They are multi-step sequences with conditional branching.

  • A typical sequence might look like: send connection request, wait for acceptance, send a personalized message, wait three days, check for a reply, and send a follow-up if none came.
  • Conditional logic lets the sequence branch based on outcomes. If a connection request times out after seven days, the system can fall back to an email outreach instead.
  • Auto-pause on reply ensures that prospects who respond are immediately removed from the automated sequence so a human can take over the conversation.
  • Per-prospect tracking shows exactly where each lead is in the sequence and what step comes next.

Visual Workflow Builder

The workflow builder is powered by React Flow (xyflow), the same library used by Stripe, Typeform, and OneSignal for their visual editors.

graph TD
    START["Find Leads"] --> VIEW["View Profile"]
    VIEW --> WAIT1["Wait 1 Day"]
    WAIT1 --> CONNECT["Send Connection"]
    CONNECT --> CHECK{"Accepted?"}
    CHECK -->|"Yes"| MSG["Send Message"]
    CHECK -->|"No, 7d timeout"| EMAIL["Email Fallback"]
    MSG --> WAIT2["Wait 3 Days"]
    WAIT2 --> CHECK2{"Replied?"}
    CHECK2 -->|"Yes"| DONE["Mark as Interested"]
    CHECK2 -->|"No"| FU["Send Follow-Up"]
    FU --> END["End Sequence"]
    EMAIL --> END

    style START fill:#22c55e20,stroke:#22c55e
    style CONNECT fill:#8b5cf620,stroke:#8b5cf6
    style CHECK fill:#f59e0b20,stroke:#f59e0b
    style MSG fill:#3b82f620,stroke:#3b82f6
    style EMAIL fill:#ef444420,stroke:#ef4444
Loading

Key capabilities:

  • Drag-and-drop node types: Action, Condition, Delay, and Trigger nodes can be placed and connected freely.
  • Serialization: Workflows are stored as JSON (nodes + edges + configuration) in PostgreSQL, making them portable and versionable.
  • Auto-layout with ELK.js for complex branching workflows is planned for an upcoming release.

Message Templates and Personalization

Templates support dynamic variable substitution so the same template produces different output for each lead.

  • Variables like {{first_name}}, {{company}}, {{position}}, and {{industry}} are replaced with actual lead data at send time.
  • Live preview lets users see exactly what a message will look like for a specific lead before sending.
  • A/B testing tracks which variant of a template gets better response rates, so users can iterate on their messaging.
  • AI-generated icebreakers are planned for Phase 3 and will use the lead's recent activity to craft a personalized opening line.

Analytics Dashboard

The analytics dashboard gives users a clear picture of how their outreach is performing.

  • Key metrics: connections sent, acceptance rate, reply rate, and InMail performance are displayed as summary cards.
  • Time-series charts built with Recharts show trends over time, with daily and weekly aggregation options.
  • Filtering by campaign, date range, or lead segment lets users drill into specific subsets of their data.
  • Real-time updates via WebSocket are planned for future releases.

Engagement and Enrichment

Before sending a connection request or message, it helps to warm up the relationship with lighter touches. The engagement tools automate these pre-outreach interactions.

  • Profile viewing with configurable daily limits acts as a soft introduction. The prospect sees your name in their "Who viewed your profile" list.
  • Skill endorsements automatically endorse the top skills listed on a prospect's profile.
  • Post likes engage with a prospect's recent content before making direct contact.
  • Email enrichment uses GDPR-compliant third-party services to find prospect email addresses for multi-channel outreach.
  • Multi-channel workflows combine LinkedIn and email touchpoints in a single sequence.

Landing Page

The landing page is built entirely in React and CSS with no external animation libraries. It is designed to communicate the product's value quickly through interactive elements.

  • Canvas particle field with 60 interconnected particles and dynamic connection lines that respond to mouse movement.
  • Typewriter effect cycles through key use cases: "Outreach", "Recruiting", "Lead Gen", "Pipeline".
  • Parallax mouse tracking on floating decoration cards that follow the cursor.
  • Scroll-triggered animations using IntersectionObserver with staggered reveal timing.
  • Animated counters that count up to target values as they scroll into view.
  • Interactive dashboard mockup with tabbed preview of the actual product (Dashboard, Inbox, Campaigns, Analytics tabs).
  • Visual workflow diagram showing an animated outreach sequence.
  • Pricing toggle between monthly and annual plans with an animated switch.
  • FAQ accordion with smooth expand/collapse transitions.

Data Flow Diagrams

Account Connection Flow

This diagram shows what happens from the moment a user clicks "Connect LinkedIn" in the extension through to the account being stored in the database.

sequenceDiagram
    participant Ext as Chrome Extension
    participant API as FastAPI
    participant Enc as Encryption Service
    participant Val as LinkedIn Validator
    participant DB as PostgreSQL

    Ext->>API: POST /api/linkedin/connect<br/>{li_at, jsessionid, user_agent}

    Note over API: Token is FRESH from browser<br/>Guaranteed valid

    API->>Val: validate_linkedin_session()<br/>(best-effort, may fail)
    Val->>Val: GET linkedin.com/voyager/api/me<br/>(from SERVER IP)

    alt LinkedIn allows (200)
        Val-->>API: {valid: true, name, profile_url}
    else LinkedIn blocks (302/429/timeout)
        Val-->>API: {valid: false, reason: "server_blocked"}
        Note over API: Failure is OK --<br/>token is still valid
    end

    API->>Enc: encrypt_session(cookies)
    Enc-->>API: (ciphertext, nonce)
    API->>DB: INSERT account<br/>status=WARMING_UP
    API-->>Ext: 201 Created
Loading

Health Check Flow with Grace Period

Health checks are designed to avoid false positives. During the first 24 hours after connection, failed validation from the server IP does not change the account status.

flowchart TD
    A["Health Check Triggered"] --> B{"Account age < 24h?"}
    B -->|"Yes"| C{"Age < 5 min?"}
    C -->|"Yes"| D["Skip check entirely<br/>Return current status"]
    C -->|"No"| E["Run validation"]
    B -->|"No"| E

    E --> F["Decrypt session"]
    F --> G["Call LinkedIn API<br/>from server IP"]

    G -->|"200 OK"| H["Update profile info<br/>Preserve WARMING_UP"]
    G -->|"401/403"| I{"Within 24h grace?"}
    I -->|"Yes"| J["Treat as server_blocked<br/>Do not change status"]
    I -->|"No"| K["Set status = EXPIRED"]
    G -->|"302 redirect"| L["Classify as server_blocked<br/>Do not change status"]
    G -->|"429/timeout"| L

    style D fill:#22c55e20,stroke:#22c55e
    style J fill:#f59e0b20,stroke:#f59e0b
    style K fill:#ef444420,stroke:#ef4444
    style L fill:#3b82f620,stroke:#3b82f6
Loading

Database Schema

The schema is designed around multi-tenancy. Every table includes a tenant_id foreign key that ties records to a specific organization. PostgreSQL row-level security policies enforce isolation at the database level.

erDiagram
    TENANTS ||--o{ USERS : has
    TENANTS ||--o{ LINKEDIN_ACCOUNTS : has
    USERS ||--o{ LINKEDIN_ACCOUNTS : owns
    TENANTS ||--o{ LEADS : has
    TENANTS ||--o{ TEMPLATES : has
    TENANTS ||--o{ SEQUENCES : has
    LINKEDIN_ACCOUNTS ||--o{ CONVERSATIONS : has
    LINKEDIN_ACCOUNTS ||--o{ CONNECTION_REQUESTS : sends

    TENANTS {
        uuid id PK
        string name
        datetime created_at
    }

    USERS {
        uuid id PK
        uuid tenant_id FK
        string email
        string hashed_password
        string full_name
        string role
        datetime created_at
    }

    LINKEDIN_ACCOUNTS {
        uuid id PK
        uuid tenant_id FK
        uuid user_id FK
        string linkedin_name
        string linkedin_profile_url
        text encrypted_session
        string encryption_nonce
        jsonb proxy_config
        jsonb daily_limits
        enum status
        datetime warmup_started_at
        datetime last_health_check
        jsonb fingerprint_seed
    }

    LEADS {
        uuid id PK
        uuid tenant_id FK
        string name
        string company
        string title
        jsonb tags
        string status
    }

    TEMPLATES {
        uuid id PK
        uuid tenant_id FK
        string name
        text body
        jsonb variables
    }

    SEQUENCES {
        uuid id PK
        uuid tenant_id FK
        string name
        jsonb workflow_definition
        string status
    }
Loading

API Reference

The backend exposes a RESTful API organized by domain. All endpoints (except auth and health) require a valid JWT access token.

Module Method Endpoint Description
Auth POST /api/auth/signup Register a new user and create a tenant
POST /api/auth/login Authenticate and receive JWT tokens
POST /api/auth/refresh Exchange a refresh token for a new access token
POST /api/auth/forgot-password Request a password reset email
POST /api/auth/reset-password Reset password using a token from email
GET /api/health Service health check (no auth required)
LinkedIn POST /api/linkedin/connect Connect an account via extension cookies
GET /api/linkedin/accounts List all connected accounts
GET /api/linkedin/accounts/{id} Get detailed account info and warmup progress
POST /api/linkedin/accounts/{id}/check-health Trigger a manual session health check
PATCH /api/linkedin/accounts/{id}/proxy Configure a residential proxy for the account
PATCH /api/linkedin/accounts/{id}/limits Adjust daily action limits
DELETE /api/linkedin/accounts/{id} Disconnect and remove an account
Leads GET /api/leads List leads with filtering and search
POST /api/leads Create a new lead or import from CSV
GET /api/leads/{id} Get lead details and interaction history
PATCH /api/leads/{id} Update lead information or tags
DELETE /api/leads/{id} Remove a lead
Inbox GET /api/inbox/conversations List all conversations across accounts
GET /api/inbox/conversations/{id} Get messages in a conversation thread
Templates GET /api/templates List message templates
POST /api/templates Create a new template
GET /api/templates/{id} Get template details including variants
PATCH /api/templates/{id} Update a template
DELETE /api/templates/{id} Remove a template
Campaigns GET /api/campaigns List campaigns
POST /api/campaigns Create a new campaign
Sequences GET /api/sequences List outreach sequences
POST /api/sequences Create a new sequence
Analytics GET /api/analytics/dashboard Get summary dashboard metrics
GET /api/analytics/time-series Get time-series data for charts
Engagement POST /api/engagement/view-profile Trigger a profile view action
Enrichment POST /api/enrichment/find-email Look up a prospect's email address

Interactive API documentation is available when the backend is running:


Getting Started

Prerequisites

You will need the following installed on your machine:

  • Docker and Docker Compose -- for running PostgreSQL and Redis locally.
  • Python 3.11 or later -- for the FastAPI backend.
  • Node.js 18 or later and npm -- for the React frontend.
  • Google Chrome -- for loading the extension.

1. Start Infrastructure

From the project root, bring up the database and cache services:

docker-compose up -d

Verify that both containers are running:

docker-compose ps
# linkai-db     -> postgres:5432
# linkai-redis  -> redis:6379

This starts PostgreSQL 16 with the pgvector extension on port 5432 and Redis 7 on port 6379.

2. Run the Backend

cd backend

# Create and activate a virtual environment
python -m venv .venv
source .venv/bin/activate        # macOS / Linux
# .venv\Scripts\activate         # Windows

# Install Python dependencies
pip install -r requirements.txt

# Make sure your .env file is in place (see Environment Variables below)

# Run database migrations
alembic upgrade head

# Start the API server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Once running, the API is available at:

3. Run the Frontend

cd frontend

# Install Node dependencies
npm install

# Start the development server
npm run dev

The frontend will be available at http://localhost:5173. From there you can access the landing page, create an account, and explore the full dashboard.

4. Load the Chrome Extension

  1. Open Google Chrome and navigate to chrome://extensions.
  2. Enable Developer Mode using the toggle in the top-right corner.
  3. Click Load unpacked.
  4. Select the extension/ folder from the project root.
  5. The LinkAI extension icon will appear in the Chrome toolbar.

5. Connect a LinkedIn Account

  1. Sign up at http://localhost:5173/signup and log in.
  2. Navigate to the LinkedIn Accounts page in the dashboard.
  3. Click Connect Account and follow the on-screen instructions:
    • Copy the access token shown in the dashboard.
    • Open the LinkAI Chrome extension by clicking its icon in the toolbar.
    • Paste the token into the extension and click Save.
    • Make sure you are logged into LinkedIn in Chrome.
    • Click Connect LinkedIn in the extension popup.
  4. Your account will appear with a WARMING_UP status.
  5. Over the next 21 days, daily action limits will gradually increase according to the sigmoid warmup curve described above.

Environment Variables

Create a .env file in the backend/ directory with the following variables:

# Database
DATABASE_URL=postgresql+asyncpg://linkai:linkai_dev_2026@localhost:5432/linkai

# Redis
REDIS_URL=redis://localhost:6379/0

# Authentication
SECRET_KEY=your-secret-key-change-in-production
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=30
ALGORITHM=HS256

# Session Encryption (AES-256-GCM)
# Generate a key with: python -c "from app.services.encryption_service import generate_encryption_key; print(generate_encryption_key())"
ENCRYPTION_KEY=your-base64-encoded-32-byte-key

# Frontend URL (used for CORS and email links)
FRONTEND_URL=http://localhost:5173

Make sure to replace placeholder values with real secrets before deploying to any shared or production environment.


Roadmap

The project is organized into three phases spanning 22 epics and 79 user stories.

Phase 1 (MVP)          ========================     Weeks 1-12
  Auth, LinkedIn Connect, Leads, Inbox,
  Templates, Analytics, Integrations

Phase 2 (Automation)   ............================     Weeks 10-20
  Scheduled Sends, Sequence Builder,
  Workflow Builder, Engagement, Enrichment,
  Anti-Detection Hardening

Phase 3 (Applicant)    ............................========     Weeks 18-30
  AI Job Matching, Easy Apply, Knowledge Base,
  AI Messages, Drip Campaigns, GDPR, Scale Infra

Current Status: Phase 1 is complete. Phase 2 is in progress, with Epics 9 through 14 implemented. The sequence engine, workflow builder, engagement tools, and anti-detection hardening are functional. Phase 3 work on the applicant-side features has not yet started.

credentials

email: demo@linkai.com password: Demo1234!


License

Proprietary. All rights reserved.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors