← Back to Blog
System Architecture

Why We Killed Our AI Calendar Agent — And Built Something Better

3
AI Agents Replaced
1
SQLite Table
60
Lines of JS

For the past 6 months, we've been running an AI-powered booking system. Customers would text us, an AI agent named Steve would qualify them, another AI agent named Anna would manage the calendar and find slots, and a third agent named Dev would handle follow-ups and reminders.

It worked. Kind of. The pipeline had 6 compliance gates, a double-gated token locker, GPG encryption, and bootstrap hash verification. Over 3,900 lines of bash across 9 scripts. Three Docker containers. It was technically impressive — a proprietary agentic system that could draft SMS replies, detect booking intent, find available slots, and send confirmations.

But here's what we learned: for post-booking automation, AI is complete overkill.

The Problem

Three AI agents. Three Docker containers. Three queue tables. Three sets of bootstrap files. Every booking confirmation, reminder, and follow-up had to pass through the full 6-gate compliance pipeline — token locker and all — just to send "Your booking is confirmed at 10:30am."

The complexity created real, production-breaking problems:

Every time we wanted to change a reminder message, it meant editing bootstrap files, recomputing MD5 and SHA256 hashes, restarting Docker, clearing caches, and hoping the token locker would re-register. Changing one word in one SMS required touching 4 files.

The New System: One Table, One Daemon

We archived Anna entirely. We removed the hold system, the slot tracker, the anna_queue, the calendar bridge in Gate 5. Then we built something so simple it's almost embarrassing to explain:

The message_queue Table

CREATE TABLE message_queue (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  phone TEXT NOT NULL,
  message TEXT NOT NULL,
  template_key TEXT,
  scheduled_at DATETIME NOT NULL,
  status TEXT DEFAULT 'pending',
  sent_at DATETIME,
  booking_id INTEGER
);

That's it. A single SQLite table. Rows get inserted with a phone number, a pre-written message, and a future delivery date. A PM2 daemon polls every 30 seconds, finds pending messages where scheduled_at <= now, sends them directly via the Join SMS API, and marks them done.

No AI. No gate pipeline. No token locker. No Docker containers. No bootstrap hashes. Just rows in a table and a 30-second loop.

What Happens When Someone Books

When a customer books at myairconcare.com/book, six messages get queued automatically:

  1. Confirmation — immediate: "You're booked in! 🎉" with details, what to expect link, and a change/cancel link
  2. 2-month reminder — loyalty discount reminder with keep-or-cancel link
  3. 1-month reminder — final discount lock-in, keep-or-cancel link
  4. 48-hour reminder — prep instructions, job info link
  5. Morning-of — 7am on the day: "We're on our way!"
  6. Thank you — 4pm after: review link + one-click rebook for next year

Every message includes a link back to the booking page where customers can self-serve — change dates, cancel (with a discount warning popup), or rebook for next year. Steve handles inbound SMS only. Everything else is links and automation.

The Daemon Itself

message-daemon.js — Core Logic

const CURFEW_START = 480;   // 8am AEST
const CURFEW_END = 1439;    // 11:59pm AEST
const MAX_RETRIES = 3;
const DEDUP_WINDOW_MIN = 5; // minutes

async function processQueue() {
  if (!inSendingWindow()) return;

  const messages = await dbAll(
    `SELECT * FROM message_queue
     WHERE status='pending'
     AND scheduled_at <= datetime('now','+10 hours')
     ORDER BY scheduled_at LIMIT 5`);

  for (const msg of messages) {
    // Anti-spam: max retries, dedup check
    // Send via Join API directly (no gate, no token)
    // Mark sent or retry
  }

  // Daily brief at 9pm — tomorrow's jobs with Apple Maps links
}

The daemon runs as a PM2 process. It enforces curfew (8am-11:59pm AEST), prevents spam (max 3 retries, 5-minute dedup window), and every night at 9pm generates a daily brief with tomorrow's bookings and Apple Maps links sent directly to Jason.

Compare this to the old system: Gate 0 (fingerprint 6 bootstrap files) → Gate 1 (context check) → Gate 2 (draft validation) → Gate 3 (8-point compliance) → Gate 4 (double-token GPG handshake with PC locker) → Gate 5 (CRM log + Anna delegation) → Gate 6 (clearance token). All of that. To send one reminder SMS.

What We Gained

Steve Is Still Here

We didn't kill Steve. He's still the front-line SMS agent — qualifying leads, quoting prices with exact dollar amounts, and when someone's ready to book, sending them a personalized link to the booking page. He doesn't manage the calendar anymore. He doesn't "hold slots" or "check availability" or "delegate to Anna." He just talks to customers and sends links. His bootstrap files dropped from 6 to 4 active files. His pipeline still runs through all 6 gates — but Gate 5 no longer delegates to a non-existent calendar agent.

The booking page handles slot selection, payment (3 tiers: pay in full with $30 off, $50 deposit, or pay on day), and confirmation. The message daemon handles reminders and follow-ups. Steve handles conversation. Each piece does one thing well.

The Same Pattern Replaces Follow-Ups Next

The most exciting part: this exact same system replaces our entire follow-up pipeline. Instead of scheduling messages months and days out, we use seconds and minutes. Cold lead autoresponders. Nurture sequences. Re-engagement campaigns. All driven by the same message_queue table and the same 60-line daemon.

No Dev agent. No followup_queue. No separate Docker container. Just rows in a table with scheduled_at timestamps.

The Lesson

AI is incredible for conversations. It's terrible for scheduling. When you need reliability, simplicity, and zero hallucination risk, a SQLite table and a 30-second loop beats a 6-gate AI pipeline every single time.

We built an agentic calendar manager because it sounded cool. We replaced it with a database table because it actually works.

The full system — self-service booking page, Steve SMS agent, message daemon, 6-message lifecycle, one-click rebook, discount-protected cancel flow, daily brief with Maps links — was built in a single 14-hour session. No new infrastructure. No external services. Just Node, SQLite, PM2, and Join.

Book Your Air Con Clean Online