About the project

Inspiration

The résumé-building market is stuck between two frustrating extremes: heavy, exhausting platforms where you fill out endless forms and follow flows that complicate your life instead of simplifying it — and "career coaching" services charging $100-200 USD that sell you theory but never actually build you anything usable, so you end up spending both time and money for nothing.

I hit this wall myself. When I sat down to update my own résumé for a job application, the experience was so exhausting, impractical, and unhelpful — even with tools from well-known brands — that I started questioning whether these were really "automation tools" at all, or just polished-looking "solutions" built to sell an idea rather than solve the actual problem.

So I stopped complaining and built the real thing: something fast, genuinely easy to use, that generates your résumé in seconds. The premise is simple — why write, when you can just talk?

What it does

Career Story Agent is a WhatsApp-based AI recruiter, powered end-to-end by the Gemini SDK for language processing and transcription, with a lightweight Node.js + Supabase backend keeping everything running smoothly. A candidate pays a small launch-price credit, then just sends a voice note describing their work experience the same casual way they'd tell a friend. Gemini translates that everyday language into a high-impact, ATS-optimized corporate format, and delivers the finished résumé — ready, clean, and friction-free — in whichever language the candidate needs, in seconds instead of hours. It's built for anyone: students, working professionals, people between jobs, or just someone curious to see their résumé reimagined. Technology should simplify our lives, not become one more headache.

How I built it

The backend is a single Node.js/Express service. Google Gemini (@google/genai) is the actual engine of the business — it conducts the interview, transcribes voice notes natively, detects and mirrors the candidate's language turn by turn, and produces the final résumé as structured JSON in one call. pdfkit renders that JSON into a professional, ATS-friendly PDF with a repeating watermark. Meta's official WhatsApp Cloud API is the messaging channel; Stripe (with native Promotion Codes, configured entirely on Stripe's own checkout — no custom coupon logic in my code) handles payment and 2-for-1 promotions; Supabase persists accounts, credits, and generated résumés; and every generated PDF is also backed up to Google Cloud Storage. The whole thing runs on Railway.

Because WhatsApp webhooks need an instant response, the server acknowledges every incoming message immediately and does the real work — Supabase lookups, Gemini calls, PDF generation — in the background, behind a per-phone-number queue that serializes a single user's messages without blocking anyone else's. Every external call (Gemini, WhatsApp, Cloud Storage) is wrapped in a timeout, so a network hiccup fails loudly and visibly instead of hanging forever.

Challenges I ran into

  • An unofficial WhatsApp gateway with a silent delivery bug. I initially built on Evolution API (a self-hosted, community-maintained WhatsApp gateway). Partway through, outbound messages started silently failing for any contact whose WhatsApp account had migrated to the newer "LID" addressing scheme — a known, unresolved upstream bug (error code 463) with no fix timeline. I migrated the entire messaging layer to Meta's official WhatsApp Cloud API mid-build, which meant rewriting the webhook payload parsing, the outbound send logic, and the voice-note media pipeline from scratch, under real time pressure.
  • A currency mismatch that silently declined real cards. My Stripe account settles in MXN, but the Payment Link was priced in USD — a combination that caused real customers' cards (mostly Mexican-issued) to be declined with "currency not supported," even though everything looked correctly configured. The fix required re-pricing in the account's settlement currency and understanding how Stripe's Adaptive Pricing actually resolves presentment vs. settlement currency.
  • A watermark that corrupted its own content. PDFKit's save()/restore() only captures the PDF graphics state, not its own text-size tracking — so a résumé line that overflowed onto a second page, right as the watermark redrew itself, inherited the watermark's larger font size and rendered like a stray heading. Debugging it meant understanding exactly which parts of PDFKit's state restore() does and doesn't roll back.
  • Keeping my own privacy policy honest. As the credit/account system matured, I realized my privacy policy still described data handling from an earlier, simpler version of the product. I had to rewrite it to accurately reflect what's actually retained (résumés and account state) vs. what's genuinely temporary (raw interview transcripts, cleared after each résumé is generated).

What I learned

The biggest lesson was about infrastructure trust: an unofficial API can get you to a working demo fast, but a silent, unresolved delivery bug in production — one that only affects a subset of real users — is a completely different risk than a bug you can reproduce and fix yourself. Migrating to an officially supported channel mid-hackathon was painful, but it was the right call for anything meant to actually serve real people.

I also came to appreciate how much of this business genuinely is AI in production, not AI bolted onto a traditional app: Gemini doesn't just summarize what a human already decided — it runs the interview, decides when enough has been gathered, and produces the final deliverable, with no human in the loop at any point between payment and PDF delivery.

Built With

Share this project:

Updates