← Back to all projects

Case Study — Soodori (수돌이)

Two people close to me were each paying a lot for different fortune-telling apps. I thought I could build a better one.

Spotted a real market from watching people around me pay for saju apps. Built and shipped a multilingual AI fortune-telling product solo in under a week. Then cut the bundle size by 89%.


Problem

Two people close to me were each paying a lot for different saju (四柱, Chinese astrology) services. I watched them use it, and I kept hearing friends discuss their charts like it was the most important thing in the world. That told me this wasn't a niche hobby. People take this seriously, and they pay for it. I looked at what's out there. Posteller (포스텔러, 860M+ cumulative users) gives you pre-written readings based on your birth info. Useful, but there's no conversation. You can't ask follow-ups or go deeper on something specific. Jeomsin (점신) connects you with real fortune tellers over the phone, and they make money the longer the call goes. It works, but it's expensive, awkward for a lot of people, and you're at the mercy of whoever picks up. Then there's ChatGPT and general-purpose LLMs. People try them for saju readings, but they can't even calculate the four pillars correctly. They hallucinate chart data and have no understanding of the actual calendar system (만세력) behind it. That was the gap. Combine the accuracy of a properly tuned calculation engine with the conversational depth of AI, in a UX where you don't have to talk to a stranger on the phone. Just type and get real answers about your actual chart.


My Role

Entirely solo. Market research, product decisions, full-stack development, AI prompt engineering, domain-specific calculation logic, performance optimization, and deployment.


Process

Product

  1. 1

    Studied how the existing players monetize. Posteller charges per reading. Jeomsin connects you with fortune tellers over the phone and makes money the longer the call goes. Both profit from volume and time spent. My product is fundamentally different: conversational AI where users ask follow-ups and come back with new questions. I set up a freemium model (5 free conversations, then subscription tiers) to test willingness to pay before investing in growth.

  2. 2

    Built the core loop: birth info input, four pillars calculation, then AI chat. The key differentiator is the system prompt. I spent more time on prompt engineering than on the UI. The AI references the user's specific elements, explains jargon in plain language, and builds on previous answers. It feels like talking to someone who actually read your chart, not a template.

  3. 3

    Launched with zero marketing budget. Shared it with a few friends first, and they started spreading it through word of mouth. I built a referral system where users can share a referral code with friends even without paying, which lowered the barrier to invite others. 10 users signed up on day one, all organic.

UX

  1. 1

    The home screen adapts based on how many profiles you have saved. New users see the birth info form right away. Returning users get a one-tap start button so they never re-enter their info. This sounds simple, but it required rethinking the entire page layout to handle both cases without feeling like two different apps.

  2. 2

    The compatibility feature went through several iterations. The first version had separate menus for 'my compatibility' vs 'between two other people,' which confused testers. Simplified it to one flow: just pick any two people from your saved profiles.

  3. 3

    Even small labeling choices mattered. The Korean word for the romantic relationship option went through three rounds. The first assumed the couple was already dating. The second excluded same-sex relationships. The final choice ('애정') covers all orientations and relationship stages.

  4. 4

    Supported three languages (Korean, English, Chinese) from day one. Not just translated strings, but adapted UX patterns. Korean users expect different date formats, different honorifics in the AI responses, and different cultural framing of the same BaZi concepts.

Engineering

  1. 1

    Chose Next.js 14 with Supabase because I needed to ship fast, alone. Server-side API routes let me hide the LLM API key and keep sensitive logic off the client. Supabase gave me auth, database, and row-level security without managing infrastructure. Deployed on Vercel for instant previews during iteration.

  2. 2

    Solved a domain-specific accuracy problem that most apps get wrong. BaZi calculation depends on true solar time, not just timezone. A birth at 11:50 PM in western China (UTC+8) is actually a different solar hour than the same clock time in Shanghai, because the timezone spans 60 degrees of longitude. I implemented longitude-based correction using the city's coordinates, which can shift the hour pillar by up to 2 hours. Getting this wrong means the entire reading is based on the wrong chart.

  3. 3

    Encrypted birth data with AES-256-GCM before storing in Supabase. Birth date, time, and location are genuinely sensitive personal information, and users trust the app with data they wouldn't share publicly.

  4. 4

    Profiled the production build and found 1.9 MB of JavaScript being shipped to every visitor on page load. Traced it to three libraries: a 44,000-city timezone database (1.4 MB), the BaZi calculation engine (272 KB), and an HTML-to-canvas screenshot library (194 KB). All loaded on page mount despite only being needed on specific user actions.

  5. 5

    Moved the city database to a server-side API route, eliminating 1.4 MB from the client entirely. Converted the calculator and screenshot library to dynamic imports triggered on user action. Replaced render-blocking Google Font @imports with next/font. Result: page JS went from 415 kB to 45.5 kB, an 89% reduction.


Evolution

v0.0.1

Initial launch

Shipped solo in under a week. Minimum viable product — accurate four-pillars calculation, conversational AI on top, three languages. Put it in front of friends and friends-of-friends and watched who stuck around.

v0.0.1 Initial launch

What I noticed

Day one: 10 organic signups through word of mouth. But the pattern in who returned and shared was specific — women in their 20s. They weren't just reading their own chart. They were screenshotting pieces of it and sending them to friends in group chats. That told me the growth loop here wasn't individual retention. It was social sharing — and the product wasn't built for it.

v1.0.0

Redesigned for shareability

Rebuilt the product around the behavior I actually saw. The goal was no longer 'can one person get value?' — it was 'can this spread through a friend group?' Every design decision was made to lower the friction of sharing.

  • Full visual redesign — softer, more character-driven language that felt natural to screenshot and send
  • Character cards — each reading generates a shareable card summarizing the user's BaZi archetype
  • Referral system — invite friends, both sides unlock something
  • Review system — social proof surfaced for new visitors arriving from shared links
v1.0.0 Redesigned for shareability

Outcomes

10

Organic signups on day one via word of mouth

-89%

Client JS reduced: 415 kB → 45.5 kB

3

Languages supported: Korean, English, Chinese


Stack

Next.js 14TypeScriptLLM APISupabaseTailwind CSSVercel

What I Learned

The biggest lesson was about knowing when you're looking at a real market versus a personal interest. I didn't start with an idea for an app. I started by noticing that people around me were already paying for something, and the existing products weren't that good. That's a very different starting point than 'I think this would be cool to build.' On the technical side, I learned to measure before optimizing. I assumed the BaZi calculator would be the heaviest part of the bundle. It wasn't. A city timezone database I'd barely thought about was 73% of the problem. I only found it by reading the raw bytes of each webpack chunk, not by guessing. The other thing that stuck with me: every static import is a bet that the user needs that code immediately. For most libraries, that bet is wrong. Aligning when code loads with when the user actually needs it is a simple principle, but applying it cut the bundle by 89%.