Skip to content

Work01Live · payingJul – Sep 2026

Pictimate

Instant quotes for floor-coating contractors. A homeowner uploads a photo, sees it rendered in the contractor's finishes, and gets a line-item price from the contractor's own rates.

Pictimate estimator showing a rendered basement floor in a flake finish with an upload prompt beneath it
The estimator on the demo tenant: a seeded before-and-after, then the customer's own photo
<8 wk
first commit to a paying customer
6
trade templates, from floor coating to countertops
2
languages: English and French
103
tests, every one a shipped bug or a contractor's rule

The product
call by call

Problem

Epoxy and concrete-coating contractors sell a visual result on a floor the customer has never seen coated. Before a homeowner commits they want two answers: what will it look like, and what will it cost. Getting either usually means a site visit, a quote a few days later, and a customer who has meanwhile called two other contractors. Every one of those visits that goes quiet is an evening the contractor spent for nothing.

Discovery

Selling it taught more than building it. All of this is from the outreach tracker and call notes, written down as it happened.

  • Nobody replies to a description. They reply to their own page. Every reply ever received came from a page already built with the contractor's logo and photos, never from a pitch. One replied "Okay show me" within the hour. So the offer became "I'll build you a free page", and the cold-call script leads with what the homeowner sees, never with "we built you a website".
  • Contractors won't try it themselves. Three prospects visited their page 15, 16 and 24 times and none uploaded a photo. They formed firm opinions from the seeded example. If contractors won't upload, assume their customers might not either, so every page opens on a worked before-and-after.
  • The price objections were a sourcing problem in disguise. Every contractor who balked at $199 a month was brand new, one with a single post and one follower. Established businesses did not flinch at the same number. The fix was who to contact, not what to charge.
  • The real objections. "I already have a visualiser" (built with a no-code tool, and it shows a picture but never a number). "My customers call and I close on the phone", from a contractor who also does not want his prices public where competitors can see them. And the honest deadlock: they can't get a lead without publishing, and won't publish without believing they'll get a lead.
  • What closed the one sale. Not a discount, not a deadline. The contractor was asked twice when he would publish and ignored both. He moved when asked what was in the way, with three named options and an easy exit.
  • Contractors quote in bands, not rates. One contractor's phone quote, a minimum job, a per-square-foot band for mid-sized floors, and a price floor he allows to break only on very large jobs, became four executable assertions. Tax is regional practice, not a rate table: Canada always charges, US contractors bill flat except in states that tax real-property improvements, and both invoices the Florida contractors sent were flat.
  • Reaching them. Contractors answer texts, not calls, and are reachable before 7:30am or after 4:30pm; mid-day they are on site with a grinder running. Cold SMS is fine in Canada under CASL and off the table in the US under TCPA, so Americans get email.

Decisions

  1. Build the page before the pitch. A demo tenant is generated from a prospect's public branding, marked as unaffiliated with a claim-or-remove banner, hidden from search engines, and sandboxed so a real business is never emailed. It is the only thing that has ever produced a reply.
  2. Constrain by scene, not only by prompt. Where the customer supplies the photo, the prompt names where the floor ends and lists what lies beyond it. Where the image is generated from scratch, only enclosed rooms are used: four walls and a shut door leave nothing for the coating to run onto.
  3. Price like tax brackets. Each slice of a job is charged at its own tier, so a bigger floor can never quote cheaper. Monotonic by construction, not by hope.
  4. One function decides tax. The arithmetic and the words on the quote come from the same place, so a page can no longer say "TPS + TVQ" while charging Ontario's 13%.
  5. Don't retry into a customer's wallet. Paid renders stop after three attempts and say so. The contractor's own photograph beats anything generated.
  6. The contractor approves every generated pair. Each rejection records a tap-to-select reason, and the reason list was harvested from failures that actually happened, in the order they happened.
  7. Unused plan credits become content. Stories, wipe reels, carousels and a print kit generated from the contractor's own brand assets, so a slow month still produces value from the subscription.
  8. Source better before charging less. Discounts are held back until someone objects, and outreach targets established businesses scored on Google review count, the only honest cheap proxy for job volume.
  9. Nothing sends without a human tap, and nothing is ever claimed. Incoming DMs get an AI draft in a plain house voice, a person decides, and outreach never claims a conversion figure, a customer count or that a render is a photograph.

Outcome

Live at pictimate.com, with contractors on branded pages and a paying customer on live-mode Stripe under eight weeks after the first commit. Quotes are correct for Québec at 14.975% and Florida at zero, and every square foot from 100 to 3,000 is proven never to price lower than the one before it.

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.

Pictimate runtime architecturePEOPLEAPP · VERCELBACKGROUND · CRONSSERVICESHomeownerphoto + choicesContractordashboard, subscribeFounderTelegram ops/t/[slug] estimatorDashboardleads · pricing · content/api/visualize/api/content/api/lead/api/ops/instagramsigned webhookOps brief2× dailyMonday content email+ free renderWeekly Apify sweepAI GatewayGemini · Flux · VeoStripesubscriptions + webhookResendTelegram botNeon PostgresVercel Blob
A homeowner uploads a photo on the contractor's branded estimator page; the visualize route edits only the floor through the AI Gateway and prices the job from the contractor's own rates. Leads land in the dashboard and email the contractor. Crons drive the founder's twice-daily Telegram brief, Monday content emails, and weekly prospect sweeps. Not drawn: Sentry and the admin panel's own routes.

Stack

  • Next.js 16 · React 19 · TypeScript
  • Vercel
  • Neon serverless Postgres (raw SQL)
  • Vercel Blob
  • Stripe (subscriptions, metered overage)
  • Resend
  • Vercel AI SDK via AI Gateway (Gemini image-edit, Flux, Veo)
  • sharp · ffmpeg

Three problems
worth telling

A bigger floor that quoted cheaper

The pricing engine launched applying one economy-of-scale multiplier to a whole job based on its size bracket. That design carries a cliff at every boundary, and the fix commit says exactly what the cliff did.

A 301 sq ft garage crossed into the cheaper bracket and came out $300 LESS than a 300 sq ft one — three such reversals across the range, at 300, 800 and 2000 sq ft. A contractor playing with his own estimator would have found it in a minute and stopped trusting the number.

commit ae09247

The hard part was noticing, because every individual quote looked plausible. The test that guards the fix doesn't spot-check: it walks every square foot from 100 to 3,000 and asserts the price never goes down. The same file opens with its thesis: every case is a bug this code has actually shipped, or a rule a real contractor stated on a real phone call.

Teaching an image model where a floor ends

The first prompt said "coat the floor completely and uniformly, edge to edge" and listed what to preserve as "walls, objects, vehicles". Indoors that worked, because walls stop the coating.

On a screened patio, or a garage with the door up, the lawn and the driveway sit at the same level as the slab and read as more floor, so the render poured flake across the grass beyond the screen.

commit 62b6c37

Describing what to do failed three times in a row on one tenant. The fix inverted the frame: name where the surface ends, then enumerate the ground beyond it, "grass, lawn, soil, planting, driveways, paths, pavement, kerbs, thresholds and any ground visible through a doorway, window, screen or open garage door". When a porch still failed with that list in place, the answer was architectural rather than another instruction: generated scenes use enclosed rooms only.

Serverless failure is silent by default

Three production incidents in one month shared a root cause: on serverless, things don't crash loudly, they succeed quietly at doing nothing.

The SDK initialised, the tunnel route existed, the bundle looked right, and not one error was ever sent. That is the worst way for an error reporter to fail… Off and knowable beats on and lying.

lib/sentry-dsn.ts

CI had baked a redacted placeholder into the bundle as the Sentry DSN; it was found by inspecting the deployed bundle, not the green deploy log. The Instagram webhook answered Meta with a 200 and then did nothing, because a Vercel function freezes the instant it responds. The daily Telegram brief died for three straight mornings on a date-format mismatch, logged to a console nobody reads. The lasting change wasn't any single fix: every background job now reports failure into the channel its output was missing from, and regression tests pin the exact timestamps that did the killing.

Screens

  • A Pictimate estimate of $3,630 to $5,655 for a 2-car garage with line items and HST
    The estimate: system rate band, prep items, topcoat upgrade, and the regional tax line
  • Pictimate landing page
    Landing page