Skip to content
← Back to work
Case Study / Cabana

Cabana

A pool-service demo for reporting problems and tracking repairs. AI can route a repair to a fixed $75 test deposit step before staff approval; staff approve work and assign visit times.

$75 Stripe test depositsTelegram approval and schedulingAirtable completion and visit notes20 evidence checks · one demo run

Overview

Cabana is a demo portal for a fictional pool-service company. Customers can report problems, track repairs and pay test deposits.

AI classifies requests and drafts acknowledgments for staff. Eligible repair classifications can open a fixed $75 test deposit step before staff approval. Staff approve work and assign visit times.

RoleSolo build with AI-assisted coding
Year2026
DomainField-service operations · fictional company
StackNext.js 15 (Vercel) · Supabase (Postgres · RLS · Edge Functions) · Stripe Checkout · n8n · Airtable · Telegram · Claude Haiku · Resend
StatusPublic demo · fictional data · Stripe test mode

Walkthrough and verified results

One repair booking was checked end to end on October 25, 2026: request, $75 Stripe test deposit, Telegram approval and scheduling, Airtable completion, and visit notes in Service history.

62 seconds · captioned live checkpoints · fictional service data · Stripe test mode. No real money moved.

Open the walkthrough full size · Read the verification record

The latest full-flow test ran on October 26, 2026, using a separate QA account and email sign-in:

  • Payment and approval: One booking, one Airtable record and one paid test deposit confirmed by Stripe’s sandbox webhook. Staff approved the request through the actual Telegram button.
  • Scheduling and completion: The app, Airtable and Telegram matched the fictional October 26, 2026, 6:15–7:15 PM EDT window. Completing the visit in Airtable updated the app and sent the Telegram notification.
  • Notes and delivery: The exact Airtable note appeared in Service history. All six lifecycle and scheduling events reached Airtable and Telegram without retries. No deliveries were pending or dead-lettered at the final check.

The October 25 walkthrough passed 20 evidence checks. The October 26 verification record includes email sign-in, the repeated flow and the final reconciliation result below.

The system

System architecture

Swipe horizontally to explore the diagram.

Cabana system architectureMember portal and ops automation for Sailfish Pool Care: Next.js on Vercel, Supabase Postgres, three incoming webhook handlers and a private outbound Telegram function. n8n updates Airtable and calls the Telegram function; Stripe records test payments.MEMBERVERCELSUPABASEserver actioncreate sessionsigned webhookoutbox: trigger + sweepwhitelisted automationcommands / buttonsMemberphone / browserClaude Haikuintake triageNext.js App Routerportal UI +server actionsStripe Checkoutpayment sessionPostgresRLS · source of truthoutbox · auditairtable-writebacktelegram-webhooktelegram-notifystripe-webhookn8norchestrationretries · DLQAirtableowner consoleTelegramowner botResendops alert fallbackCOMPLEXITY ZONESpayment truth — verified webhook → DB, idempotentdelivery tracking — outbox → n8n, retries + DLQmember isolation — RLS on Postgres, per-account
Open full-size diagram ↗

Next.js provides the member portal. Supabase Postgres stores member and booking data, with row-level security restricting access to each member’s records.

Edge functions validate incoming Stripe, Telegram and Airtable events. n8n processes pending outbox events to update Airtable and calls a private Supabase function to send Telegram notifications. That function records Telegram’s message ID after a successful send. Member pages read the recorded booking and payment states.

Problem framing

Three risks shaped the implementation:

  1. Failed integrations: A booking change may succeed while an update to Airtable or a staff notification fails.
  2. Payment confirmation: Customers may close their browser after paying, and Stripe can resend the same event. Payment recording needs to handle both cases.
  3. AI mistakes: A misclassified request can reach the test deposit step. Staff approval and visit times remain separate decisions, and AI drafts stay off member status pages.

AI request triage

Claude Haiku classifies free-text requests and drafts acknowledgments for staff. The application validates the response structure, then applies a routing policy. Its confidence score comes from the model; it does not measure observed classification accuracy.

A repair classification with a score of at least 0.8 can open the fixed $75 Stripe test deposit step unless it is flagged as out of the service area. This happens before staff approval. The request action routes other classifications, low scores, timeouts and invalid responses to review. Staff still approve the work and assign its visit time.

The 20-case golden suite checks expected routes and forbidden phrases in draft acknowledgments, including prompt-injection cases. It is a regression sample, rather than a general accuracy or containment result.

Member sign-in

Registered members request an email link, open it, then select Sign in to confirm. The shared demo needs no email.

Tests with real emails confirmed that expired links are rejected and requesting a fresh link restores sign-in. Checks covered desktop and phone-sized browser windows, with expiry simulated on the test account.

Member status

The dashboard shows the regular service day from the member’s plan and labels Service history entries as Completed, Cancelled or Missed. The request page separates payment receipt, staff approval and the assigned visit window.

A request that reaches scheduling without an assigned visit window shows “Awaiting visit time,” or “Approved · time pending” after approval.

When a visit window is recorded before approval, the app shows “Awaiting approval” and the proposed time. After approval, it shows “Confirmed” with both dates, times and the Eastern time zone.

Deposit confirmation

Cabana uses Stripe Checkout in test mode for $75 repair deposits. Payment status is updated from Stripe webhook events after their signatures are verified.

The application records event IDs so it can recognize repeated deliveries and avoid applying the same payment update twice. After Checkout, the request page displays “Confirming your payment…” until the database records the payment.

Telegram staff approval

Staff can use Approve or Needs info on a Telegram notification. Approve records staff approval, updates the existing Airtable booking record and clears the message’s buttons. When a visit time is still unassigned, the member dashboard and request page show “Approved · time pending.” The payment receipt stays separate.

Needs info displays “Noted — reach out to the member for details.” It leaves the booking status unchanged; staff still need to contact the member.

Visit scheduling

The owner assigns a visit window from the authorized Telegram chat using /schedule with the booking ID, start and end timestamps. Each timestamp must include a UTC offset or Z; the command does not guess a time zone.

The scheduling function saves the window and its delivery event in one transaction. It keeps the existing approval and payment states and checks for overlapping visits when a technician is assigned. Repeating the same window creates no additional event.

n8n updates the existing Airtable row with both visit bounds and sends a Telegram notification. The member app, Airtable date fields and Telegram messages display the window in America/New_York, including the applicable EST or EDT offset.

Visit completion

Office staff check mark_completed on the Airtable booking. The writeback handler validates the allowed field and records the confirmed → completed transition with the actor office:airtable. The outbox then updates the existing Airtable row and sends the Telegram status notification.

On refresh, the request page shows “Completed” and “This request is marked completed.” The visit appears in the dashboard’s Service history. Unchecking the Airtable box does not reopen a completed visit.

Cancelled and missed visits

Owners cancel bookings with Telegram’s /cancel command. Office staff enter a reason and check mark_missed in Airtable. This requires a confirmed visit whose recorded window has ended and saves staff identity and reason in an audit record.

Separate synthetic tests on October 26, 2026 verified both actions across the app, existing Airtable records and Telegram. The app showed Cancelled or Missed in Service history (cancelled or no_show in the operational tools).

Future visits, unconfirmed visits and missing reasons were refused with clear feedback. Repeating the accepted missed action created no additional transition or notification.

Visit notes

Office staff enter the completion note in Airtable’s visit_notes field. The writeback handler saves the text to the booking and records applied: visit_notes in the sync log. After the member dashboard is refreshed, Service history displays the saved note in place of the request description, as defined in the member read layer. Editing the note leaves the booking status and payment records unchanged.

The transactional outbox

Booking status and visit-window changes are saved with their pending integration events in the same database transaction. This record of pending work is the transactional outbox.

n8n processes events to update Airtable and send notifications. Failed deliveries are retried, and deduplication keys identify repeated deliveries. Events that reach the retry limit move to a dead-letter queue for operator review.

A webhook trigger starts processing, while a 60-second sweep checks pending events. Nightly reconciliation compares booking IDs and 11 fields, including status, deposit state, visit times and notes, between the app and Airtable. It reports the result to Telegram without changing records.

After the full-flow test, the October 26, 2026, 7:35 PM EDT reconciliation matched 16 current bookings across all 11 fields with zero drift, and Telegram received the report. Airtable’s 14 historical QA records, whose source bookings are no longer in the app, retained their IDs, notes and staff evidence and were counted separately. A history label cannot exclude a current booking from comparison.

Failure-injection testing

A recorded test used 50 bookings while the n8n consumer was stopped, Airtable authentication was interrupted and Stripe events were replayed.

All 50 booking records appeared in Airtable with no duplicates. The log also recorded 43 dead-lettered outbox events and no duplicate booking-created events or status transitions.

The test exposed a retry bug that caused repeated alerts for dead-lettered events. The sweep was updated to skip those events until an operator explicitly requeues them.

These results come from one recorded run at demo volume.

Demo scope

The public demo uses fictional data, a shared member account and Stripe test mode. Requests and edits are saved to that account, so visitors should use sample details. Row-level security restricts the session to that member’s records.

The deposit step includes sandbox instructions; no real money is collected. Shared demo data resets every half hour, so walkthrough bookings are temporary.

Booking email notifications are not implemented. The Telegram bot rejects and logs unauthorized chats; rate limiting and alerts for those attempts remain follow-up work.