← projects
Side project · shipping · salsa-collective.com

tanda — Salsa Collective Lead Intake

AI-powered Gmail inbox that never loses a lead

Go 1.23Supabase PostgresLM Studio (local LLM)Gmail OAuth2Calendar-aware AILovable admin UI
// 01 — problem

A salsa studio takes class, private-lesson, and event inquiries through their Gmail inbox. Messages slip between threads, replies stall, and there is no shared record of who owes a customer what. When a customer asks 'are there classes this week, and what do they cost?', the assistant has to answer from real, current studio availability — not a stale schedule — or it risks quoting the wrong night or inviting someone to a night the studio is closed.

// 02 — approach
  • 01Go worker polls the studio Gmail inbox (webhook later) and dedupes by gmail_message_id.
  • 02Each new message is parsed by a local LLM (LM Studio, OpenAI-compatible API) into structured lead fields: intent, style, level, confidence.
  • 03Transactional write to Postgres: leads, email_threads, and a draft_responses row with approval_status=pending.
  • 04Calendar-aware AI: the worker reads the studio's `events` and `event_series` tables, expands recurring weekly series into upcoming occurrences, and injects them as authoritative schedule context into every prompt — with today's date always included so the LLM can reason about 'this week' and 'next week'.
  • 05Break/closure series ('NO CLASSES — Summer Break') are rendered as full date ranges in a dedicated 'Closed dates' section, so the LLM never tells a customer classes are running during a closure.
  • 06Lovable admin UI talks only to Supabase — list/filter leads, edit drafts, approve, assign tasks. The worker never exposes an HTTP API.
  • 07On approval, the worker sends the reply via Gmail and stamps sent_at; task assignee notifications work the same way.
// 03 — result

Every inbound email becomes a triaged lead with a pre-written reply in seconds — one that answers scheduling questions from the studio's real calendar, so the AI doesn't invent nights or quote during closures. No customer mail is ever sent without a human approving it in Lovable, and the worker runs on a Mac or small VM with no inbound ports.

// 03b — architecture
Tanda worker-only architecture: Gmail → Go worker → LM Studio + Supabase, with Lovable admin UI reading Supabase directly.
Worker-only architecture — Lovable talks to Supabase directly; the Go worker has no HTTP API.
// 04 — key decisions
Why feed the studio calendar into the LLM

Scheduling is the question that's most likely to be wrong and most likely to cost the studio a customer. By injecting the real `events`/`event_series` tables (recurring series expanded, closures marked) into the prompt, the LLM answers from ground truth instead of a hardcoded schedule — and the calendar is declared authoritative over anything in thread history.

Why local LLM

Lead data is personal. LM Studio on the LAN keeps inference on-prem, removes a recurring cost line, and lets the studio swap models freely.

Why no HTTP API

Lovable talks to Supabase directly with the JS client. Skipping a worker API removes an auth surface, a deploy target, and an entire class of integration bugs.

Why drafts default to pending

Auto-send is a one-way door with customers. Drafts always land as approval_status=pending so a human is in the loop before anything leaves the studio inbox.

// 05 — further reading