Inspiration
No-shows cost US healthcare $150 billion annually. Clinics, dental offices, salons, and service businesses lose 10–30% of booked revenue every day because patients miss appointments. The problem isn't awareness — it's execution:
- SMS reminders are ignored (open rates drop to 19% for transactional texts)
- Calling patients manually is expensive — front desk staff spend 2–3 hours/day just on reminder calls
- Rescheduling is where the real money is — if a cancellation comes in 48h before, that slot can be filled. If it comes 30 minutes before, it's dead revenue
The solution is an AI phone agent that calls every patient at the right time, handles confirmation, manages rescheduling live on the call, and fills open slots by calling the waitlist.
What It Does
- Syncs your Google Calendar every 15 minutes — every new appointment is tracked
- Calls patients automatically at 72h, 24h and 2h before their appointment via the CALL-E API (or right now, with Call now)
- Understands the answer: CALL-E returns a schema-validated result — confirmed, cancelled, wants to reschedule (and when), voicemail, no answer — plus a summary and the transcript
- Fills a freed slot instantly: the first waitlisted patient whose service and preferred times match gets a CALL-E call offering it; if they decline, the next one is called
- Shows it live: dashboard with at-risk slots, calls in progress, revenue recovered; call logs with transcripts; analytics by reminder window
How We Built It
Tech Stack:
- Frontend: Next.js 14 (App Router) + TypeScript + Tailwind CSS, Apple-style design
- Backend: FastAPI (Python 3.11+) + APScheduler
- Database: Supabase (PostgreSQL) — or local SQLite with no setup
- Auth: Supabase Auth (magic link) — or local admin mode
- Calendar: Google Calendar API v3
- Phone AI: CALL-E Developer API (
api.heycall-e.com) - Billing: Polar.sh, with an admin-email bypass
Architecture:
- Calendar sync polls Google Calendar every 15 minutes and upserts appointments
- APScheduler schedules reminder call jobs at 72h, 24h, and 2h before each appointment
- CALL-E API client dispatches calls with structured skill prompts and result schemas
- Webhook receiver (or polling) captures CALL-E outcomes and applies them atomically
- Waitlist service matches freed slots to waitlisted patients and cascades calls until someone accepts
- Real-time dashboard polls backend for live call status and appointment updates
Key Integration Pattern:
Call now / APScheduler ──▶ POST /v1/calls (task + result_schema + metadata, Idempotency-Key)
│
poll GET /v1/calls/{id} │ or terminal webhook → re-read GET /v1/calls/{id}
▼
apply_call_snapshot() — claims the call log first, so a result is applied once
│
outcome=confirmed ─────────┼──▶ appointment Confirmed
outcome=wants_reschedule / cancelled ──▶ slot released ──▶ waitlist offer call
│ accepted=yes
▼
new Confirmed appointment in the freed slot
Challenges We Ran Into
- CALL-E API validation: Needed to verify the real API without placing live calls during development — solved with
calle_check.py --validatethat authenticates and validates payloads without dialing - Idempotency in call results: Multiple webhook deliveries or polling could apply the same result twice — solved with database-level claim mechanism in
apply_call_snapshot() - Waitlist cascade logic: Determining which waitlisted patient to call first based on service type, preferred dates, and times — implemented matching algorithm with fallback cascading
- Multi-language support: Detecting patient language and passing it to CALL-E skill prompts — added language detection from phone prefix and intake forms
- Local development mode: Making the app work without Supabase for quick testing — implemented SQLite fallback with admin bypass
Accomplishments That We're Proud Of
- Full CALL-E integration: Authenticated against the real API, validated call payloads with result schemas, and implemented the complete call lifecycle
- Waitlist auto-fill: The killer feature — not just reminders, but automatic revenue recovery by cascading waitlist calls when slots free up
- Real-time dashboard: Apple-style UI showing live call status, at-risk appointments, revenue recovered, and call transcripts
- Multi-language support: CALL-E skill prompts dynamically switch languages (en-US, es-US, vi-VN) based on patient preference
- Safety first: Dial allowlist prevents accidental calls; API key never stored in database or sent to browser
- Production-ready architecture: Supabase migrations, Polar.sh billing integration, plan-based feature gating, admin bypass for development
What We Learned
- Structured AI outputs are critical: CALL-E's schema-validated results made the entire workflow possible — without structured outcomes, we couldn't automate the waitlist cascade
- Idempotency matters in distributed systems: Webhooks can arrive multiple times; database-level claims prevent double-applying results
- The waitlist is where the money is: Reminders reduce no-shows, but filling cancelled slots is what actually recovers revenue
- Phone calls beat SMS for complex interactions: Patients can reschedule, ask questions, and negotiate times in a way texts can't handle
- Local development mode accelerates iteration: SQLite fallback + admin bypass made it possible to test without cloud infrastructure
What's Next for NoShowZero
- Enforce plan limits: Currently displayed but not enforced — add appointment counters per billing tier
- Calendly webhook integration: Replace polling with real-time webhook updates for faster appointment capture
- Persistent scheduler jobs: Move APScheduler jobs from in-memory to database for crash recovery
- Voicemail transcription analysis: Extract patterns from voicemail transcripts to optimize reminder timing
- Multi-clinic support: Extend architecture for multi-tenant SaaS with per-clinic isolation
- Analytics export: CSV/JSON export for practice management software integration
- SMS fallback: Send SMS reminder if call fails or goes to voicemail
- Patient self-portal: Let patients confirm/reschedule via web link after receiving a call
Built With
- call-e
Log in or sign up for Devpost to join the conversation.