Every conversation, one clear line.
Threadline is a private, self-hosted tool for the conversations you are keeping alive: who you are talking to, where each conversation stands, who owes whom a reply, and what to do next.
Once a day it reads your new LinkedIn messages and your mailbox (Gmail, Outlook.com, iCloud, Yahoo, Fastmail or any IMAP mailbox), plus, if you use them, your Outlook or Google calendar, your one-to-one Telegram chats, your Slack direct messages and your work Teams chats. It throws away the noise and groups what is left into one timeline per person. An AI step, run by Claude on your own subscription, then judges each person: the status, who is waiting on whom, the next action and when it is due. You see the result on a private dashboard, and a short summary e-mail reaches you every morning, when a mailbox that can send it is connected (for example Gmail with an app password). one timeline per person. An AI step, run by Claude on your own subscription, then judges each person: the status, who is waiting on whom, the next action and when it is due. You see the result on a private dashboard, and a short summary e-mail reaches you every morning, when a mailbox that can send it is connected (for example Gmail with an app password).
Threadline started as a job-search tool and is being generalised. You pick a
preset — job search, sales outreach, fundraising, freelance clients or general
networking — or define your own categories. See
docs/customising.md.
Live demo: https://demo.threadlineapp.com
See the dashboard with made-up data before setting anything up. You need
Node.js 22 or newer; no accounts and no settings. The
green Code → Download ZIP button on this page is all the download you
need (if you already installed Threadline, part 1 of the guide, use its
threadline folder instead). Then in a terminal, inside that folder:
cd frontend
npm install
npm run demonpm install may print warnings about vulnerabilities or install scripts: they
concern developer tools only and can be ignored. Then open
http://localhost:5173. You are signed in as an invented owner who
does sales outreach. You can correct people, answer the review questions and
change the categories on the Settings page; nothing is saved, and reloading the
page starts again from the same made-up data. A demo build is made with
npm run build:demo; a normal build never contains the demo. To put the demo
online as its own website, follow docs/demo-site.md.
The home page, and the questions the AI step was unsure about:
The same people grouped by organisation, so you can see where you stand with each one as a whole:
One person's page with every message in one timeline, and the people list on a phone:
Every picture comes from the demo, so every name, company and address in it is made up.
- It is a personal tool: one copy per person, on accounts you own.
- It is read-only. It never writes to any of the places it reads, and never sends anything to anybody except the one summary to you.
- It is not a CRM, an outreach or auto-reply tool, or a hosted service. Never run one copy for several people: the database has a single owner and no separation between users.
Anyone who juggles many one-to-one conversations across LinkedIn and e-mail and keeps losing track of who is waiting for an answer: job seekers, founders raising money, freelancers, people doing sales or partnership outreach.
LinkedIn messages ─────────┐
Gmail, Outlook or any │
IMAP mailbox ────────────┼─▶ collect ─▶ drop noise ─▶ group by person ─▶ Claude judges each person
Outlook or Google calendar ┤ │
Telegram, Slack and │ │
Teams chats ─────────────┘ │
▼
your Supabase database (checked before saving)
│
┌───────────────┴───────────────┐
▼ ▼
private dashboard morning summary e-mail
Everything runs once a day on GitHub Actions in your own private copy, on your
own Claude subscription, so your computer can be off. There is no server of its
own: the backend is a command-line tool (tracker) that a Claude Code session
runs step by step, following
.claude/commands/daily-run.md. A Claude
cloud routine can run the same recipe instead (the alternative route, for
Outlook-only set-ups). The design is described in
docs/architecture.md.
| Item | What it is used for | Cost |
|---|---|---|
| A computer with macOS, Linux or Windows (10 or 11) | Running the set-up once. After that it can stay off | – |
| A paid Claude plan (Pro, Max or Team) | The Claude Code session that runs everything and does the judging. The daily run uses part of your plan's usage limits | Your existing subscription; no separate API key |
| A GitHub account | Your private copy of the code, and GitHub Actions, which starts the run every day. The installer adds the GitHub CLI (gh), which signs you in, makes your copy and saves your settings |
Free (2,000 Actions minutes a month for private repositories at the time of writing; a month of runs uses about 150–300) |
| A Supabase account (sign up with Continue with GitHub) | The database and the dashboard sign-in. The set-up creates the project after one click on Authorize. Its built-in sign-in e-mail only reaches the address the Supabase account was registered with (or members of its organisation) unless you set up your own sending service (custom SMTP) | Free plan |
| At least one mailbox: Gmail, Outlook.com/Hotmail, iCloud, Yahoo, Fastmail or any IMAP mailbox | The mail that is read (read-only). Gmail and the others need an app password, which also sends you the summary; Outlook needs a one-time sign-in | Free (Fastmail: a paid plan above Basic) |
| Optional: a LinkedIn developer app | Reading your LinkedIn messages; only for members located in the EEA or Switzerland (see below) | Free |
| Optional: a Microsoft personal account | The Outlook calendar, read with the Outlook sign-in | Free |
| Optional: a Google Calendar | Your meetings, read through the calendar's secret address: no Google sign-in. Any other private .ics address (iCloud, Fastmail, Proton) works too |
Free |
| Optional: a Telegram account | Reading your one-to-one Telegram chats with people (not groups, channels or bots). You make your own small app at my.telegram.org and log in once; the set-up walks you through it | Free |
| Optional: a Slack workspace | Reading your Slack direct messages, through a small read-only app you create in it with one link | Free |
| Optional: a work or school Microsoft account | Reading your Teams chats. Some companies block this; then only their IT admin can allow it | Free |
| Optional: Node.js 22 or newer | Only the manual way of switching on the dashboard's Refresh now button; also needed for the demo above | Free |
| Optional: an Azure account | Only to use your own Microsoft application instead of the public one | Free |
| Only for the Claude cloud route: a Gmail account connected to Claude | Sending the summary from a Claude cloud routine, when you read Outlook alone | Free |
No other paid service is needed, unless you choose a paid mailbox such as Fastmail. The dashboard is shared, at https://app.threadlineapp.com: your data stays in your own Supabase database, and only you can sign in. Setting it up takes about 20 minutes in total, making the accounts included; LinkedIn comes later, in about 5 minutes. The first summary e-mail follows about ten minutes later, if you have a mailbox that can send it, for example Gmail with an app password.
LinkedIn's official API for exporting your own messages (Member Data Portability) is available only to members located in the European Economic Area and Switzerland. Elsewhere, leave LinkedIn out: everything else works without it.
LinkedIn's copy of your messages also runs one to two days behind, so a LinkedIn message reaches Threadline a day or two after you receive it. Mail and calendar entries are read as they are at the moment of the run.
Connecting LinkedIn takes about five minutes the first time, after the rest
of the set-up: you make a small developer application on LinkedIn's pages and
uv run tracker setup linkedin walks you through it.
When the key expires, getting a new one is one command and one click on
LinkedIn's Allow button.
The easy way: let Claude do it. You already need a Claude plan, and
Claude can run the whole set-up for you: install the tools, make your private
copy, start the guided set-up and check the result, while you create the
accounts and click where it says. Keys go into a page in your browser, never
into the chat. See docs/setup-with-claude.md:
it is one sentence to paste into the Claude app.
By hand: create a free GitHub account, then a free Supabase account with Continue with GitHub. Then paste one line into a terminal.
On macOS or Linux:
curl -LsSf https://raw.githubusercontent.com/roccoterr97/threadline/main/install.sh | shOn Windows, in PowerShell:
irm https://raw.githubusercontent.com/roccoterr97/threadline/main/install.ps1 | iexWhat happens next:
- The installer adds uv, the GitHub CLI and Claude Code if they are missing
(and Git, on Windows), signs you in to GitHub in the browser, makes your
private copy called
threadline, downloads it to~/threadline, and starts the guided set-up. The first time, it may also download Python itself, which can take a few minutes and needs the internet. Pasting the line again carries on where it stopped. - The set-up runs 11 steps and asks only what you must do. You click
Authorize on a Supabase page and type the code it shows; it then
creates your project (or reuses the one named
threadline), builds the database, makes your dashboard login for your Supabase account's address and switches sign-ups off. It asks your e-mail address, tells the provider from it, and opens the page where you make the app password (Outlook signs in with Microsoft instead). The rest takes the usual answer, each said in one line with the command that changes it: your computer's time zone, the usual categories, the shared dashboard (with your personal link) and 07:00 for the daily run.uv run tracker setup --ask-everythingasks every question instead. - Last, you click Authorize on a Claude page: the key that lets GitHub use your plan goes straight to GitHub (on some computers you paste it instead). It saves your settings on GitHub, starts the first run and watches it for two minutes (so a problem is said at once), and checks every connection. Its last screen names your personal link and the e-mail address that signs in, and lists every choice with the command that changes it. The first summary e-mail arrives about ten minutes later, then every day at the time you chose (this needs a mailbox with an app password; with Outlook alone the set-up tells you the options).
- LinkedIn is left for later, to keep the set-up short: the last screen
names its command (
uv run tracker setup linkedin, about 5 minutes, for profiles located in the EEA or Switzerland), and once the first summary is in, the dashboard and the morning e-mail offer it too. LinkedIn, Google Calendar, Telegram, Slack, Teams, the dashboard's Refresh now button and the Claude cloud route are optional extras:uv run tracker setup extras, any time. Your own copy of the dashboard on Netlify is optional too (uv run tracker setup dashboard). Refresh now also switches on the on-time morning start: GitHub often starts its scheduled runs hours late, so your Supabase project starts the daily run at your time instead, and GitHub's own schedule stays as a backup.
Add --browser to any uv run tracker setup command to have the questions
asked on a page in your web browser instead of in the terminal: keys go into
hidden fields, and the page is served to your computer only.
docs/setup-your-accounts.md walks through
every step, with what you should see and a short list of the words it uses
(IMAP, app password, secrets…). To come back to the set-up later, open a
terminal and type cd ~/threadline/backend, then uv run tracker setup.
Day-to-day operation — what to do when something fails, renewing the LinkedIn
key and the yearly Claude key, redoing the Microsoft sign-in, changing the time,
pausing and resuming — is in
docs/operations.md.
Run every command from the backend/ folder as uv run tracker ….
uv run tracker --help, or --help after any command, shows the same
information.
| Command | What it does |
|---|---|
tracker setup |
The guided set-up: every unfinished core step in order, then a final check of every connection. It asks only what you must do (the Supabase code, your e-mail address and its app password) and takes the usual answer for the rest, listing every choice at the end with the command that changes it. Running it again carries on where it stopped |
tracker setup --ask-everything |
The same, asking every question instead of taking the usual answers |
tracker setup extras |
The optional steps, in order, each skippable: linkedin, google-calendar, telegram, slack, teams (only named there: run it by name), refresh and cloud |
tracker setup <step> |
Run one step alone, even if it was done before, asking all its questions. The core steps, in order, are supabase, encryption, database, login, categories, timezone, mailbox, microsoft, dashboard, schedule and github; the extras are linkedin, google-calendar, telegram, slack, teams, refresh and cloud |
tracker setup [<step>] --browser |
The same, asked on a page in your web browser instead of the terminal; what is said and asked is still shown in the terminal, answers never are |
tracker setup supabase |
Sign in to Supabase in the browser (or paste an access token), create your project (or reuse one), and save its address and keys; the sign-in is never saved. Typing an existing project's address and keys is also offered |
tracker setup mailbox |
Choose the mailbox to read (the full run asks only the address and tells the provider from it); for Gmail and other IMAP mailboxes, check an app password live and store it encrypted (for a custom provider it also asks for the sending server, SMTP host and port) |
tracker setup schedule |
Write the daily time and time zone into the GitHub workflow, and into the database for the on-time morning start; commit and push the workflow only after a yes |
tracker setup github |
Save your settings and the Claude key as your repository's Actions secrets and variables (with gh), or list the names to add by hand; settings you cleared locally are removed from GitHub too. With gh it then switches the workflow on and starts the first daily run after a yes (the full run does both without asking). It only ever uses your own private copy |
tracker setup google-calendar |
Paste your Google Calendar's secret address in iCal format (the step shows the three clicks to copy it); it is read once to check it, saved, and sent to GitHub without a question. Enter skips |
tracker setup telegram |
Make your own small Telegram app at my.telegram.org (the step shows how), then log in once with your phone number and the code Telegram sends; saved and sent to GitHub without a question |
tracker setup slack |
Create a small read-only Slack app from one prefilled link, install it and paste its User OAuth Token; checked, saved and sent to GitHub. Run it again to add another workspace |
tracker setup teams |
Sign in to Teams with a work or school account (its own sign-in, apart from Outlook) and switch Teams chats on, here and on GitHub. Asks nothing else |
tracker setup refresh |
Switch on the dashboard's Refresh now button: deploy the refresh-now function and its settings through Supabase's Management API, with a GitHub key that can only start your workflow. It also switches on the on-time morning start: a timer in your database that starts the daily run at its time, every 15 minutes checking whether it is due |
tracker setup cloud |
The Claude cloud route, the alternative to GitHub: what a Claude cloud routine needs |
tracker doctor |
Check that every account and setting is in order |
tracker sample load |
Write made-up sample records, their dates moved so they look as fresh today as on the day the sample was written; running it twice changes nothing |
tracker sample clear |
Remove the made-up sample records, leaving real data untouched |
| Command | What it does |
|---|---|
tracker microsoft login |
Sign in to the mailbox once; the key then renews itself |
tracker microsoft forget |
Remove the stored mailbox key, so the next run needs a new sign-in |
tracker collect linkedin [--since YYYY-MM-DD] [--show-folders] |
Read the LinkedIn archive and store the recent conversations; --show-folders also prints the distinct LinkedIn folder values |
tracker collect email [--since YYYY-MM-DD | --refresh] |
Read every mailbox you set up (Outlook, Gmail…) and store the conversations worth keeping; --refresh reads only what is new since the mailboxes were last read successfully |
tracker collect calendar |
Read the Outlook calendar and store the meetings with other people |
tracker collect slack [--since YYYY-MM-DD] |
Read the direct messages of every Slack workspace you set up |
tracker collect teams [--since YYYY-MM-DD] |
Read your Teams one-to-one and group chats and store the ones written in recently |
tracker collect telegram [--since YYYY-MM-DD] |
Read your one-to-one Telegram chats with people and store the recent ones |
tracker collect google-calendar |
Read the Google calendar from its secret address and store the meetings with other people |
tracker collect all [--since YYYY-MM-DD | --refresh] [--record] [--run ID] |
Read every source at the same time, then store them one after the other. --record also records each source as its step of the run and carries on past a source that failed; with --refresh it records into the refresh that is open, never the daily run — this is the command the daily run uses |
tracker people list |
Show everybody the collectors have found, most recent first |
tracker people merge |
Join the records you have confirmed are one person |
tracker people link |
Ask about records that look like one person or one opportunity |
tracker people tidy |
people merge, then people link, as one command; if one fails its code is printed and the other still runs — this is the command the daily run uses |
tracker people untangle |
Split up records that were really a shared sender, such as a hiring system |
Without --since, the first run reads the last 30 days and later runs continue
from the last successful run.
| Command | What it does |
|---|---|
tracker ai export [--limit N] [--batches DIR] [--results DIR] [--record [--refresh]] [--run ID] |
Write one file per group of people who still need a verdict, and make the directory their verdicts go in. --record also records the assess step of the run when there is nobody to assess; with --refresh, into the refresh that is open rather than the daily run |
tracker ai import [--results DIR] [--batches DIR] [--record [--refresh]] [--run ID] |
Check the verdict files, save the ones that pass, and delete the files it applied. --record also records the assess step of the run: people assessed, and how many were sent to review; with --refresh, into the refresh that is open rather than the daily run |
tracker ai status [--results DIR] [--batches DIR] |
Show what is waiting: people to judge, files to import, questions to answer |
tracker ai clean |
Remove every exchanged file, including batches that were never answered |
The judging itself happens in Claude Code with the /assess recipe
(.claude/commands/assess.md); no command
calls an AI service.
| Command | What it does |
|---|---|
tracker profile choose [--preset NAME] [--guide PATH] |
Pick a preset, keep the suggested categories you use and add your own; saved to the database, and the assessment guide is rebuilt; --guide PATH chooses where it is written (default docs/assessment-guide.md) |
tracker profile check [--file PATH | --preset NAME] |
Check your profile (or a shipped preset) and print its suggested categories and stage labels; changes nothing |
tracker profile apply [--file PATH] [--categories] [--guide PATH] |
Save the stage labels and suggestions and rebuild the assessment guide from your categories (written to docs/assessment-guide.md, or to --guide PATH); --categories also replaces your categories with the file's list |
See docs/customising.md.
| Command | What it does |
|---|---|
tracker run start [--trigger github|cloud|refresh|mac|manual] [--prepare] [--force] |
Open today's run and print its identifier (default trigger: cloud). --prepare goes on to tracker healthcheck and tracker profile apply and ends with ready: yes or ready: no — this is how the daily run opens. It refuses while another run of the same kind is going; --force starts anyway and closes that one as interrupted |
tracker healthcheck |
The daily run's own check of the configuration, the database and the secret store, run by run start --prepare. By hand, tracker doctor says more |
tracker run step --step STEP --result success|failed [--found N] [--new N] [--error-code CODE] [--error-detail TEXT] [--run ID | --refresh] |
Record what one part of the run did. STEP is collect_linkedin, collect_email, collect_calendar, collect_google_calendar, collect_telegram, collect_slack, collect_teams, assess or summary_email. --refresh records into the refresh that is open rather than the daily run |
tracker run finish [--run ID | --refresh] [--clean] |
Close the run with the status its steps add up to. --refresh closes the refresh that is open rather than the daily run. --clean then removes the exchanged files, as tracker ai clean does — this is how the daily run closes |
tracker summary build [--out PATH] [--run ID] [--send-again] |
Write the morning summary to a file, ready to send, and say how it is sent (delivery: smtp or gmail_connector). When another daily run already sent today's summary it writes nothing, records the e-mail as skipped and prints summary skipped · …; --send-again builds it anyway |
tracker summary send [--file PATH] [--run ID] [--send-again] |
Send that file from your own mailbox by SMTP, exactly as built and only to your configured recipient, and record the step. Like summary build, it skips the e-mail when today's summary already went out with another daily run, unless given --send-again |
The workflow .github/workflows/threadline-run.yml
runs /daily-run every day, or by hand with mode daily or refresh (new
messages only, no e-mail). Without its secrets it finishes green and does
nothing. With the on-time morning start switched on, your Supabase project
starts the daily run at its time, and a run started by GitHub's own (often
late) schedule stops by itself when the day's run already started
(docs/refresh-now.md).
Choose your categories on the dashboard's Settings page (it asks before
removing one), or in the terminal with tracker profile choose. Categories, presets and wording are explained in
docs/customising.md. What every status and
waiting-on value means is defined in
docs/assessment-guide.md.
- Everything lives in accounts you own: your database, your GitHub repository and your Claude subscription. The shared dashboard page only shows what your own database sends it after you sign in. Nothing is sent to the project's authors. The Claude key goes only into your own repository's secrets; Threadline never stores it.
- The mailbox and calendar are read with read-only permissions. Nothing is
ever written to LinkedIn, the mailbox, the calendar or any chat. The Google
calendar's secret address, the Telegram login and the Slack keys are kept
like passwords: in
.envand as GitHub secrets, never in a log. - Message text is treated as untrusted input. Only a restricted helper with no shell and no network reads it, and its answers are validated strictly before anything is saved, so an e-mail saying "ignore your instructions" cannot act.
- Conversations judged to be noise keep no subject and no text. The files that carry message text to the AI step are deleted once their verdicts are saved.
- The dashboard's public key reads nothing on its own: the database only answers the one signed-in owner.
- Notes you type on a person's page are yours alone: they are never sent to the AI step and never put in the summary e-mail.
The full model — what is stored where and how untrusted text is contained —
is in SECURITY.md. Report vulnerabilities privately through
GitHub, as described there.
| Folder | What is in it |
|---|---|
backend/ |
The Python jobs and the tracker command-line tool |
backend/tests/ |
Unit tests; they never touch the network |
frontend/ |
The dashboard (Vite, React, TypeScript) |
website/ |
The public website: what Threadline is, the guided set-up, the way to your dashboard (Vite, React, TypeScript) |
supabase/migrations/ |
The database structure and its access rules, one file per change |
.claude/ |
The daily-run and assessment recipes, the assessor helper and the session's permissions |
.github/workflows/ |
The checks every change must pass, and threadline-run.yml, the daily run |
docs/ |
Setup, operations, architecture, the assessment guide, the coding standards and the README's pictures (docs/images/) |
cd backend
uv sync
uv run ruff check .
uv run pyright
uv run pytest -q
cd ../frontend
npm ci
npm run lint
npm run typecheck
npm test
npm run buildThe home-screen icons in frontend/public/icons/ are drawn by
npm run icons from the logo and the colour tokens; run it again after
changing either, and commit the new files.
All of these run in CI on every push and pull request. See
CONTRIBUTING.md before opening a pull request, and
CLAUDE.md for the engineering standards.
MIT © 2026 roccoterr97




