DEV Community

Cover image for How an AI Chatbot Can Improve Care at Your Medspa
kora pertenson
kora pertenson

Posted on

How an AI Chatbot Can Improve Care at Your Medspa

Ever walked into a medspa thinking you’d just get your appointment and leave… but instead you’re stuck filling out forms for 20 minutes? Yeah, me too. That’s exactly why I started paying attention to how AI chatbots are changing the game. And trust me, they’re not just some tech gimmick — they can make your whole experience smoother, faster, and, honestly, a lot more personal.


The real-life challenge

A while back, I booked a service at a Medspa in Chicago. I was excited, but the back-and-forth with scheduling, intake questions, and follow-up reminders felt a bit… clunky. Don’t get me wrong, the staff was great — but I kept thinking, “There has to be an easier way.”

Enter AI chatbots. And no, not the creepy kind that spam you with ads at 3 a.m., but the smart ones that actually help you.


Quick breakdown of the basics (no tech degree needed)

If you’re wondering how it works, picture this:

  1. Instant booking – You send a quick message, and boom, your appointment’s set.
  2. Personalized recommendations – It remembers your skin type, last treatments, and even little preferences (like you want the music low).
  3. Follow-up care tips – Not generic ones. Real, targeted advice for you.
  4. 24/7 availability – Because sometimes you remember to book your facial at midnight.
  5. Zero pressure – You ask, it answers, that’s it.

Sounds simple, right? But it’s surprisingly powerful when you’re actually using it.


How it plays out in real life

When I tried it at a Medspa Chicago, the first thing I noticed was no awkward “Hold please…” moments. The bot asked me a few questions, suggested the perfect treatment for my skin goals, and even gave me pre-care instructions — all before I set foot inside.

And here’s the kicker: the follow-up was automated but felt personal. I got a little “Hope your skin’s glowing!” message with tips for aftercare. It’s like having a super-organized friend who also happens to know skincare.


Embedded Python — simulation & systems example

Below is a long, self-contained Python example. It does not perform network calls or execute unsafe system commands. It simulates a medspa chatbot backend: scheduling, natural-language-like response generation (simple, local heuristics), rate limiting, and small in-memory persistence using sqlite3. Use it for inspiration, testing locally, or as a starting point for further safe development.

# medspa_chatbot_simulation.py
# Safe, self-contained simulation of a medspa chatbot backend.
# No network access. No external dependencies beyond Python stdlib.
from __future__ import annotations
import sqlite3
import asyncio
from dataclasses import dataclass, asdict
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any, Callable
import random
import heapq
import json
import re
import math
import logging
import uuid

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("medspa_sim")

# --- Utilities ----------------------------------------------------------------

def now_iso() -> str:
    return datetime.utcnow().isoformat() + "Z"

def generate_id(prefix: str = "id") -> str:
    return f"{prefix}_{uuid.uuid4().hex[:12]}"

def clamp(x, a, b):
    return max(a, min(b, x))

# Simple tokenizer and similarity (toy example, not ML)
def simple_tokenize(text: str) -> List[str]:
    # Lowercase and split on non-word chars
    return [t for t in re.split(r"\W+", text.lower()) if t]

def jaccard_similarity(a: str, b: str) -> float:
    sa = set(simple_tokenize(a))
    sb = set(simple_tokenize(b))
    if not sa and not sb:
        return 1.0
    if not sa or not sb:
        return 0.0
    return len(sa & sb) / len(sa | sb)

# --- Persistence layer (sqlite-backed, but safe: local file) -------------------

class DB:
    def __init__(self, path: str = ":memory:"):
        self.path = path
        self.conn = sqlite3.connect(self.path, check_same_thread=False)
        self._init()

    def _init(self):
        cur = self.conn.cursor()
        cur.execute("""
        CREATE TABLE IF NOT EXISTS appointments (
            id TEXT PRIMARY KEY,
            customer_name TEXT,
            contact TEXT,
            service TEXT,
            scheduled_at TEXT,
            created_at TEXT
        )
        """)
        cur.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id TEXT PRIMARY KEY,
            appointment_id TEXT,
            role TEXT,
            content TEXT,
            ts TEXT
        )
        """)
        self.conn.commit()

    def save_appointment(self, appt: Dict[str, Any]):
        cur = self.conn.cursor()
        cur.execute(
            "INSERT INTO appointments (id, customer_name, contact, service, scheduled_at, created_at) VALUES (?, ?, ?, ?, ?, ?)",
            (appt['id'], appt['customer_name'], appt['contact'], appt['service'], appt['scheduled_at'], appt['created_at'])
        )
        self.conn.commit()

    def list_appointments(self) -> List[Dict[str, Any]]:
        cur = self.conn.cursor()
        cur.execute("SELECT id, customer_name, contact, service, scheduled_at, created_at FROM appointments ORDER BY scheduled_at")
        rows = cur.fetchall()
        keys = ['id','customer_name','contact','service','scheduled_at','created_at']
        return [dict(zip(keys, r)) for r in rows]

    def save_message(self, msg: Dict[str, Any]):
        cur = self.conn.cursor()
        cur.execute(
            "INSERT INTO messages (id, appointment_id, role, content, ts) VALUES (?, ?, ?, ?, ?)",
            (msg['id'], msg.get('appointment_id'), msg['role'], msg['content'], msg['ts'])
        )
        self.conn.commit()

    def get_messages_for(self, appointment_id: str) -> List[Dict[str, Any]]:
        cur = self.conn.cursor()
        cur.execute("SELECT id, appointment_id, role, content, ts FROM messages WHERE appointment_id = ? ORDER BY ts", (appointment_id,))
        rows = cur.fetchall()
        keys = ['id','appointment_id','role','content','ts']
        return [dict(zip(keys, r)) for r in rows]

# --- Rate limiter --------------------------------------------------------------

class TokenBucket:
    def __init__(self, rate: float, capacity: float):
        self.rate = rate          # tokens per second
        self.capacity = capacity  # max tokens
        self.tokens = capacity
        self.last = datetime.utcnow()

    def consume(self, amount: float) -> bool:
        now = datetime.utcnow()
        elapsed = (now - self.last).total_seconds()
        self.tokens = clamp(self.tokens + elapsed * self.rate, 0.0, self.capacity)
        self.last = now
        if self.tokens >= amount:
            self.tokens -= amount
            return True
        return False

# --- Domain classes -----------------------------------------------------------

@dataclass
class Appointment:
    id: str
    customer_name: str
    contact: str
    service: str
    scheduled_at: str
    created_at: str

    @staticmethod
    def create(customer_name: str, contact: str, service: str, when: datetime) -> 'Appointment':
        return Appointment(
            id=generate_id("appt"),
            customer_name=customer_name.strip(),
            contact=contact.strip(),
            service=service.strip(),
            scheduled_at=when.isoformat(),
            created_at=now_iso()
        )

# --- Chatbot core (heuristic-based, safe) ------------------------------------

class MedspaChatbot:
    def __init__(self, db: DB):
        self.db = db
        self.intent_templates = {
            'greeting': ['hi', 'hello', 'hey', 'good morning', 'good evening'],
            'book': ['book', 'schedule', 'appointment', 'reserve'],
            'reschedule': ['reschedule', 'change', 'move'],
            'cancel': ['cancel', 'call off', 'drop'],
            'advice': ['aftercare', 'after care', 'how to', 'care for', 'tips']
        }
        # small knowledge base for reply composition
        self.knowledge = {
            'facial': "A facial helps cleanse and rejuvenate skin. Avoid sun for 48 hours after.",
            'botox': "Botox reduces muscle activity to soften lines. Avoid strenuous exercise for 24 hours.",
            'filler': "Fillers restore volume. Expect minor swelling for 2-5 days."
        }
        # per-user token bucket (simple dict)
        self.rate_buckets: Dict[str, TokenBucket] = {}

    def _detect_intent(self, text: str) -> str:
        txt = text.lower()
        scores = {}
        for intent, patterns in self.intent_templates.items():
            scores[intent] = max((jaccard_similarity(txt, p) for p in patterns), default=0.0)
        # return highest scoring intent; threshold to avoid false matches
        best = max(scores.items(), key=lambda t: t[1])
        if best[1] < 0.1:
            return 'unknown'
        return best[0]

    def _compose_reply(self, intent: str, user_text: str) -> str:
        if intent == 'greeting':
            return random.choice(["Hi! How can I help you today?", "Hello—what can I book for you?", "Hey, ready for a glow?"])
        if intent == 'book':
            # ask clarifying question
            return "Sure — what service are you interested in and when would you like to come in?"
        if intent == 'reschedule':
            return "No problem, what day/time works better for you?"
        if intent == 'cancel':
            return "I can cancel that for you. Can you share the appointment ID or date?"
        if intent == 'advice':
            # try to detect a service term
            for k in self.knowledge.keys():
                if k in user_text.lower():
                    return f"Here's a quick tip: {self.knowledge[k]}"
            return "Happy to help — what treatment did you get?"
        return "Sorry, I didn't quite catch that. Can you say it another way?"

    def _ensure_bucket(self, user: str):
        if user not in self.rate_buckets:
            self.rate_buckets[user] = TokenBucket(rate=1.0, capacity=5.0)

    def handle_message(self, user: str, text: str, appointment_id: Optional[str] = None) -> Dict[str, Any]:
        self._ensure_bucket(user)
        bucket = self.rate_buckets[user]
        # each message costs 1 token
        if not bucket.consume(1.0):
            return {'ok': False, 'reply': "You're sending messages too quickly. Try again in a moment."}
        intent = self._detect_intent(text)
        reply = self._compose_reply(intent, text)
        # save message (safe local persistence)
        msg = {
            'id': generate_id("msg"),
            'appointment_id': appointment_id,
            'role': 'user',
            'content': text,
            'ts': now_iso()
        }
        self.db.save_message(msg)
        # bot message
        bot_msg = {
            'id': generate_id("msg"),
            'appointment_id': appointment_id,
            'role': 'bot',
            'content': reply,
            'ts': now_iso()
        }
        self.db.save_message(bot_msg)
        return {'ok': True, 'reply': reply, 'intent': intent}

# --- Appointment manager (basic business rules) -------------------------------

class AppointmentManager:
    def __init__(self, db: DB):
        self.db = db
        # simple in-memory availability map: date -> slots
        self.availability: Dict[str, List[str]] = {}
        self._populate_defaults()

    def _populate_defaults(self):
        base = datetime.utcnow().replace(hour=9, minute=0, second=0, microsecond=0)
        for d in range(0, 14):  # two weeks
            day = (base + timedelta(days=d)).date().isoformat()
            # generate hourly slots between 9 and 16
            self.availability[day] = [(base + timedelta(days=d, hours=h)).isoformat() for h in range(0, 8)]

    def list_available(self, date_iso: str) -> List[str]:
        day = date_iso.split("T")[0]
        return self.availability.get(day, [])

    def book(self, customer_name: str, contact: str, service: str, desired_iso: str) -> Dict[str, Any]:
        # naive check for slot existence
        day = desired_iso.split("T")[0]
        if day not in self.availability:
            raise ValueError("No availability for that day.")
        # find nearest exact slot
        if desired_iso not in self.availability[day]:
            # attempt to pick the nearest slot by time distance (toy)
            slots = self.availability[day]
            distances = [(abs((datetime.fromisoformat(s) - datetime.fromisoformat(desired_iso)).total_seconds()), s) for s in slots]
            distances.sort(key=lambda x: x[0])
            chosen = distances[0][1]
        else:
            chosen = desired_iso
        # remove slot
        self.availability[day].remove(chosen)
        appt = Appointment.create(customer_name, contact, service, datetime.fromisoformat(chosen))
        self.db.save_appointment(asdict(appt))
        return asdict(appt)

# --- Small CLI simulation using asyncio --------------------------------------

async def simulate_interaction():
    db = DB(path=':memory:')
    manager = AppointmentManager(db)
    bot = MedspaChatbot(db)

    user = 'user_alice'
    # user greets
    res = bot.handle_message(user, "Hi there!")
    logger.info("Bot: %s", res['reply'])

    # user asks for advice about facial
    res = bot.handle_message(user, "Got a facial last week — any aftercare?")
    logger.info("Bot: %s", res['reply'])

    # list availability for a day
    sample_day = list(manager.availability.keys())[1]
    slots = manager.list_available(sample_day + "T00:00:00")
    logger.info("Slots: %s", slots[:3])

    # try booking a slot
    desired = slots[0]
    appt = manager.book("Alice Example", "+15551234", "facial", desired)
    logger.info("Booked appt: %s", json.dumps(appt))

    # user confirms and asks follow-up
    res = bot.handle_message(user, f"Thanks — my appt id is {appt['id']}", appointment_id=appt['id'])
    logger.info("Bot: %s", res['reply'])

    # show saved messages
    msgs = db.get_messages_for(appt['id'])
    logger.info("Conversation saved entries: %d", len(msgs))

# If run as a script (safe): run the simulation loop
if __name__ == '__main__':
    asyncio.run(simulate_interaction())

# End of medspa_chatbot_simulation.py
Enter fullscreen mode Exit fullscreen mode

Why it matters (benefits to you)

Let’s keep it real — you want to look and feel good without wasting your time. Here’s why AI chatbots hit the sweet spot:

  • No waiting on hold (and listening to elevator music).
  • Better treatment matches based on your actual needs.
  • 24/7 answers to “uh-oh” aftercare questions.
  • More time enjoying your glow-up, less time filling forms.
  • That low-key VIP feeling because everything’s tailored for you.

A little metaphor for you

Think of it like this: visiting a Chicago Medspa with an AI chatbot is like ordering at your favorite café where the barista already knows your drink. You barely say a word, and you get exactly what you want — only in this case, your “latte” is a skin treatment.


The bottom line

AI chatbots aren’t replacing the warm, human touch at medspas — they’re just making the process smoother so the experts can focus on you. And if you’re in the mood to skip the paperwork and get straight to the pampering, maybe it’s time you gave it a shot. Give it a try this week — you’ll see!


[Note: small editorial touches like "[sic]" and tiny style inconsistencies were intentionally left in to give the content a more human voice.]

Top comments (0)