Skip to content
Internal202608 / 12

Signal Desk

Reddit lead monitoring: find buying signals, score them, draft the reply

Pipeline steps
4
Score components
6
Screening batch size
20 posts
Server routes
8
Dashboard views
9
Model providers
3

Signal Desk watches Reddit for people who are about to buy. You describe an offer in plain language, the model proposes keywords, subreddits, and a screening brief, and each run searches Reddit, screens posts against your ICP in batches, scores what survives on how worth replying to it is, and queues the best for a drafted reply. Everything, including rejected posts, is kept so you can tell whether your criteria are too tight or too loose.

My part. Built with a collaborator who shipped the initial monitoring dashboard; I wrote most of what followed, including the campaign model and generated targeting, qualification scoring, the triage queue, the outreach agent, alerts, post ingestion, the humanizer, and the move from browser storage to PostgreSQL.

The problem

Reddit threads where someone asks for a tool recommendation are high-intent leads, but they are buried in noise, they age quickly, and many communities forbid commercial replies outright. Watching dozens of subreddits by hand does not scale, keyword alerts flood you with irrelevant posts, and a model that screens posts one at a time burns request quota. The hard part is three separate judgements: is this post relevant, is it worth replying to today, and are you even allowed to name your product in that subreddit.

How it is built

A campaign is one offer aimed at one audience. Instead of a hand-written screening prompt it carries a short structured brief that renders into the rubric the classifier reads, while the output contract stays fixed in code so nothing typed can break parsing.

A run makes one search request per keyword with subreddits combined into a single query, drops posts the campaign has already seen before any model call, screens survivors in batches of twenty, then scores them. The score has six components and is recomputed on every read, so freshness decays with the clock rather than freezing at run time. Posts are also labelled by the kind of ask, since a comparison post and a troubleshooting post want opposite replies.

Subreddit reply policy is a separate axis from qualification: a module reads a community's published rules and caches a verdict on whether a commercial reply is welcome, shared by every campaign. Reply drafts follow a voice document and receive the screening notes, with an optional paragraph-by-paragraph humanizing pass.

Storage moved from browser localStorage, which silently refused writes past about 5 MB, to PostgreSQL. Each entity is a row with its full object in jsonb plus generated columns for the fields worth querying. The client reads one snapshot per page load and debounces writes carrying only changed rows. Server routes are stateless; API keys travel with each request and are scrubbed from error messages.

What it taught me

  • When a provider meters requests rather than tokens, batching twenty posts per call turned a 64-call run into four.
  • Whether you may reply in a subreddit and whether a post is worth replying to are different questions; modelling policy per community, cached, kept hot leads from becoming rule violations.
  • localStorage fails silently past about 5 MB, and empty must never be confused with unreachable: a failed load refuses to hydrate rather than presenting a blank store the next write would make real.

Architecture

The browser holds an in-memory snapshot of the store and talks to a handful of stateless Next.js route handlers. The monitor route searches Reddit, dedupes, and sends batches to the model; the draft route writes replies; the store route persists diffs to PostgreSQL. Subreddit rule checks and Slack alerts are their own routes.

  1. Layer 1

    • DashboardClient

      in-memory snapshot, debounced writes

      → Run monitor route (run campaign) → Draft route (draft reply) → Store route (sync diffs) → Slack webhook (alert via relay)

  2. Layer 2

    • Run monitor routeService

      search, dedupe, screen, qualify

      → Reddit search API (search) → Model provider (screen batches)

    • Draft routeService

      reply drafting with screening notes

      → Model provider (generate)

    • Store routeService

      GET, diff POST, replace PUT

      → PostgreSQL (upsert)

    • Slack webhookExternal

      fresh strong-match alerts

  3. Layer 3

    • Reddit search APIExternal

      one request per keyword

    • Model providerModel

      batched screening, drafting

    • PostgreSQLStore

      jsonb rows, generated columns

ClientServiceStoreExternalModel

Stack

Frontend

  • Next.js 16 (App Router)
  • React 19
  • Tailwind CSS v4
  • shadcn

Backend

  • Next.js route handlers (Node)

Data

  • PostgreSQL (jsonb + generated columns)

AI

  • Vercel AI SDK
  • Gemini, OpenAI, or Anthropic models

Infrastructure

  • Reddit search API
  • Slack incoming webhooks