d1fpc3.com

Case study, October 2026

How I built SeenRank

I'm Frank Claridge, a self-taught developer. Since May I've built SeenRank on my own: a live product where AI does most of the work, from a pipeline that questions four AI engines on a schedule to an agent that takes actions for users and a server that lets customers use it from inside Claude. I also sell it.

Email me Code on GitHub
Built
Alone, May 2026 to now
Commits
1,314 across 168 working days
Backend
74 serverless functions, 194 database migrations
AI
4 engines, an 11-tool agent, a 24-tool MCP server
Sales
837 cold emails, first paying customer in October

Problem

People have started asking ChatGPT, Claude, Perplexity and Gemini who to hire instead of searching Google. A few businesses get named in the answer. Everyone else is invisible, and they have no way to find out.

SeenRank asks those engines the questions real customers ask, on a schedule, and records who gets named. Then it explains why a business was left out and writes the fixes: page rewrites, Google Business Profile changes and content the engines can cite. It runs at seenrank.com.

System

React on the front, Supabase underneath (Postgres with row-level security, auth, Deno edge functions, pg_cron), and the AI providers called directly over HTTP. No framework between me and the models.

SeenRank architecture Two triggers, a weekly cron job and the dashboard, feed one engine pipeline. It fans out to ChatGPT, Claude, Perplexity and Gemini, a Claude Haiku parser reads each answer, and results land in Postgres. Action plans, the Iris agent and the MCP server all read from Postgres, and claude.ai connects to the MCP server over OAuth 2.1. pg_cronevery Monday at 06:00 UTC Dashboard and Irison demand, rate limited per org Engine pipelineretries with backoff, per-attempt and total timeouts, concurrency cap ChatGPT Claude Perplexity Gemini Parser, Claude Haikustructured output, answers treated as untrusted input Postgres with row-level securityone row per engine per question per run, scores, history Action plansfixes written by Claude Iris agent11 tools, in the app MCP server24 tools, OAuth 2.1 claude.aicustomer connects a URL SeenRank architecture The weekly cron job and the dashboard feed one engine pipeline, which asks ChatGPT, Claude, Perplexity and Gemini. A Claude Haiku parser reads each answer into Postgres, which feeds action plans, the Iris agent and the MCP server that claude.ai connects to. pg_cronMondays, 06:00 UTC Dashboard, Irison demand Engine pipelineretries, timeouts, concurrency cap ChatGPT Claude Perplexity Gemini Parser, Claude Haikustructured output, untrusted input Postgres, row-level securityone row per engine, per question Action plansfixes written by Claude Iris agent11 tools, in the app MCP server24 tools, OAuth 2.1 claude.aicustomer connects a URL
One pipeline serves both the weekly run and on-demand checks, so a fix lands everywhere at once.
  1. Ask. Each tracked question goes to every engine whose key is configured, the way a customer would ask it.
  2. Read. A small, fast model reads each answer and returns structured JSON: was the business named, where, who else was named, which sources were cited.
  3. Store. Every answer becomes a row, so a business sees its trend week over week, engine by engine.
  4. Act. Claude turns a miss into steps that fix it, Iris answers questions and runs actions, and the MCP server hands the same tools to Claude itself.

Hard parts

01

Rate limits made the product look dead

A big run fanned out every engine call at once. It hit the provider's per-minute ceiling, and each 429 was saved as a permanent engine error with no result. The dashboard hides empty results, so for affected accounts the whole product looked broken.

FixEvery engine call goes through one wrapper that retries 429, 529 and 5xx with exponential backoff, honors Retry-After, and spreads retries with deterministic jitter. A concurrency cap keeps calls under the per-minute limit instead of bursting past it.

02

One hung connection could kill the weekly run

There was no timeout anywhere. A provider that opened a socket and never answered held the request open, and edge functions die at about 150 seconds, so one silent engine took down the run for every business after it.

FixTwo bounds, because one is not enough: a ceiling per attempt and a total budget per call that fits inside the function's lifetime, both wired through AbortSignal so a caller can still cancel.

// the shape of it
const MAX_RETRIES = 4
const PER_ATTEMPT_TIMEOUT_MS = 45_000
const TOTAL_BUDGET_MS = 110_000  // under the ~150s isolate limit

export async function fetchWithRetry(url, init, opts = {}) {
  const deadline = Date.now() + (opts.totalBudgetMs ?? TOTAL_BUDGET_MS)
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    // per-attempt AbortSignal, backoff, Retry-After, deadline check
  }
}
03

AI answers are untrusted input

The parser reads text written by other models and descriptions written by customers. Either one could tell it what to conclude, or blow up its cost.

FixCustomer fields are flattened so they can't forge prompt structure, and labeled as data rather than instructions. Answers are capped before parsing so one bad response can't cost a hundred times the original call, and citation lists are bounded. The prompt stays byte-identical when nothing changes, so prompt caching keeps working.

04

Letting Claude use SeenRank directly

A Claude user should be able to ask "how am I doing?" and get their real SeenRank numbers, without copying anything over.

FixA hosted Model Context Protocol server with its own OAuth 2.1 authorization server: discovery metadata, dynamic client registration, PKCE, short-lived audience-bound tokens stored hashed. The user signs in to SeenRank and consents, and Claude never sees their session. The MCP tools and the REST API share one implementation, so they can't drift apart.

05

An agent that acts, carefully

Iris, the in-app assistant, has 11 tools. Reading data is free. Writing data, like adding a business or a question, means confirming the specifics in one sentence and then acting in the same turn, so it is fast without being reckless.

FixTools only ever touch the signed-in user's own account through row-level security, paid features are gated server-side by subscription status, and every tool result is reported back in plain words.

Open code

SeenRank's own code is private. These two are written fresh so you can read how I work.

Selling it

I built my own lead lists, wrote the first touches and follow-ups, and sent 837 cold emails to home-service companies, med spas and agencies from a CRM I built into SeenRank. Most got no reply. The ones that did taught me that a reply isn't a yes until money moves, so I started following up with warm leads one by one. The first paying customer, an HVAC company, signed in October.

Looking for

A remote role building AI systems that real companies run on: agents, automations and integrations, close to the customer. I learn fast, I ship every day, and nothing I build is done until I've clicked through it in a real browser.

Reach me