●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
A Daily Slack Digest of Six Apps' Store Reviews, Built with Gemini Flash
How I built a Cloud Run + Gemini Flash ETL that translates, classifies, and prioritizes 30–80 daily store reviews across six apps and posts them to Slack — cutting my review triage from 60 minutes to 12, for about $4 a month.
Across App Store and Google Play, my six apps receive 30 to 80 reviews per day. Beautiful HD Wallpapers alone, with about 50 million lifetime downloads, contributes a stream of bug reports, feature requests, and reviews in 11 languages — Thai, Persian, Ukrainian, Polish among others. Until this month, I was reading all of them by eye, which cost me about 60 minutes every weekday.
I replaced that with a small Gemini Flash + Cloud Run ETL that posts a structured digest to Slack each morning. The actual writing of review replies still happens by hand, but the triage step — deciding which reviews need attention today — is now automated. The whole system costs about $4 per month and was the highest-leverage thing I built in May.
Why translation and sentiment alone weren't enough
The first version I tried was the obvious one: shove every new review into Slack with the raw text. It didn't work. Sixty to a hundred reviews in eleven languages, dumped into a channel each morning, just made me stop opening the channel.
Eleven languages also makes "preparing to read" itself expensive. A Thai bug report costs me 30 seconds before I've understood it. The point of Gemini in the middle wasn't translation; it was eliminating the "preparing to read" step entirely.
For every review, I now produce five fields:
Japanese translation (regardless of source language)
Fields 4 and 5 are what the eyeball-only pipeline never gave me.
The architecture — Cloud Run + Cloud Scheduler + Gemini Flash
Cloud Scheduler (daily 8:50 JST)
↓
Cloud Run (Python 3.12, 1 vCPU, 512MB)
├── App Store Connect API (review delta per app)
├── Google Play Developer API (review delta per app)
├── Gemini API (gemini-3-2-flash, structured output)
├── Firebase Crashlytics REST (issue matching)
└── Slack Incoming Webhook (formatted message)
I expected Cloud Run cold start to be a problem. It isn't — running once a day, cold start is irrelevant. Total wall time across six apps lands between 40 and 90 seconds.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Reproduce the Cloud Run + Cloud Scheduler + Gemini Flash pipeline that handles 6 apps × 2 stores × 11 languages of reviews every morning at 9:00 JST
✦Get the actual monthly bill (~$4 Gemini Flash + ~$0 Cloud Run) for a 50M-download portfolio and a clear criterion for when to upgrade to Gemini Pro
✦Adopt the Slack thread design that drops indie developer review triage from 60 minutes to 12 per day
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
import google.generativeai as genaigenai.configure(api_key=os.environ["GEMINI_API_KEY"])PROMPT_TEMPLATE = """\You are a mobile app support engineer. Read the single app review belowand return one structured JSON object.App: {app_name}Store: {store}Rating: {rating}Language: {language}Body: {body}Fields:- ja_translation: Japanese translation (or original if already Japanese)- sentiment: positive | neutral | negative- category: bug | feature_request | praise | question | spam- urgency: low | medium | high- bug_summary: one-sentence symptom (only if category == bug)- feature_request_summary: one-sentence ask (only if category == feature_request)Urgency rubric:- high: crash, app unusable, billing error, data loss- medium: feature broken, recurring inconvenience- low: general dissatisfaction, requests, praiseReturn only the JSON object. No explanation text."""def classify(review: dict) -> dict: prompt = PROMPT_TEMPLATE.format(**review) model = genai.GenerativeModel( "gemini-3-2-flash", generation_config={ "response_mime_type": "application/json", "temperature": 0.1, }, ) res = model.generate_content(prompt) return json.loads(res.text)
response_mime_type = "application/json" is the line that keeps Gemini from emitting anything other than JSON. In practice it matters a lot — without it, ~5% of responses include leading commentary.
I run temperature at 0.1 rather than 0.0. Pure determinism is appealing but I saw occasional missing-field bugs at 0.0; nudging it slightly above produces stable JSON schemas with effectively the same categorization.
Crashlytics matching — "is this review about that crash?"
A review saying "won't start" might be about the current release or about a device-specific issue from a year ago. I give Gemini a second pass with the recent 48-hour Crashlytics top issues as context.
def match_crashlytics_issue(review_text: str, recent_issues: list[dict]) -> str | None: context = "\n".join( f"- {i['issue_id']}: {i['title']} ({i['affected_users']} users)" for i in recent_issues ) prompt = f"""\Compare the review below against the recent Crashlytics issue list, andreturn the single most likely matching issue ID. Return "none" if noissue plausibly matches.Review: {review_text}Recent Crashlytics issues:{context}""" model = genai.GenerativeModel("gemini-3-2-flash") res = model.generate_content(prompt) issue_id = res.text.strip() return issue_id if issue_id != "none" else None
The match is not 100% accurate, but it replaces the most painful manual step — sitting with a spreadsheet of recent crashes and a feed of recent reviews, trying to correlate them by eye. On Beautiful HD Wallpapers v2.0.0, this Crashlytics matcher correctly linked four user reviews to the RecyclerView crash issue I wrote about elsewhere.
The Slack message shape — threads enforce reading order
I post a single parent message with a one-line summary, and put the per-review detail in the thread.
:large_yellow_circle: 6 apps daily review digest — 2026-05-27
Total: 53 (positive 31, neutral 12, negative 10)
:red_circle: high: 2 / :large_orange_circle: medium: 6 / :large_green_circle: low: 45
[1] Beautiful HD Wallpapers, Google Play, ★1, ja
high / bug / Crashes 3 seconds after launch on Android 6.0.1
Crashlytics: maybe → ABC-1234
↪ thread for original text + reply draft
On days when no review is high or medium urgency, the parent line tells me that in one glance and I close Slack without expanding the thread. Days with more than ten negatives, I open the thread and work top-down by urgency.
I don't post reply drafts automatically. Replies are written by hand. With a 50M-download user base, the words that land in those review responses matter too much to delegate to a model. The ETL's job is exactly the triage step, not the writing step.
Monthly cost — Gemini Flash $4, Cloud Run effectively $0
I've considered upgrading to Gemini Pro a few times, but for a six-app indie portfolio Flash is the right tool. Upgrade when you need >95% accuracy on sentiment, or when you need multi-step reasoning over a single review (summarize → extract feature request → compare to competitor parity). I don't need either of those, so I stay on Flash.
Twelve years of indie work and where I draw the line
My grandfathers were both shrine carpenters in Japan, and what I absorbed from them is that the parts no one sees decide whether the structure holds. In app review work, the part no one sees is the triage step — picking which reviews get answered today. The part users see is the reply itself. Splitting these in two and automating only the first half is what makes the second half sustainable.
I started teaching myself code in 1997, at sixteen. The thing technology does best is reclaim time we spend on the wrong work. Gemini Flash giving me back 48 minutes a day, for about four dollars a month, is exactly that shape of recovery.
Where to start if you want to try this
Building the full thing from day one is overkill. Start with one app, one store (App Store Connect's review API is friendlier), and send Gemini Flash's structured output to a Slack webhook from a cron job on your own machine. Cloud Run isn't needed until you're past three apps. Five reviews a day is enough volume to settle the JSON schema and Slack message shape.
I hope this is useful for anyone else running a small portfolio of apps on their own. Thanks for reading.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.