An experimental Chrome extension that reimagines LinkedIn messaging with a keyboard-driven, local-first UI. Built as a personal project to explore browser extension development with React, IndexedDB, and real-time streaming.
You need Google Chrome or any Chromium-based browser (Edge, Arc, Brave, etc.).
Add inflow to Chrome — one click, and Chrome keeps it up to date on its own.
The two manual paths below still work, and are what you want if you're hacking on inflow. Note that Chrome gives a manually loaded build a different extension ID than the store one, so the two are separate installs with separate local databases; running both means syncing twice.
- Go to the latest release
and download
inflow-<version>-chrome.zip. - Unzip it somewhere you'll keep it (the folder is the extension — don't delete it).
- Open
chrome://extensions, enable Developer mode (top right). - Click Load unpacked and select the unzipped folder.
Requires Node.js 18+ and npm.
git clone https://github.com/grinich/inflow.git
cd inflow
npm install
npm run buildThen Load unpacked the dist/chrome-mv3 folder at chrome://extensions
(with Developer mode on).
- Sign into LinkedIn in any tab.
- Open inflow.im/app, or click the inflow icon in the toolbar (pin it for easy access). From inflow.im/app you can also install inflow as a standalone desktop app (Chrome menu → Install inflow).
inflow notifies you in-app when a new release is out — see Updating.
![]() |
![]() |
- Send, receive, edit, and unsend messages with file attachments
- Optimistic sending with instant UI updates; offline actions queue and replay when back online
- Emoji reactions, read receipts, shared-post previews, and draft auto-save
- Reply to a specific message, with reply-to indicators and edited-message timestamps
- Emoji shortcode autocomplete (
:smile) and paste-to-attach for images - New conversation composer with typeahead recipient search
- Reply suggestions for incoming messages
- Inline autocomplete while composing
- Bring your own Gemini API key (set it in the app); off until configured
- Privacy note: when enabled, message text and participant names from the active conversation are sent to Google's Gemini API to generate suggestions. See Google's Gemini API terms. AI features are off until you add a key.
- Four tabs: Focused, Other, Archived, Spam
- Star, archive, move to Other, mark read/unread, mark as spam, delete
- One-click unread quick-filter toggle
- Undo for destructive actions
- Per-account IndexedDB (supports multiple LinkedIn accounts)
- Real-time local filtering across names, messages, and metadata
- Server-side LinkedIn search with pagination
- Filter autocomplete with Tab/Enter completion
| Filter | Description |
|---|---|
is:unread |
Unread conversations |
is:read |
Read conversations |
is:starred |
Starred conversations |
is:group |
Group conversations |
has:attachment |
Has attachments |
has:draft |
Has an unsent draft |
from:name |
Filter by sender |
after:YYYY-MM-DD |
Active after date |
before:YYYY-MM-DD |
Active before date |
newer:Nd |
Active within the last N days |
older:Nd |
Inactive for at least N days |
- View incoming connection requests with their notes; accept or ignore without leaving the keyboard
- Browse recent connections sorted by recency or name, with instant filtering
- One-keystroke "message this connection" that drops into the composer
G Nto open,1/2/Tabto switch Invitations / Connections,Enterto accept/message,D/X/⌫to ignore,Pto open a profile,Escback to the inbox- Routed by the URL hash —
app.html#/networkdeep-links straight in, and Chrome's back button returns to the inbox
| Key | Action |
|---|---|
J / K |
Navigate conversations |
Enter |
Open conversation |
1 / 2 / 3 / 4 |
Jump to Focused / Other / Archived / Spam |
G S / G U |
Go to starred / unread |
G N |
Go to Network (invitations & connections) |
R |
Reply (focus compose) |
Enter |
Send message (in compose) |
⌘+Enter |
Send + archive |
Shift+Enter |
New line |
Escape |
Back to list |
E |
Archive (un-archive / move to Focused in Archived tab) |
O |
Move to Other |
S |
Star / unstar |
U |
Toggle read / unread |
Shift+U |
Mark unread & go back (in thread) |
! |
Mark as spam |
P |
Open sender's LinkedIn profile |
D |
Delete conversation |
C |
Compose new message |
/ |
Focus search |
Cmd+K |
Command palette |
Z |
Undo last action |
? |
Show all shortcuts |
- 30-second background polling with SSE real-time updates
- Multi-category discovery (Focused, Other, Archived, Spam)
- Priority-based message backfill with configurable depth
- Scroll-triggered burst discovery and idle prefetch
- Pause / resume controls
- Grouped message bubbles with time separators
- Emoji reactions and read-receipt indicators
- Image lightbox, file downloads, audio/video attachments
- Light / dark / system theme
- Real-time sync progress and error logs
- Diagnostic API report
- Database stats and reset controls
- Configurable backfill window
This project is not affiliated with, endorsed by, or associated with LinkedIn or Microsoft.
This extension uses LinkedIn's undocumented internal APIs to read and send messages through your existing browser session. This may violate LinkedIn's User Agreement and could result in account restrictions.
This software is provided as-is for personal and educational use only. The author assumes no responsibility for any consequences of using it, including account suspension or data loss. Use at your own risk.
Installed from the Chrome Web Store? Chrome updates inflow on its own — there is nothing to do.
A manually loaded copy never updates itself. inflow shows a banner pointing at the store listing; moving there is the one-time fix. Because Chrome treats the store build as a separate extension, it starts with an empty local database and re-syncs your conversations from LinkedIn — unsent drafts and your Gemini API key do not carry over.
To stay on a manual install:
A. Download the latest build — grab inflow-<version>-chrome.zip from the
latest release, unzip it,
and load the unzipped folder (or replace your existing one) at chrome://extensions.
B. Rebuild from source (if you cloned the repo):
git pull
npm install
npm run buildThen go to chrome://extensions and click the reload button (↻) on the inflow
card. Your data is stored locally and is preserved across updates — the
extension uses a fixed ID, so reinstalling or moving the folder keeps your
conversations and settings.
npm run devStarts a dev server with hot reload. The extension auto-reloads in Chrome on save.
The version lives in package.json (WXT reads it for the manifest). To cut a release:
npm version minor # or patch / major — bumps package.json and creates a vX.Y.Z tag
git push --follow-tagsPushing the tag triggers .github/workflows/release.yml,
which runs the tests, builds the extension, publishes a GitHub Release with
auto-generated notes and the inflow-<version>-chrome.zip attached, and then
uploads and publishes the store build to the Chrome Web Store. Store users get
it automatically once review clears. See
docs/chrome-web-store-release.md for the
one-time OAuth setup.
The extension ID is pinned by a public
keyin the manifest. The matching private key (inflow-signing-key.pem) is gitignored and only needed for.crxsigning — keep a copy somewhere safe if you ever want it.
The Chrome Web Store signs packages itself and rejects any upload whose manifest
carries a key field, so store packages are built separately:
npm run zip:store # → dist/inflow-<version>-chrome-store.zip, no `key`Because the store assigns its own extension ID, a store install is a different
origin than a sideloaded one: it starts with an empty database and cannot inherit
conversations from an unpacked install. Upload only the -store.zip; the plain
inflow-<version>-chrome.zip is the GitHub release artifact for Load unpacked.
Chrome extension (Manifest V3) built with:
- WXT — extension framework
- React 19 — UI
- Dexie / IndexedDB — per-account local storage with live queries
- Zustand — state management
- Tailwind CSS v4 — styling
- SSE — real-time message streaming
MIT


