Skip to main content

AI-powered LinkedIn outreach message generator

Project description

tmessage

A CLI tool that generates personalized LinkedIn outreach messages using AI. Built for sales and account teams who need to send high-quality, contextually relevant messages at scale — without spending 10 minutes researching each person manually.

Write-up

How I built this →

Demo

Loom walkthrough link here


Installation

Requirements

Install via Homebrew

brew install pipx
pipx install tmessage-cli

First run

On first run, tmessage will prompt you to enter your API keys. These are saved to ~/.config/tmessage/.env and reused on every subsequent run — you only need to do this once.


Usage

tmessage

The tool will prompt you for:

  1. Name — the person's first and last name
  2. LinkedIn description — paste the job description section from their LinkedIn profile, then press Ctrl+D when done

tmessage then runs a three-stage pipeline and outputs a ready-to-send LinkedIn message. Press y when prompted to generate another message, or n to exit.


How it works

Pipeline overview

LinkedIn description paste
        │
        ▼
┌─────────────────────┐
│  Stage 1: Extraction │  Qwen3-30B
│  company, title,     │
│  key_facts,          │
│  persona_inference   │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  Stage 2: Research  │  Tavily Search
│  title relevance    │
│  + company search   │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│  Stage 3: Message   │  DeepSeek-V3.2
│  generation         │
└─────────────────────┘

Stage 1 — Person extraction

The user pastes the raw LinkedIn job description block. A fast, lightweight model (Qwen3-30B) extracts structured information:

  • company_name — extracted from the pasted text
  • job_title — extracted from the pasted text
  • key_facts — a list of concrete responsibilities and activities, close to paraphrasing. Each fact describes what the person does, not how they do it.
  • persona_inference — a list of inferred characteristics about how this person operates (e.g. hands-on, technically engaged, builds from scratch). Critically, this list is only populated when the inference is directly supported by specific language in the source text. If nothing is confidently inferable, this returns an empty list.

Design decision: the persona_inference constraint — preferring honest-but-generic over confident-but-wrong — is a core principle of the tool. A message that admits it doesn't know much about someone is better than one that fabricates detail and sounds off.

Stage 2 — Company research

The job title is first distilled to its most AI/tech-relevant component (e.g. "VP of Generative AI Marketing" → "Generative AI"). This focused query is then used to search Tavily for recent, role-specific company news and initiatives — scoped to the last 365 days to keep results current.

Design decision: searching by distilled title rather than full title produces more relevant results. A full title like "Vice President & Global Head of Generative AI // Marketing Transformation Office" retrieves noisier results than "Generative AI."

Stage 3 — Message generation

A larger, higher-quality model (DeepSeek-V3.2-fast) writes the final message using:

  • Structured person context from Stage 1
  • Tavily search results from Stage 2
  • A library of real example messages to match tone and style

The model follows explicit rules: only reference facts supported by the provided context, don't fabricate a Tavily use case if one isn't evident, and write under 100 words with a low-pressure ask. If search results aren't relevant, it falls back to a generic message rather than forcing a connection that isn't there.

Design decision: two separate models for extraction vs. message generation. Extraction is a structured, deterministic task — a smaller, faster model handles it well. Message generation requires more nuance and writing quality, so a stronger model is used only where it matters.


Design philosophy

  • Prefer honest-but-generic over confident-but-wrong. The tool will not fabricate relevance or invent personal detail. A generic message is better than a weird one.
  • Minimize user input. The user provides a name and a LinkedIn paste — nothing else. All structure is inferred.
  • Degrade gracefully. If search returns nothing useful, the tool still produces a message. It doesn't stall or crash.
  • Speed over completeness where possible. Smaller models are used for simpler tasks to keep the pipeline fast enough for batches of 50+ people.

Tech stack

  • LangChain — LLM orchestration
  • Nebius — model hosting (Qwen3-30B, DeepSeek-V3.2-fast)
  • Tavily — web search API
  • Typer — CLI framework
  • Rich — terminal formatting

Project structure

tmessage/
├── tmessage/
│   ├── __init__.py          # main pipeline and CLI entrypoint
│   └── example_messages.txt # message style templates
├── pyproject.toml
├── requirements.txt
└── README.md

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tmessage_cli-0.1.3.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tmessage_cli-0.1.3-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file tmessage_cli-0.1.3.tar.gz.

File metadata

  • Download URL: tmessage_cli-0.1.3.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tmessage_cli-0.1.3.tar.gz
Algorithm Hash digest
SHA256 84aa21e9ccc53b0953d99d6fa0aaf511866733f970ff99d8de9b4462d427a7a5
MD5 3b061f24019dadf50ad8a81daf2882b0
BLAKE2b-256 616864f70b8bf54a2ca80cc7d18bf4978e3b886fb68a23f2b485c07877aca084

See more details on using hashes here.

File details

Details for the file tmessage_cli-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: tmessage_cli-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 11.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tmessage_cli-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 31912b1de7a18bc7b4991ce7240c459f84f091b5aeb49f06da37d78ef54aac77
MD5 a630ea19d3d6c92ee7324e87793fc894
BLAKE2b-256 a30a2b4fe5a3262bbe77f32f248b533ac174ccff3e8fc56d9f69eb18962fcf30

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page