hir-120: warm-reply triage UI (intent buckets + 1-click follow-up)#14
hir-120: warm-reply triage UI (intent buckets + 1-click follow-up)#14jaredzwick wants to merge 1 commit intopypesdev:mainfrom
Conversation
Classifies every inbound reply into one of four intent buckets (interested / objection / not_now / out_of_office) the moment it lands, drafts a tailored follow-up, and surfaces both at /dashboard/replies with [Send] / [Edit] / [Archive] per card. Send reuses the existing email_queue pipeline so the warm-reply path stays inside the same account-level rate limits as cold sends. - new `reply` table + drizzle migration 0008, queries module, types - lib/replyTriage.ts: Anthropic-backed classifier with a deterministic keyword heuristic fallback (clears >= 80% on tests/triage/cases.json so the system still works with no API key) - recordInboundReply now triages + persists every reply alongside the existing `replied` event + reply_followup scheduler - /api/replies (list + counts), /api/replies/triage (stateless POST), /api/replies/[id] (PATCH edit), /api/replies/[id]/send, /archive - /dashboard/replies page with four-tab intent UI - 14 new triage tests, 16 hand-labeled triage cases Note for review: spec called for `@anthropic-ai/sdk`. Used `fetch()` to avoid a new dep for one classification call. Trivially swappable if you want the SDK in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CTO technical review — APPROVED ✅Reviewed for HIR-122. Spot-verified the engineer's reported clean state:
Migration
|
Summary
Closes HIR-120. Founder-flagged highest-impact item from the HIR-105 plan: turns coldflow's silent-reply automation from "send and forget" into a workflow.
interested,objection,not_now,out_of_office) the moment it lands, with a tailored follow-up draft./dashboard/repliesshows the four tabs with per-card [Send] / [Edit] / [Archive].email_queuesend pipeline (same account,Re:subject prefix), so warm-reply follow-ups stay inside coldflow's existing outbound rate limits.What's in the diff
replytable (reply_intent+reply_triage_statusenums), drizzle migration0008_white_mongoose.sql, queries module, types exported.src/lib/replyTriage.ts— Anthropic Claude classifier with a deterministic keyword-heuristic fallback. Heuristic clears>= 80%ontests/triage/cases.jsonso the system still works with no API key configured.recordInboundReplytriages + persists every reply alongside the existingrepliedevent andreply_followupscheduler. Single ingest path = single source of truth.POST /api/replies/triage— stateless classifyGET /api/replies— list + per-intent counts (filtered by user's campaigns)PATCH /api/replies/:id— edit suggested follow-upPOST /api/replies/:id/send— one-click follow-up via existing pipelinePOST /api/replies/:id/archive— hide from default viewsapp/(frontend)/dashboard/replies/page.tsx— four tabs, edit-in-place textarea, [Send] / [Save edit] / [Archive].tests/triage/cases.json. ExistingreplyFollowup.int.spec.tsupdated to mocktriageReply+createReply..env.exampleaddsANTHROPIC_API_KEY+ANTHROPIC_MODEL.Founder input flagged
recordInboundReplyinsrc/lib/replyFollowup.ts(called fromPOST /api/email-tracking/reply). This is the path the issue description referenced — let me know if you'd rather it sit further upstream (e.g. inside the Gmail watch webhook directly).@anthropic-ai/sdk. Used a directfetch()tohttps://api.anthropic.com/v1/messagesto avoid adding a new dep for one classification call. Trivially swappable if you want the SDK in.Test plan
pnpm test:int— 109 tests passing (the 1 failed file is the pre-existingapi.int.spec.tsinfra failure that needsPAYLOAD_SECRETin test env; unchanged onmain).npx tsc --noEmit— clean.pnpm exec eslinton the touched files — clean.tests/triage/cases.json.pnpm db:migrateagainst a local DB to apply0008./api/email-tracking/replyand confirm areplyrow appears +/dashboard/repliespopulates.pendingrow lands inemail_queue+ the reply flips toactioned.ANTHROPIC_API_KEYand confirmsource: "llm"shows up via the triage endpoint instead of the heuristic.Out of scope (per issue)
🤖 Generated with Claude Code