plzknockreply-classifier / v0.1

billing classifier

Here’s exactly how a reply gets counted.

This is the whole classifier. It takes the product, prior thread, and latest reply, then returns interested, not_interested, or neutral. Only interested is billable.

classify-reply.ts
import { openai, type OpenAIResponsesProviderOptions } from '@ai-sdk/openai';
import { generateText, Output } from 'ai';
import { z } from 'zod';

type EmailMessage = {
  from: string;
  to: string[];
  subject: string;
  body: string;
  sentAt: string;
};

type ClassifyReplyInput = {
  product: { name: string; website: string; description: string };
  thread: EmailMessage[];
  reply: EmailMessage;
};

const replyClassification = z.object({
  verdict: z.enum(['interested', 'not_interested', 'neutral']),
  reason: z.string().describe('One short factual sentence.'),
  evidence: z.string().describe('The smallest exact excerpt supporting the verdict.'),
});

const system = [
  'You are the billing classifier for PlzKnock cold outreach.',
  'Classify only the latest inbound reply, using the prior thread as context.',
  'interested: they clearly want to continue — e.g. ask for a call, demo, pricing, trial, proposal, or relevant details.',
  'not_interested: they clearly decline, unsubscribe, complain, or say the offer is not relevant.',
  'neutral: automated, out-of-office, ambiguous, merely polite, wrong-person, or otherwise neither clearly interested nor clearly not interested.',
  'Do not infer interest from friendliness, opens, clicks, or sentiment.',
  'Only interested is billable. When evidence is weak, return neutral.',
].join('\n');

export async function classifyReply(input: ClassifyReplyInput) {
  const { output } = await generateText({
    model: openai.responses('gpt-5.6-luna'),
    output: Output.object({
      name: 'reply_classification',
      description: 'Billing classification for one inbound cold-email reply.',
      schema: replyClassification,
    }),
    system,
    prompt: JSON.stringify(input),
    maxOutputTokens: 200,
    providerOptions: {
      openai: {
        reasoningEffort: 'low',
        textVerbosity: 'low',
        store: false,
      } satisfies OpenAIResponsesProviderOptions,
    },
  });

  return output;
}

Think we counted one wrong?

Send us the reply. A human will review it. If it clearly doesn’t count, we refund the $7.

Request refund