OneChair
Fully booked. Hands-free.
Inspiration
It started with a cancelled appointment.
A friend of ours is a nail tech with a full client list, a Instagram following, and a one-woman operation. She told us she'd lost three bookings in a week. Because she was too busy when the DMs came in. By the time she finished and picked up her phone, those clients had already booked somewhere else.
Independent beauty professionals in the US are a $50B industry built almost entirely on solo operators who wear every hat at once, artist, accountant, marketer, and receptionist. The booking problem isn't a scheduling problem. It's an attention problem. No amount of scheduling software fixes the fact that a stylist cannot respond to a message while she's doing a full set.
We started asking stylists how many bookings they lose a month to unanswered messages. Answers ranged from two to eight. At an average service price of $150, that's:
Monthly lost revenue = Average missed appointments × Average service price = 5 × $150 = $750 per month
Per stylist. Per month. Just from not being able to answer a message while working. That's $9,000 a year sitting on the table for someone running a one-person business on tight margins. We built OneChair to give that back.
What It Does
OneChair gives every independent hair and nail stylist an AI receptionist that works while they do.
For the stylist: Sign up in three minutes. Enter your services, pricing, and working hours. Connect your Google Calendar. You get a personal booking link — onechair/book/sarah — to drop in your Instagram bio, Google Business Profile, or anywhere clients find you. From that point, OneChair handles every new client conversation automatically.
For the client: Tap the link. A chat interface opens. No app, no account, no friction. The AI introduces itself as the stylist's receptionist, asks what service they're looking for, finds a real available time from the stylist's calendar, and walks them through leaving a deposit. The whole booking takes under five minutes.
The full loop, automatically:
| Step | What happens |
|---|---|
| Client messages | AI starts the conversation, collects service, name, and preferred times |
| Availability check | Real free/busy query against the stylist's Google Calendar |
| Time proposed | AI offers a specific slot; client confirms |
| Deposit collected | Stripe Checkout link sent in-chat; appointment held on payment |
| Confirmation | Email sent to client with date, time, and details |
| Calendar updated | Confirmed event written to stylist's Google Calendar |
The stylist finishes their current client, glances at their dashboard, and sees a new confirmed booking with deposit paid that came in while their hands were full.
How We Built It
Architecture
onechair/book/[stylist-slug]
│
▼
Chat widget (React + Vite)
│
▼
/api/chat ──► Gemini AI API
│ │
│ Conversation engine
│ (state machine + stylist context)
│
├──► Supabase (conversation + appointment state)
├──► Google Calendar API (availability + event creation)
├──► Stripe Checkout (deposit collection)
└──► Resend (confirmation emails)
Background sync service (1-minute interval)
└──► Calendar synchronization
Stack
- Frontend: React with Vite — TypeScript, Tailwind CSS, React Router
- AI: Gemini AI API — the conversation engine; receives stylist context and conversation history
- Database: Supabase (Postgres) — conversation state, stylist profiles, appointments, row-level security per stylist
- Calendar: Google Calendar API — real free/busy queries; confirmed events written on payment webhook
- Payments: Stripe Connect — deposit collection with embedded onboarding for stylists
- Email: Resend — booking confirmations, stylist new-booking alerts
- Scheduling: Background calendar sync service — runs every 1 minute, handles real-time calendar changes
- Infrastructure: Docker, Nginx, VPS deployment
The conversation engine
The Gemini integration handles the conversation flow through a state machine. The AI receives the stylist's services, pricing, working hours, and conversation context. It guides clients through the booking process: service selection → date/time → name → phone → email → confirmation → payment.
The deposit and confirmation flow
| Appointment status | Meaning |
|---|---|
tentative |
Time agreed; awaiting payment |
confirmed |
checkout.session.completed webhook received |
slot_lost |
The slot was taken by a concurrent booking during payment |
Two clients can be offered the same slot simultaneously. We resolved this with a first-to-pay wins rule: no DB locks, no hold timers. At the Stripe webhook, we re-check availability before confirming. If the slot was taken, we void the payment intent and re-enter the conversation with fresh slots. The losing client is never charged.
Challenges We Ran Into
Earning the stylist's trust in the AI
Stylists were excited about the concept and nervous about handing it control. They asked the same question: "What if it says something wrong to my client?" Their instinct was to add an approval step every booking requires confirmation before it's finalized.
We pushed back on this because an approval step eliminates the core value proposition. A stylist who has to check their phone to approve each booking is still checking their phone. The real answer wasn't a control mechanism. It was predictability. We tightened the system prompt until the AI's responses were consistent and on-brand enough that stylists stopped feeling like they needed to supervise it. We then replaced the approval flow with a real-time dashboard: every conversation visible, every state readable, a direct contact button for each client. Transparency instead of control.
The 24-hour refund window across timezones
The policy is simple to state: deposit refundable outside 24 hours, non-refundable within. It was the tricky to implement correctly.
The trap: a client in a different timezone cancels at what feels like "two days before" to them, but falls inside the 24-hour window in the stylist's local time. Every appointment is stored in UTC. Every window calculation happens in the stylist's configured timezone, not the server's, not the client's. We wrote a test suite that exercises the boundary at T‑24:00:00 and T‑23:59:59 in each timezone, including across daylight saving transitions.
Recognizing the architecture that was killing us
Our original plan had more complex integrations that would have made onboarding difficult. We had to accept that the most complex architecture wasn't the most valuable one. Focusing on the core web chat experience with essential integrations (Google Calendar, Stripe, Resend) made the product faster to build, more reliable to demo, and something a nail tech could actually sign up for between clients.
Accomplishments That We're Proud Of
The three-minute onboarding. A stylist can go from the landing page to a live, working booking link with real calendar integration in under three minutes. Embedded Stripe onboarding and Google Calendar OAuth eliminate complex setup steps.
The conversation engine architecture. The state machine approach with Gemini AI integration means every conversation turn is independently testable. We can replay any booking conversation from its stored transcript and verify the AI's output.
Solving the slot race condition cleanly. Two clients, same available slot, both in Stripe Checkout simultaneously. The first-to-pay-wins rule with webhook-time re-verification means no double bookings, no held deposits for slots that don't exist, and no complex locking infrastructure.
Getting the timezone math right. It sounds small. Anyone who has implemented time-sensitive financial policy enforcement across arbitrary timezones and DST transitions knows it isn't.
The name. OneChair captures the whole product in one word. One stylist, one chair, one client at a time, and the thing that makes it all possible is that the chair stays full without the stylist having to manage it.
What We Learned
The channel question is everything
Our first instinct was SMS by giving every stylist a dedicated "text to book" number and let the AI handle conversations there. We spent real time on this before realizing we were solving the wrong surface. Stylists don't lose clients to missed texts. They lose them to missed Instagram DMs. That's where discovery happens and where "are you taking new clients?" lands.
Building an SMS receptionist meant asking clients to change their behavior before experiencing any value. A web booking link requires no behavior change at all. Clients already tap links in Instagram bios. Same AI, same conversation, different entry point, dramatically lower friction.
The product is the conversation
Every technical complexity we stripped away made the core product more visible: an AI that talks to clients the way a great receptionist would. Getting that conversation right, making it feel warm and specific to each stylist rather than generic and form-like, turned out to be where the actual product value lived. Everything else was delivery infrastructure.
$$\text{Completion rate} \approx \frac{\text{conversations reaching payment}}{\text{conversations initiated}}$$
Moving that number up was almost entirely a prompt engineering problem, not a features problem.
What's Next for OneChair
The core loop is proven: client chats, books, pays, stylist's calendar stays full without lifting a finger. The schema and conversation engine are already built to support what comes next.
- 48-hour reminder emails — automated reminder system with configurable timing
- Working hours enforcement — ensure bookings only fall within stylist's available hours
- WhatsApp bookings — the conversation engine is channel-agnostic; adding WhatsApp is a surface change
- Instagram DMs — Meta integration for direct Instagram messaging
- Stylist voice — let stylists describe their personality and vibe in plain English for more personalized AI responses
- Client memory — returning clients get recognized, greeted by name, and offered their usual service first
- No-show recovery — automated follow-up sequence for missed appointments
- Stylist-to-stylist referrals — when a stylist is fully booked, the AI can refer the client to a trusted colleague
The tagline holds for all of it: Fully booked. Hands-free.
Built With
- docker
- fastapi
- google-calendar
- google-gemini
- google-oauth
- javascript
- nginx
- node.js
- postgresql
- python
- react
- redis
- resend
- stripe
- supabase
- tailwind
- typescript
- vite
Log in or sign up for Devpost to join the conversation.