ANAlpesh Nakrani
SolutionsBlogBooksPraiseAboutWork with me
Solutions/Buyer-intent prospecting

Jev field guide · sales

Stop scoring records. Score the moment someone asks.

A prospect list tells you who exists. A public "what should we use for…" post tells you who is buying this month. Here are two small Jev jobs: find the people asking, then check every message is written for the person who will read it.

By Alpesh Nakrani · Updated September 27, 2026

3questions per post
1call per post
~$0.07per 1,000 posts
0messages sent by software

Cost is arithmetic, not a benchmark: ~1,500-token posts at $0.042 per 1M input tokens (Vercel pricing).

TL;DR

  • Look where buyers ask for recommendations, not where they talk about the topic. Source choice matters more than the model.
  • Ask three questions per post in one call: buying, live pain, and "is this a vendor?"
  • Before sending, score the person and the message together. The same message can fit one reader and miss the next.
  • The software shortlists and flags. A person reads every lead and sends every message.
01/Why

Signals beat records

I have written about why a big prospect database is not a pipeline. Most records describe companies that fit your ICP on paper and have no reason to talk to you this quarter. The useful unit is a buying signal: a moment when someone's situation changed and they said so.

The clearest public signal is a person asking a room for a recommendation. Reading every such post by hand doesn't scale, and keyword alerts can't tell a buyer from a vendor using the same words. That judgment is what Jev is for. New to it? Start with the Jev quickstart.

02/Source

Search where people ask, not where they talk

A keyword match is not a lead. Threads where people argue about a topic are full of practitioners and vendors. Threads where someone asks "what do you use for…" are full of buyers. Point the script at the second kind:

  • Ask HN posts, filtered by a word your buyers use. The script below uses the free Hacker News search API.
  • Recommendation threads in the subreddit or Slack community where your buyers complain.
  • Your own inbound: form fills, demo requests, and replies are the best state you have.

This is the same idea as treating an ICP as a situation, not a description: you are looking for the situation, stated in the buyer's words.

03/Questions

Three questions, one call per post

Jev evaluates every question against the same post in parallel, so three questions cost about what one does.

QuestionWhat it catches
buyingThe author is looking for what you sell, or something that does the same job, and would welcome a reply.
painThe author describes a problem their own team has right now. Use it to sort, not to filter.
vendorThe author is promoting, launching, or hiring. The filter most people skip.

Keep posts where buying clears about 0.6 and vendor stays under 0.5, then sort by pain. Those are starting numbers. Label 30 posts yourself and adjust.

Why the vendor question matters. The people who write most about your buyer's problem are the ones selling a fix. Without this filter, launch posts and agency pitches crowd out the one person who actually asked.

04/Script 1

Find buyers

Set what you sell and a word your buyers use when they ask. You get a CSV of posts worth reading. Swap the fetch for another source once it works.

find-buyers.mjs
// find-buyers.mjs  |  npm install ai  |  node --env-file=.env find-buyers.mjs
import { experimental_evaluate as evaluate } from 'ai';
import { writeFileSync } from 'node:fs';

const OFFER = 'a fractional RevOps lead who fixes CRM and pipeline reporting';  // what you sell, plainly
const QUERY = 'CRM';        // a word your buyers use when they ASK for help
const DAYS = 90;            // how far back to look
const BUY_MIN = 0.6;        // keep: likely buyer...
const VENDOR_MAX = 0.5;     // ...and probably not a seller

// Hacker News "Ask HN" posts: people asking a room for advice. Free, no key.
const since = Math.floor(Date.now() / 1000) - DAYS * 86400;
const url = 'https://hn.algolia.com/api/v1/search?tags=ask_hn&hitsPerPage=50' +
  '&numericFilters=created_at_i>' + since + '&query=' + encodeURIComponent(QUERY);
const hits = (await (await fetch(url)).json()).hits;

const posts = hits.map((h) => ({
  link: 'https://news.ycombinator.com/item?id=' + h.objectID,
  text: (h.title + '\n\n' + (h.story_text ?? '')).replace(/<[^>]+>/g, ' ').slice(0, 8000),
}));

const questions = {
  buying: { type: 'boolean', instructions: 'The author is currently looking for ' + OFFER +
    ', or for something that would do the same job, and would welcome a relevant reply.' },
  pain:   { type: 'boolean', instructions: 'The author describes a problem their own team has right now.' },
  vendor: { type: 'boolean', instructions: 'The author is promoting, launching, or hiring for their own product or service.' },
};

async function pool(items, size, fn) {   // a few at a time; bursts get rate-limited
  const out = []; let i = 0;
  await Promise.all(Array.from({ length: size }, async () => {
    while (i < items.length) { const n = i++; out[n] = await fn(items[n]); }
  }));
  return out;
}

const scored = await pool(posts, 3, async (post) => {
  const { answers } = await evaluate({ model: 'typesafe-ai/jev', state: post.text, questions, maxRetries: 2 });
  return { ...post, buying: answers.buying.probability, pain: answers.pain.probability, vendor: answers.vendor.probability };
});

const leads = scored
  .filter((p) => p.buying >= BUY_MIN && p.vendor < VENDOR_MAX)
  .sort((a, b) => b.pain - a.pain || b.buying - a.buying);   // live pain first

writeFileSync('leads.csv', ['link,buying,pain,vendor',
  ...leads.map((p) => [p.link, p.buying, p.pain, p.vendor].map((v) => (typeof v === 'number' ? v.toFixed(2) : v)).join(','))].join('\n'));
console.log(leads.length, 'of', posts.length, 'posts look like buyers -> leads.csv');
05/Script 2

Check every message before it goes

The quiet failure in outbound is the right message sent to the wrong person. Mail merge does it at scale and nobody notices until reply rates fall. So score the recipient and the message together: does it fit this person, would they reply, and could it have gone to anyone?

check-messages.mjs
// check-messages.mjs  |  node --env-file=.env check-messages.mjs
// outreach.json: [{ "who": "Head of Sales, 12 SDRs, Series B fintech", "message": "..." }, ...]
import { experimental_evaluate as evaluate } from 'ai';
import { readFileSync } from 'node:fs';

const drafts = JSON.parse(readFileSync('outreach.json', 'utf8'));

for (const d of drafts) {
  const { answers } = await evaluate({
    model: 'typesafe-ai/jev',
    state: 'RECIPIENT: ' + d.who + '\n\nMESSAGE THEY WILL RECEIVE:\n' + d.message,
    questions: {
      fit:   { type: 'boolean', instructions: 'This message was written for someone in this recipient\'s role, with a problem this recipient plausibly has.' },
      reply: { type: 'boolean', instructions: 'This recipient would reply to this message.' },
      generic: { type: 'boolean', instructions: 'This message could be sent unchanged to almost anyone.' },
    },
    maxRetries: 2,
  });
  const fit = answers.fit.probability, reply = answers.reply.probability, generic = answers.generic.probability;
  const verdict = fit < 0.5 ? 'WRONG PERSON: do not send'
    : generic >= 0.5 ? 'TEMPLATE: rewrite for this person'
    : reply < 0.5 ? 'WEAK: rewrite and re-check'
    : 'READY: read it, then send it yourself';
  console.log(fit.toFixed(2), reply.toFixed(2), generic.toFixed(2), verdict, '|', d.who);
}
ResultWhat to do
Fit under 0.5Wrong person. Don't send. Rewrite for them or drop them.
Generic 0.5 or higherTemplate. It isn't written for anyone. Rewrite it.
Fit fine, reply under 0.5Right person, weak message. Rewrite and check again.
All clearRead it yourself, then send it yourself.

This is the check I would put in front of any founder-led outbound motion: cheap enough to run on every message, strict enough to catch the embarrassing ones.

06/Agent build

Or paste this into Claude Code or Codex

It carries the three questions, both thresholds, the message check, and the rule that nothing gets sent by software.

Prompt for Claude Code / Codex
Build me a two-part prospecting workflow using Jev through Vercel AI Gateway. Research and review only: you never contact anyone.

PART 1: FIND BUYERS
1. Ask me what I sell in one plain sentence, and where my buyers ask for help (Ask HN, a subreddit, a forum). Wait for my answer.
2. Pull the last 90 days of posts from that source. Prefer places where people ASK for a recommendation over places where they merely mention a topic.
3. Use experimental_evaluate from the ai package with model 'typesafe-ai/jev'. ONE call per post, the post as the state, three boolean questions together:
   - buying: "The author is currently looking for <what I sell>, or for something that would do the same job, and would welcome a relevant reply."
   - pain: "The author describes a problem their own team has right now."
   - vendor: "The author is promoting, launching, or hiring for their own product or service."
4. Keep posts where buying >= 0.6 AND vendor < 0.5. Sort by pain, then buying.
5. Run 3 calls at a time. Write leads.csv (link, buying, pain, vendor) and stop to show me.

PART 2: CHECK MESSAGES
6. For each lead I approve, I will give you one line on who they are and the message I plan to send. Save them to outreach.json.
7. One call per message: state = recipient line + exact message. Questions: fit ("written for someone in this role with this problem"), reply ("this recipient would reply"), generic ("could be sent unchanged to almost anyone").
8. Verdicts: fit < 0.5 = wrong person, do not send. generic >= 0.5 = template, rewrite. reply < 0.5 = weak, rewrite. Otherwise ready for me to read.

Never send, post, comment, DM, or connect with anyone. Respect each source's terms and rate limits. I review every lead and send every message myself.
07/Trade-offs

What this will not do

  • It won't make a thin source rich. If nobody in your channel is asking, a better model finds nothing. Change the source first.
  • Mentioning is not wanting. Someone building a CRM, or asking about a personal one, uses the same words as a team buyer. Word the buying question around your actual offer.
  • Public posts have rules. Respect each platform's terms, rate limits, and norms. Reply as a person, with something useful, or not at all.
  • Scores are a shortlist, not a pipeline. What turns a lead into pipeline is still the conversation, which is why I keep pipeline quality separate from lead volume.

If LinkedIn is your main channel, the free LinkedIn Forger applies the same research-then-review approach to people and company pages.

08/FAQ

Prospecting with Jev: common questions

How does Jev find prospects who are ready to buy?

It does not search. Your script pulls posts from places where people ask for recommendations, and Jev scores each post on typed questions: is the author looking to buy what you sell, do they describe a live problem, and are they a vendor. You keep the posts that clear your thresholds.

Why add a vendor question?

The people who write most about a problem are often selling a fix for it. Without a vendor filter, launch posts, job ads and agency pitches crowd the top of the list because they use the same words as buyers.

Can Jev check a sales email before I send it?

Yes. Put a one-line description of the person and the exact message into the state, then ask whether the message was written for someone in their role with their problem, and whether they would plausibly reply. Low fit means the message is going to the wrong person.

Should Jev send the messages automatically?

No. Use it to shortlist and to catch bad pairings. A person should read every lead and send every message. Automated sending on public communities also breaks most platforms' rules.

What does it cost to score posts with Jev?

At $0.042 per million input tokens, a 1,500-token post with three questions costs about $0.00007. Scoring 1,000 posts costs roughly seven cents.

Sources

Sources and further reading

Ask AI about finding buyer-intent sales prospects and checking outreach with Jev