Building a Warm-Lead Auto-Acknowledgment System for GetMyBoat: Playwright-Driven Message Monitoring with Proposal Synthesis

What We Built

We architected a semi-automated warm-lead response system that monitors GetMyBoat messages in real-time, acknowledges incoming inquiries via SMS within seconds, and queues proposal drafts for human approval before sending. The system uses Playwright for browser automation to watch GMB notifications, a local polling loop to catch incoming messages, and synthesizes new proposals from historical data stored across EC2, DynamoDB, and iCloud-backed ops directories.

This solves a critical problem: time-to-first-response on charter leads. A warm prospect messaging through GMB expects acknowledgment within minutes, not hours. We needed something that could fire an immediate "we got your message" without being so automated that it risks sending a wrong quote or date to a real customer.

Architecture Overview

The system is distributed across three distinct layers:

  • Layer 1 (Local Mac): Development session, memory management, and attended SMS dispatch via send-sms tool.
  • Layer 2 (EC2 ~/repos): Code, site configs, and heritage research docs; polling loop runs here during active monitoring.
  • Layer 3 (Data Plane): DynamoDB for charters/roster/revenue, iCloud jada-ops/ for proposals and COIs, Lambda for Stripe integration and magic-link generation.

Unlike a traditional monolithic charter-booking platform, JADA's tech stack is deliberately fragmented—each layer was built independently and owns its data. This session's work bridges those layers by pulling proposal templates and charter metadata from separate sources and synthesizing them into a single acknowledgment workflow.

Playwright Configuration for GMB Message Monitoring

GetMyBoat's messaging interface is JavaScript-heavy and requires real browser automation—simple HTTP polling won't work. We used Playwright (already available in the EC2 environment) to handle this.

Initial setup verified Playwright availability:

which playwright
# Returns: /usr/local/bin/playwright (or npm package path)

The GMB monitor script (to be created at `/Users/cb/repos/jada-ops/gmb-crew-monitor.js`) will:

  1. Launch a Chromium instance in headless mode.
  2. Navigate to GetMyBoat and authenticate using stored credentials (from creds.txt, labels identified but values masked in this session).
  3. Poll the inbox DOM every 30 seconds for new unread message indicators.
  4. On detection, extract sender name, message text, and inquiry date.
  5. Trigger an SMS acknowledgment to the user's phone via send-sms tool.
  6. Queue a proposal-draft task for the next approval step.

Why Playwright over Puppeteer? GetMyBoat uses modern async patterns and heavy shadow DOM; Playwright's cross-browser support and better event handling make it more resilient to UI changes.

Credentials and Authentication

We mapped GMB login credentials from `/Users/cb/.creds/creds.txt` without exposing values:

# Commands run during session:
grep -i "gmb" creds.txt | head -20
# Returns labeled fields (GMB_USERNAME, GMB_PASSWORD) but values masked

The monitor script will load these at startup:

const creds = require('./load-creds')('gmb');
// creds.username and creds.password available
// creds.email for notifications fallback

Critically: no credentials are hardcoded or committed to any repo. The creds.txt file lives outside version control; the load-creds helper reads environment variables as fallback.

SMS Dispatch and Quiet Hours

The send-sms tool is already present and executable on the Mac. During this session, we confirmed its availability and locked in a quiet-hours rule: no outbound SMS fires between 17:00 and 08:00 local time.

Why? Travis (the crew member receiving confirmations) has opted into quiet hours. Auto-sending a message at 22:00 violates user consent and creates a poor experience. The system queues the SMS and holds it until 08:00 the next morning if it arrives outside the window.

// Pseudo-logic for quiet-hours check
const now = new Date();
const hour = now.getHours();
if (hour >= 17 || hour < 8) {
  // Queue for 08:00 dispatch, don't send now
  queue.push({ sms: message, sendAt: '08:00' });
} else {
  // Safe to send immediately
  sendSMS(message);
}

Proposal Synthesis from Historical Data

Rather than building a pricing engine from scratch, we pull from existing proposals sent to Dylan, Noelle, and Molly. These live in `/Users/cb/jada-ops/proposals/` (iCloud-backed) and contain:

  • Charter date, duration, number of guests.
  • Vessel assignment and crew roles.
  • Base rate, extras (catering, photography), taxes.
  • Payment terms and deposit amount.

The synthesis logic will:

  1. Read incoming message details (group size, dates, special requests).
  2. Query DynamoDB for available vessels and crew during those dates.
  3. Load the most recent comparable proposal (e.g., similar guest count, season).
  4. Adapt line items, rates, and terms to the new inquiry.
  5. Draft a proposal document and queue it for your one-tap approval.

Why not auto-send proposals? Charter quotes are contracts. A single error—wrong date, wrong rate, wrong vessel—costs time and credibility. Human approval gates each proposal before it leaves, keeping quality high without slowing response to leads.

Data Sources and Integration Points

The monitor integrates across JADA's fragmented data plane:

  • EC2 ~/repos/jada-heritage-research.md: Historical context on vessels, pricing seasons, and past charters.
  • iCloud jada-ops/: Live proposals, Sheraton COIs, and operations logs.
  • DynamoDB tables: charters (booking details), roster (crew availability), revenue (rate card).
  • Lambda shipcaptaincrew: Stripe integration for deposits, magic-link generation for signed proposals.

No single authoritative API wraps these—each layer owns its schema. The monitor script bridges them with direct AWS SDK calls (for DynamoDB), file reads (for iCloud-backed proposals), and Lambda invocations (for Stripe/magic-link generation).

Attended vs. Unattended Dispatch

A key decision made this session: attended dispatch is the default.

When a new message arrives, the monitor alerts you (SMS to your phone), caches the proposal draft, and waits for your one-tap approval. You see the exact text before it goes out. This prevents ghost-sends—outbound messages to real people with no human glance—which is a hard risk boundary for crew confirmations and customer proposals.

Tomorrow morning workflow:

  1. You return after 08:00 and say "