Product and growth engineering

Your product and its data layer come first. The growth engines run on the same database, and first-party events record what brought each signup, so you know what to build next.

Products built with AI

AI builds your app and its Postgres database from specs I write, and your site in the CMS your team edits, and I read every change before it goes live. Your app records what each user does in that database, and the five data systems below read from it.

App built under review

36builds, each read before it went live

Your app is built by AI from written specs, and I read each change before it goes live, so the mistakes AI code makes are caught before a user meets them.

  • 1 monthfrom an empty database to production

Each feature starts as a written spec with rules every build must follow: add to what works and never rewrite it, leave sign-in and attribution alone, check each column name against the live database. An AI app builder, driven by a coding agent over MCP, writes the change; the exact change, the diff, comes back to me, and nothing goes live until it reads clean.

On a stealth biotech startup's member app, my review of all 36 builds caught rewrites that would have lost a user's first change, two builds writing to database columns that did not exist, saves using the sign-in ID where the database expected the profile ID, and links between tables that would have blocked account deletion. The app went from an empty database to production in one month, with a 14-field intake, a progress dashboard, a calendar on a unit-tested dosing model, data export and account deletion.

Review recordSample data

Builds read 0 / 36

One build, read line by line

Log a check-insrc/routes/checkin.tsx

 const { data: { user } } = await supabase.auth.getUser()
+if (!user) throw new Error("Not signed in")
+const { error } = await supabase.from("checkins").insert({
+  member_id: user.id,
+  mood,
+  note,
+})
+if (error) throw error

Held

Each profile has an ID of its own, apart from the sign-in's. member_id takes the profile's ID, so passing user.id makes row-level security reject every check-in.

Added to the build rules: Check each ID against the column it fills

Who does each step

  • Lovable's builderwrites the code
  • Claude Opus, in Claude Codewrites the specs and drives the builder
  • Reviewme, on each change

Stack

  • Claude Codewrites the specs and drives the builder
  • Lovable, over MCPthe builder
  • TanStack StartReact with server-side rendering
  • Tailwind CSSthe styling
  • shadcn/uithe components
  • Supabasethe database

Outputs

  • The app in production
  • Review recordeach build, its change and what was caught
  • Build rulesthe standing rules, extended after each catch

Postgres data layer and its audit

6 of 6audit findings closed, three high and three medium, each with a fix and a check

Row-level security makes your database check who can read and write each row, and a read-only audit tests it before launch, so protecting your users' data does not depend on the app's code alone.

An AI agent writes each change to the database as a numbered script, a migration, and a parser reads each one before it runs, so a script that does not parse never reaches the database. Every table gets row-level security, so the database checks each read and write against the user making it. Functions with extra rights are locked down, a trigger stops members writing to fields reserved for admins, and admin actions are logged. The views shared with partners hide identities and show no figure for a group of fewer than five people.

Before launch I audit the live database read-only and rank what I find. On the stealth biotech startup's database that was three high and three medium findings, each closed with a staged fix script and a check that the fix held.

Access mapSample data

Every table: row-level security

Who can read and write each table

MemberAdminPartner view
profilesOwn rowRead; changes loggedHidden
checkinsOwn rowsRead; changes loggedGroups of five or more
eventsAdd own, never editReadHashed keys
admin_fieldsBlocked by a triggerWrite, loggedHidden
admin_logNo accessReadHidden

Who does each step

  • Claude Opus, in Claude Codewrites the migrations
  • Parsing and the audit's testscode, no model

Stack

  • Supabase Postgresthe database
  • PL/pgSQLfunctions and triggers
  • pglastparses each migration before it runs
  • Supabase MCPapplies them
  • Supabase advisorsthe audit's first pass

Outputs

  • The databasebuilt from versioned migrations
  • Auditeach finding, its severity, its fix and its check
  • Access mapwho can read and write each table

Sites and web apps

10 daysto rebuild the site for a conference deadline

Your website, built in the CMS your team already edits, or in Next.js on its own database when the site has to work as an app, and moved to your domain with no downtime.

  • 8pages
  • 31custom React components

When your team edits the site itself, an AI agent builds the pages and templates from your brief, brand and content inside Framer, Webflow or WordPress, through their MCP servers and APIs, and writes the custom parts as React components. When the site has to sign people in, save what they do or build pages from your data, it is a Next.js app on Vercel, with its DNS on Cloudflare and a Postgres database, so the site and your product share one record.

I review each page before it goes live, and the move changes only the DNS record that points your domain at the new host, so your email and the rest of your domain keep running. For a stealth biotech startup I rebuilt the marketing site in Framer in ten days for a conference deadline: 8 pages and 31 custom React components, with no downtime when the domain moved.

Go-live record

Your domain's records

  • AYour domainPoints at the new host
  • MXMailUnchanged
  • TXTSPFUnchanged
  • TXTDKIMUnchanged
  • TXTDMARCUnchanged

Who does each step

  • Claude Opus, in Claude Codebuilds the pages, the components and the app

Stack

  • Claude Codethe build
  • Framerover MCP, where your team edits
  • Webflowthe same, over MCP
  • WordPressthe same, over MCP
  • Next.jsthe site as an app
  • Vercelhosting
  • Supabase Postgresthe app's data
  • CloudflareDNS and the move to the new host
  • GA4analytics

Outputs

  • The sitein your CMS or in Next.js, yours to edit
  • Componentsreusable on new pages
  • Go-live recordthe domain moved with no downtime

Listings from unstructured posts

Messy posts from a Telegram group, an inbox or a form become clean listings (item, price, place, contact): a low-cost model reads each post, a stronger one takes the posts it is unsure of, and a person checks any post still below the confidence level you set.

As each post arrives, a low-cost model reads it into fixed fields (item, price, place, contact) and scores how sure it is. A post it scores below the confidence level you set goes to a stronger model, and one still below that level waits for a person.

After that it is all code: the listing is validated, saved in Postgres and placed on the map or in the feed. Most posts need only the low-cost call, which keeps the cost low enough to read every post your channels receive.

ListingsSample data
  • selling my road bike, barely used, 450 or best offer, pick up in Lisbon centre, message me

    ItemRoad bikePriceโ‚ฌ450PlaceLisbonContactMessage
    HaikuPlaced
  • standing desk free if you collect it this week!! porto, near the station, reply here

    ItemStanding deskPriceFreePlacePortoContactReply here
    HaikuSonnetPlaced
  • 2 bedroom flat from 1 december, 1200 a month, message for photos

    Item2-bedroom flatPriceโ‚ฌ1,200 a monthPlace?ContactMessage
    HaikuSonnetHeld for a person
  • camera lens 50mm f1.8, 120, Braga, message

    Item50 mm lensPriceโ‚ฌ120PlaceBragaContactMessage
    HaikuPlaced

Listings on the map

Your level 80

Who does each step

  • Claude Haikureads each post first
  • Claude Sonnetthe posts Haiku is unsure of

Stack

  • Supabasethe listings
  • Telegram Bot APIthe channels the posts come from
  • Mapboxturns each place into a point on the map

Outputs

  • Listingsone per post, checked against the fields
  • Live map or feedevery listing placed
  • Review queuethe posts waiting for a person

Data

These five systems read that database and the sources around it, so each figure you act on, from a signup's first link to a price that moved, traces back to the rows that produced it.

Product analytics

54types of event recorded for a stealth biotech startup

A record of what each user does, kept in your own database from the first link they clicked to what they do as a member, so you know what brought each signup and what to build next.

Your app sends each event to a table that can only be added to, tied to the signed-in user, or before signup to an anonymous ID. That ID links a visitor's visits before signup to the member after it, the source tag on the first link carries into the signup and every event after it, and the app sends events in batches and saves them when the page closes.

Partners and analysts read a version with identities hidden behind hashed keys instead of the raw table, and an attribution view shows what each first link brought, counting a member as active 30 days after signup when they recorded an event in the seven days before. For the stealth biotech startup it tracked 54 types of event.

AttributionSample data

Events as they are recorded

TimeEventWhoFirst link
10:41:52page_viewedanon 7f3cnewsletter
10:43:10pricing_viewedanon 7f3cnewsletter
10:44:51signup_completedanon 7f3c, now member 51c2newsletter
10:45:30onboarding_completedmember 51c2newsletter
10:47:02project_createdmember 51c2newsletter

The anonymous ID joins the member

What each first link brought in August

  • newsletter
  • partner
  • x
  • search
  • referral
  • linkedin
  • no tag

Active 30 days after signupGone quiet

Who does each step

  • Noneevents are recorded, never guessed

Stack

  • Supabase Postgresthe events
  • Row-level securitywho can write them
  • A hashed read viewwhat partners see, identities hidden

Outputs

  • Eventsall of them in your own database
  • Attributionfrom the first link to each event
  • Partner viewidentities hidden behind hashed keys

Enrichment and verification

Each record is filled in from public sources, and code marks each field verified, likely or unverified, so nothing downstream treats a guess as a fact.

Code fills in each record you send (a lead, a company, a contact) from public sources and tags each field with its source, and a model decides which records across sources describe the same one. An email address is judged on where it was seen in public, what its mail server answers when asked whether the mailbox exists, and whether it fits the domain's address format, since some servers accept any address.

Each field gets a grade: verified when a primary source confirms it, likely when sources agree, unverified otherwise. Your outreach, your CRM and your reports read only the grade they need, and an address that fails the check stops a send.

Graded recordsSample data
  • CompanyCompany registryVerified
  • WebsiteRegistry and DNSVerified
  • RoleTwo public profiles agreeLikely
  • CountryOne public profileUnverified
  • EmailThe mail server accepts any addressHeld

Who does each step

  • Claude Sonnetmatches records across sources
  • Verificationcode, no model

Stack

  • Pythonruns the pipeline
  • Hunterfinds addresses
  • SMTP checksask the mail server whether the mailbox exists
  • Postgresthe graded records

Outputs

  • Graded recordseach field verified, likely or unverified
  • Verified contacts
  • Source per field

Pricing and market data

Prices and market data from scattered sources land in one table you can question in plain words, and each answer shows the rows it came from.

Each source (supplier feeds, exports, scraped pages, APIs) has its own connector that runs on a schedule. Python converts the units, currencies and names into one format in Postgres, and code works out the price changes by competitor, product and region from it.

You ask a question, such as which competitor cut prices in a region this month, and a model writes the database query, runs it read-only and answers with the rows behind it. A new source means one new connector, and nothing else changes.

AnswersSample data
  • Supplier feed
  • Export
  • Scraped pages
  • API

One price table

select competitor, product, change
from price_changes
where region = 'North'
  and month = '2026-10' and change < 0
order by change;
CompetitorProductChange
A national chainEspresso beans, 1 kg-8.5%
A national chainFilter beans, 1 kg-6.0%
An online shopEspresso beans, 1 kg-3.2%

The national chain cut two products in the north this month, by 8.5% and 6.0%.

Who does each step

  • Claude Sonnetturns your question into a database query
  • Conversioncode, no model

Stack

  • Pythonthe connectors
  • Supabase Postgresthe table
  • Apifyscraped sources

Outputs

  • One price tableevery source in one format
  • Signalsprice changes by competitor, product and region, refreshed
  • Answerseach with its query and rows

Reports from your data

A weekly summary of your numbers from your analytics and your database, with each figure tied to the query that produced it.

Queries run on a schedule against GA4, Mixpanel or the events in Postgres. Code works out what changed against the week before and your targets, a model writes it up, and a check rejects any figure in the summary that does not match its query's result.

The summary is sent to you each week, and the charts sit on a dashboard behind a login.

Weekly summarySample data
  • Signups rose to 312, 9% more than last week.Matches its query
  • 58% of September's signups were active 30 days later.Matches its query
  • Paid plans brought in $41.2K.Held: does not match its query

Query sheet

  • count signups, this week312312
  • active at 30 days, September58%58%
  • sum of paid invoices, this week$41.2K$40.6K

Who does each step

  • Claude Sonnetwrites the summary
  • The figurescode, no model; each comes from its query

Stack

  • GA4
  • Mixpanel
  • Postgres
  • Next.jsthe dashboard
  • Vercelhosting

Outputs

  • Weekly summary
  • Dashboard behind a login
  • Query sheeteach figure with its query

Send me what you're growing.

There is no call to book. Send me an email and I reply in writing.

jose@jcomarketing.io