Inspiration

Living in Seoul, I noticed something nobody talks about. Korean small business owners — cafe owners, pasta shop operators, salon owners — spend an enormous amount of time on their phone replying to Naver Place reviews. Naver Place is Korea's Google Business Profile. It is where customers find local businesses and where the algorithm decides whether a cafe gets seen.

Replying to reviews moves the algorithm. It builds loyalty. It defuses public complaints. But the average cafe owner gets 30 reviews a week, and writing thoughtful Korean replies to each one takes 30 minutes a day. Most owners cannot justify the time. Naver's own data shows about 70% of small business owners never reply to their reviews.

So they lose visibility. They lose customers. And the few who do reply end up sounding robotic because they're rushing. Formulaic "안녕하세요 [biz]입니다 더 좋은 서비스로 보답하겠습니다" responses that scream auto-generated.

I built Resona to fix that.


What it does

Resona is an AI agent that runs the review side of a Korean small business. The owner pastes her Naver Place URL once. In about a minute:

  1. Resona pulls every public review for her business.
  2. The agent learns her personal reply voice from her past responses. Her formality level, her honorific style, her emoji habits, her sign-off conventions.
  3. For every unanswered review, Resona drafts three reply variants in that voice.
  4. The owner taps Approve. The reply auto-copies to her clipboard, and Resona opens the exact Smart Place editor for that specific review.
  5. She pastes, sends, done. Ten seconds per review instead of four minutes.

Resona also surfaces analytics. Which keywords customers mention most, how categories like food, service, and atmosphere trend over time, what changes week to week. So the owner sees her business the way customers actually describe it.


How I built it

Resona is a Next.js 16 app on Vercel, with a durable agent built on the Vercel Workflow DevKit. The connect-and-draft workflow is seven steps. Resolve listing, fetch reviews, derive voice profile, draft variants, persist with deep-links. Each step is independently retried and restartable on transient failure.

For the AI drafting, I use Gemini 2.5 Flash with a carefully tuned prompt that bans formulaic Korean business openers and stock phrases, and instead conditions the model on the owner's own past replies. The voice profile derivation runs once at onboarding and is cached on the listing row in Supabase.

The hardest part was Naver. There is no public Naver Place API for reading reviews or posting replies. I reverse-engineered their internal GraphQL endpoint (pcmap-api.place.naver.com/graphql), including the getVisitorRatingReviews operation and its cursor-based pagination. Then I discovered something I genuinely don't believe anyone has shipped before: a deep-link inside Naver's GraphQL response that opens the Smart Place reply editor for one specific review. Not the dashboard, not a list, the review. Naver's own documentation says this link doesn't exist. It is there, in their own data.

For observability I integrated Arize Phoenix as an MCP server. Every agent step traces into Phoenix Cloud. Voice derivation timing, every Gemini call with its variant index, every approval and rejection by the owner. That eval signal feeds back into voice profile refinement so the agent's drafts get more accurate over time.

The daily polling cron lives at /api/cron/poll-new-reviews, authenticated with a Vercel-managed CRON_SECRET. It runs once a day, compares the freshest 10 reviews on each fully-synced listing against my DB, and flips a has_new_reviews_since_sync flag that surfaces as an amber badge on the Analytics tab.


Challenges I ran into

Naver has no public review API. I reverse-engineered their GraphQL endpoint by inspecting network traffic from the mobile Naver Map app. The cursor pagination uses an after input variable but the response field is called cursor. That asymmetry burned a day.

The Smart Place reply deep-link. Took two days of GraphQL spelunking to discover. The url pattern has two variants based on originType (영수증 versus 예약), and I initially built only one. The other broke silently in production until a real Korean review surfaced it.

Gemini defaults to formulaic Korean business replies. Out of the box, the model produced four-sentence formal replies with phrases like "더 좋은 서비스로 보답하겠습니다" that sound auto-generated. I had to explicitly ban specific stock openings and closings in the prompt, and constrain positive replies to a single short sentence of 30 to 60 characters. Because brevity reads as human in Korean while length reads as auto-generated.

Vercel's proxy buffered my NDJSON workflow streams. The draft_complete events from the workflow weren't reaching the browser in real time. I had to add Cache-Control: no-store, no-transform, X-Accel-Buffering: no, and Connection: keep-alive to the response, plus a fallback status-probe poll every 4 seconds for when streaming fails entirely.

Batched Gemini calls regressed the prompt. When I switched from three sequential draftSingleReply calls per review to one draftReply batched call (3x speedup), the batched prompt was an older version that didn't have the anti-robot rules. The first variant came out fine, the other two regressed. I had to port every constraint from the single-call prompt into the batch prompt.


What I learned

  • Korean cultural nuance in AI replies matters far more than I expected. Generic "polite professional" tuning fails because owners reject the drafts the moment they recognize the AI voice. Banning specific stock phrases moved the acceptance rate dramatically.
  • Durable workflows trade a small upfront complexity for an enormous win on retry and resume semantics. Once the steps are wired, partial failures stop being your problem.
  • Phoenix observability catches drift before customers do. A prompt regression that I wouldn't have noticed for a week in production showed up immediately as anomalous variant lengths in Phoenix.

What's next for Resona

  • Naver-native posting via cookie-auth so the owner doesn't even need to paste. Resona posts directly.
  • Gemini sentiment classification per category, so the owner sees what's getting worse, not just what's getting better. Naver's voted-keywords are positive-only by design. The negative signal lives only in the free-text bodies.
  • Multi-tenancy and Stripe billing to ship to 25 paying customers by August.
  • Photo Generator that turns 5-star reviews into shareable social cards the owner can post to Instagram or Naver Feed.
  • Themes store with KakaoTalk-style character pack micropurchases on top of the subscription. Korean owners already spend on app personalization on KakaoTalk and Line. Nobody offers that for B2B small business tools yet.

Resona is live in production at resona-core.vercel.app/connect.

To try it out:

  1. Sign in with any email. This is the pilot version, no real auth yet.
  2. Paste either of these Naver Place URLs when prompted:
    • https://map.naver.com/p/entry/place/1338115333 — small Hongdae cafe (32 reviews, all positive — clean demo of the core flow)
    • https://map.naver.com/p/smart-around/place/1032580297 — 프로토콜 연희점 (500+ reviews including 2 flagged-urgent Korean complaints, demos the red URGENT badge + sentiment-aware longer drafts)

I built it nimble, in nine days. I'm aiming for my first 20 paying customers in Korea by July 1st.

Built With

  • arize-phoenix
  • gemini-2.5-flash
  • google-gemini
  • lottie
  • lucide
  • naver-place-graphql
  • next.js
  • openinference
  • postgresql
  • pretendard
  • react
  • recharts
  • supabase
  • tailwind-css
  • typescript
  • vercel
  • vercel-blob
  • vercel-cron
  • vercel-workflow-devkit
Share this project:

Updates