SignalScout: sourced outreach, or a blank field

A B2B research tool that turns a lead CSV into cold-email openers, each with a source URL, a date and a confidence level. When it doesn't know, it says nothing.

Client
Nuits
Year
2026
Status
Live
Role
Solo founder, Product design, Engineering, AI systems
Stack
Astro 7, Cloudflare Workers, Cloudflare D1, Durable Objects, Turnstile, OpenRouter, Exa, Firecrawl, Polar, Resend, Sentry
SignalScout: sourced outreach, or a blank field

Context

SignalScout is a commercial B2B product from Nuits, my one-person studio in Helsinki. You upload a CSV of leads. You get back an outreach opener for each one, and next to every opener sits the URL it came from, the date of that source, and how confident the system is that the two are related.

If the research turned up nothing usable, the field comes back blank. It does not come back plausible.

The problem

Every AI outreach tool I tried had the same failure mode, and it was not a bug in any of them. Ask for a personalised opener and you get one. Always. There is no input for which the thing says it doesn’t know. So you receive a confident paragraph about a funding round that never closed, addressed to a person who did not raise it, and you put your own name at the bottom and press send.

The cost of that is asymmetric. Generating the sentence costs a fraction of a cent. Sending it costs your reputation with the one prospect you actually wanted. Nobody was pricing that correctly, mostly because a blank cell looks like a broken product and a fabricated cell looks like a working one.

My role

Everything. Nuits has never hired anyone.

That means the positioning, the pricing model, the interface, the empty states, the research agents, the evaluation harness, the billing integration, the transactional email, the error dashboards and the sentence on the pricing page that admits AI research can be wrong. I designed it and I wrote it. There is nobody to hand the uncomfortable copy to.

Approach

I designed the blank state before I designed anything else. Not as an edge case: as the primary screen. If the product looks correct and calm when it has found nothing, the rest of the interface can be honest without apologising.

From there the shape followed. Every claim in the output carries provenance the buyer can click. Confidence is a property of the evidence, not a number I asked a model to invent about itself. And the pricing had to be per lead rather than per seat, because a per-seat price punishes exactly the careful operator who sends forty good emails instead of four thousand bad ones. Credit packs sit at $19, $79 and $249.

Building it

It runs on Astro 7 deployed to Cloudflare Workers. D1 holds accounts, credit ledgers and job records. Each research batch gets a Durable Object, which is the part that makes the product possible at all: a lead takes tens of seconds to research properly, a CSV of two hundred takes a while, and a Durable Object survives the user closing the tab. Turnstile guards the upload endpoint so the expensive path is not free to strangers.

The research runs in three stages, and the separation is the whole design. A retrieval stage uses Exa for neural search and Firecrawl to fetch and extract the pages it finds, returning candidate documents with URLs and publication dates attached. A writing stage sees only those documents and is permitted to reference nothing outside them. A grading stage then reads each generated claim back against the source text and drops anything it cannot locate. Confidence is computed afterwards from source recency and from whether independent sources agree, so the model never grades its own homework.

Models come through OpenRouter, which lets me route per stage instead of per product. Extraction and classification run on something cheap and fast, because that is most of the token volume. Only the final sentence, once per lead, goes to an expensive model. Cost per lead stays predictable enough to price against, which is the actual reason for the split.

The failure modes are the interesting part. Entity collision is the worst: two companies share a name, retrieval returns both, and the opener describes the wrong one convincingly. Domain matching and a same-entity check in the grader catch most of it. Stale sources are the second: a three-year-old press release is technically true and socially disastrous, so age feeds directly into confidence. Paywalls and bot walls produce partial extractions that read like complete ones, which the grader treats as absence rather than evidence.

Polar handles payments as merchant of record, so EU VAT is somebody else’s job. Resend sends receipts and job-complete notifications. Sentry catches what I did not anticipate, which is a longer list than I would like.

Outcome

It is live and it sells credits. The output leaves fields blank, and the pricing page states plainly that AI research can be wrong, which is a strange thing to put next to a buy button and the reason I trust the buy button.

What I’d do differently

I built the three-stage pipeline before I had an evaluation set, so for the first month “better” was a feeling. I should have hand-labelled a hundred leads on day one and made every prompt change prove itself against them.

I also priced credit packs before I knew my real cost per lead. The $19 tier was optimistic. I would rather have shipped one price, measured for a month, then built the ladder.