Skip to content

Work04Live · payingApr – Aug 2026

ReachGram

An outreach CRM for agencies running large Instagram pages: discovery, fit-scoring, AI-drafted DMs, meetings and deals, without tripping Meta's automation detection.

ReachGram landing page hero
Landing page: for agencies who won't automate DMs and won't hire a BDR
0
automated sends, by design
3
prospect categories: venues, realtors, creators
242
test cases on the discovery surface alone
10
scheduled worker jobs

The product
call by call

Problem

Agencies that run large Instagram pages sell to local businesses by direct message, because cold email reply rates have collapsed below one percent while a personalized DM to a local business still gets answered. Doing it by hand falls apart at about 200 DMs a week and burns an operator ten hours a week in spreadsheets and copy-paste. Doing it with an automation tool gets the page banned in two to three weeks, so agencies keep buying burner accounts. A 300,000-follower page is not something you rebuild.

Meta bans pages that automate sends — losing a 300K follower page would be catastrophic.

README.md

Discovery

The founding insight, from the investor deck written alongside the build: you can't automate the send, but you can automate everything around it. Discovery, personalization, queue management, reply tracking, follow-ups and pipeline analytics are all the work surrounding the DM. The send itself stays human, which is exactly why pages don't get banned.

  • Who buys. Sub-five-person digital marketing agencies trying to add three to ten local clients a month, with one to three operators or interns on outreach while the owner sells. They already feel like they're going through the motions, and they replace a spreadsheet, a DM-template document and several abandoned automation tools.
  • What they're afraid of. The first question on the landing page's FAQ is "Will my Instagram page get banned?" The product's whole architecture is the answer: it never touches the Instagram API and never controls the browser. The DM lives in the operator's clipboard.
  • Where prospects come from. Three verified categories, local businesses from Google Maps enriched with Instagram data, licensed realtors in Canada and the US, and creators by niche.
  • What the funnel measured. Production Tier-1 discovery hit rate was 0.9%, one prospect in 109, against the 28% to 75% the data source can deliver, because two code paths building the same scraper input had drifted apart. A single null field dropped about 55% of every Google Maps batch. Mailer-daemon bounces were being counted as replies, inflating the number operators cared about most.

Decisions

  1. Automate everything except the send. Discovery, scoring, drafting, follow-ups, reply detection and meeting booking are automated. The Instagram send button is always pressed by a person, on a phone paired to the workspace by code. Human-in-the-loop sending is the moat, not a limitation.
  2. Show the cost before every click. Scrapes are priced per run; the operator sees the dollar amount before firing, and deduplication guarantees they never pay twice for the same prospect.
  3. Drafts reference real posts, not templates. Claude writes each DM from the prospect's actual recent content, in the operator's tone, with a reply and a follow-up drafted the same way.
  4. A campaign pauses itself before it can do damage. Email outreach runs behind a circuit breaker: any spam complaint pauses instantly; a bounce rate above 8% pauses once the sample reaches 20 sends, because one bounce in three isn't a trend.
  5. Silent failures become loud contracts. Absent and null are treated identically everywhere, the two scraper input builders are pinned together by a drift guard, and regression tests assert the exact field names the actor returns. The discovery library alone carries 242 test cases.
  6. Price by prospects, not seats. Solo at $149, Growth at $499 and Agency at $1,499 a month in Canadian dollars, each with a monthly prospect allowance and per-prospect overage, plus a $500 pilot to get an agency's first campaign running.
  7. Ship in deployable slices. Sixty tickets across ten phases, each phase ending in something usable, from discovery in week one to send queue in week four.
  8. Marketing content is drafted by an agent, published by a person. A custom Claude Code subagent writes to the CMS as drafts only.

Outcome

Live at reachgram.app with a paying customer, a demo video on the landing page, a per-operator funnel in analytics, a safety dashboard showing pacing and circuit-breaker state, and ten scheduled worker jobs behind the pipeline.

Built by hand

The same person made the product calls above and wrote the code below, working daily in Claude Code. Quotes are verbatim from commits and code comments.

ReachGram runtime architectureCLIENTSBACKENDAI · DATAINTEGRATIONSWeb appNext.jsMobile companion/m · the phone that sendsInstagramhuman presses sendHono REST APIWorkernode-cron + BullMQ · 10 schedulesPostgresNeon · DrizzleRedisClaudeAI SDK · structured outputApifyMaps + Instagram scrapeClerkGmail APIResendTwilio SMSStripeCal.com / CalendlySanity CMS
The worker discovers local businesses through Apify, scores them, and drafts DMs and emails with Claude. The web app runs the pipeline; the mobile companion is where a human presses the Instagram send button, because there is no automated edge to Instagram by design. Email outreach flows through Gmail and Resend behind an auto-pause circuit breaker. Not drawn: Sentry, Vercel Blob, and the Meta webhook.

Stack

  • Next.js 16 · Clerk · Sanity CMS
  • Hono REST API · Drizzle · Neon Postgres
  • BullMQ · Redis · node-cron worker
  • Claude via Vercel AI SDK (structured output)
  • Apify (Google Maps + Instagram)
  • Stripe · Gmail · Resend · Twilio · Cal.com
  • Sentry · Vercel Blob

Three problems
worth telling

Keeping a live schema honest

The backend runs without a build step against a Postgres whose schema is managed by migrations. The hard part wasn't writing migrations; it was that a partially applied or unregistered migration fails silently: the API keeps serving traffic against a stale schema and crashes mid-write.

A partially-applied migration silently broke runtime writes in alpha — runs crashed mid-persist with column does not exist errors.

src/index.ts

Two production outages came from this class: a snake-case-versus-camel-case cast that took prod down on 2026-05-20, and a hand-written SQL file never added to the migration journal, which the migrator silently skipped. Running the migrator on boot wasn't enough, because both failures are silent. The final design layers four defences: every migration is idempotent so re-applying is a no-op, a journal preflight catches on-disk files missing from the journal before the migrator runs, a post-migration check reads the enum and key tables and exits loudly if anything expected is missing, and a CI test enforces the invariant.

Third-party output that drifts and fails silently

Discovery depends on scraping actors whose output shapes change without notice. The original schemas coerced mismatches into plausible-but-wrong data instead of erroring, which is the worst kind of bug.

Pre-2026-05-21 the schema used the singular likeCount/commentCount, which never matched the actor output. Every post silently got likeCount=0 + commentCount=0 … Masked the bug for months.

src/lib/discovery/ig-profile.ts

The most expensive instance was two code paths building the same scraper input drifting apart, measured as a Tier-1 hit rate of 0.9% instead of 28% to 75%. Each silent failure became a loud, tested contract, including a test named for the exact plural field names so the bug can't sneak back.

Automating the whole funnel except the one action that gets you banned

The product's core constraint is a business one with a brutal failure cost. On the email side the parallel risk is torching the sending domain. The outreach engine evolved through visible corrections: reply and bounce detection, then counting mailer-daemon bounces as bounces rather than replies, then a read scope so the inbox poller could actually read replies, then a daily cap that stopped stranding human-sent DMs on mobile.

Cold outreach can quietly torch a sending domain if the list is dirty (hard bounces) or people mark it spam. This halts a running campaign the moment its health crosses a threshold, so a problem can't compound across the remaining sends.

src/lib/outreach/circuit-breaker.ts

The rules encode hard-won judgement, a minimum sample of 20 and an 8% bounce limit, and tests pin each one: it pauses immediately on any spam complaint, ignores the bounce rate below the minimum sample, pauses on a spike once the sample is large enough, and leaves a healthy campaign alone.

Screens

  • ReachGram landing section showing search, send safely, and track replies
    Three steps, and a human always presses send
  • ReachGram landing section showing the three prospect discovery sources
    Three discovery sources: Maps-pinned businesses, licensed realtors, creators by niche