All work

Google Business Profile review-response automation

Revvy

A multi-tenant SaaS that ingests Google Business Profile reviews at scale and generates on-brand reply drafts through a tunable draft-to-approve-to-publish pipeline, engineered so throughput and per-call cost stay controlled once real review volume arrives.

6-10x
faster large-review syncs (2000 reviews: ~60-120 min sequential to ~10-20 min parallel)
Documented · PERFORMANCE.md
30-60 min to 5-10 min
1000-review sync time
Documented · PERFORMANCE.md benchmark
30-50%
targeted reduction in generation cost via pre-filtering ineligible reviews
Target · design target
300-1000ms to 50-200ms
cached reviews/locations page loads
Documented · PERFORMANCE.md
~2000 to ~20-40
DB queries per full sync after write batching
Target · design target described in docs

The problem

Responding to Google reviews is a manual, per-location chore that does not scale: a business with many locations accumulates reviews faster than a person can read, draft an on-brand reply, and post it. Doing this naively in software is worse — a sequential sync of a few thousand reviews takes over an hour, and generating a reply for every review spends model budget on reviews that are already answered or do not warrant one.

Constraints

  • Real end-customer data under Google's platform terms — the integration handles live review and business data, so nothing customer-identifying can leak into logs, media, or the portfolio.
  • Google Business Profile access tokens expire and refresh tokens can be permanently revoked (invalid_grant); the system has to survive token expiry mid-sync and detect dead connections rather than silently failing.
  • Model calls cost money per review and rate limits are real — throughput can't come at the price of runaway spend or hitting provider limits.
  • Multi-tenant from the start: every workspace has its own locations, prompts, automation settings, and cached views that must stay isolated.
  • Kept infrastructure minimal — no separate queue broker to operate alongside the database.

Approach & key decisions

  • Made pre-filtering the first stage of the pipeline — skip reviews already replied to, below the star threshold, or already drafted before spending a single model call.

    The cheapest generation is the one you avoid. Filtering ineligible reviews up front is where most of the cost discipline comes from; it targets a documented 30-50% cut in generation spend without changing output quality.

  • Built three generation strategies matched to a decision matrix rather than one code path: parallel (p-limit at 20 concurrent) for small and real-time work, a throttled mode (10 concurrent plus delays) for rate-limit safety, and an OpenAI Batch API path for non-urgent bulk jobs.

    Throughput, cost, and latency trade off against each other. Small syncs want speed, bulk backfills tolerate a 24-hour turnaround for roughly half the cost, and rate limits need a safety valve — one strategy can't serve all three honestly.

  • Wrote a real OAuth token lifecycle instead of a happy-path integration: refresh access tokens with a 5-minute expiry buffer, cache the token within a request, and detect invalid_grant to flag a connection as needing reauthorization.

    Long-running syncs outlive a token's lifetime, and revoked grants are a permanent failure, not a retry. Distinguishing a transient refresh from a dead connection is what keeps the product from either crashing or looping forever on a workspace that has to reconnect.

  • Chose pg-boss so the job queue lives entirely in PostgreSQL — no Redis or separate broker.

    For a single-operator build, one fewer piece of infrastructure to run and reason about is worth more than a marginally faster queue. Sync, generate, and post all became typed jobs on the database already in the stack.

  • Batched database writes inside transactions and cached read-heavy pages with per-workspace tagged revalidation.

    A full sync that issues ~2000 individual queries is the hidden bottleneck behind the model calls; upserting in batches (design target ~20-40 queries) and invalidating cache by workspace tag on sync keeps both writes and page loads fast without stale cross-tenant data.

What I built

  • Built the Google Business Profile integration on NextAuth v5 with offline access: a token layer that refreshes with a 5-minute expiry buffer, caches the access token in-request, and marks connections reauthRequired on invalid_grant.
  • Implemented review ingestion that paginates the GBP reviews API, maps star enums, and batch-upserts into PostgreSQL via Prisma inside transactions.
  • Built the reply pipeline with configurable per-location prompts, business/SEO context injection, and first-name-only personalization, across multiple OpenAI models (gpt-4o-mini default, with gpt-5 code paths).
  • Implemented the three-mode automation model — manual, draft-then-approve, and auto-post at or above a per-location star threshold.
  • Engineered throughput three ways: parallel generation via p-limit at 20 concurrent, a throttled 10-concurrent mode for rate-limit safety, and a Batch API path for cheap bulk work.
  • Built background processing on pg-boss with typed SYNC_REVIEWS, GENERATE_REPLY, and POST_REPLY handlers, plus an optional GBP Pub/Sub endpoint to trigger syncs.
  • Built the operator dashboard: connect, locations, and an inbox to generate, edit, approve, and publish drafts with per-location settings.

Outcomes

  • 6-10x Large-review sync (2000 reviews) improved from ~60-120 min sequential to ~10-20 min parallel — roughly 6-10xDocumented · PERFORMANCE.md; not independently audited
  • ~6x 1000-review sync improved from ~30-60 min to ~5-10 minDocumented · PERFORMANCE.md
  • 30-50% Pre-filtering targets a 30-50% reduction in generation cost by not drafting ineligible reviewsTarget · design target
  • ~50% Batch API path offers ~50% cost savings on bulk jobs where a 24-hour turnaround is acceptableTarget · design target / provider pricing
  • 50-200ms Cached page loads reduced from ~300-1000ms to ~50-200msDocumented · PERFORMANCE.md

Limitations & what's next

  • Performance numbers come from an internal PERFORMANCE.md — documented benchmarks and design targets, not independently audited production metrics. They should be read as documented, never guaranteed.
  • The product is deployed but not generally available; there are no verified users or revenue, and the README still describes it honestly as an early, functional build.
  • Pinecone is scaffolded as hooks for per-location voice memory but is not part of the shipped generation loop — reply voice is driven by per-location prompts and injected context today.
  • The AI layer is currently OpenAI-specific (with gpt-5 code paths staged); it is structured to swap models but has not been exercised against another provider.
  • Next: close the loop on per-location voice memory, add reply-quality evaluation before auto-post is trusted more broadly, and measure the cost and throughput targets against real production volume rather than benchmark runs.

Get in touch

Have a workflow worth automating?

I build the AI systems that run it end to end — and I'm looking for the roles where that ships.

Get in touch