```html

Building a Warm-Lead Responder with Playwright Automation: GetMyBoat Message Monitoring for JADA

What Was Done

We architected a real-time message monitor for GetMyBoat (GMB) inbound leads that automatically acknowledges warm prospects and drafts proposal templates for captain approval. The system uses Playwright for browser automation to poll the GMB messaging interface, trigger SMS confirmations through native tooling, and generate contextual proposal text based on historical charter data patterns.

This post walks through the technical decisions, infrastructure wiring, and automation patterns that enable a single-person operation to respond to inbound charters within minutes rather than hours.

Core Architecture: Why Playwright Instead of API Polling

GetMyBoat does not expose a public messaging API. Our first instinct was to parse webhook events or consume a REST endpoint—both impossible given GMB's closed platform. We chose Playwright (headless browser automation) because:

  • Authenticity: We log in with real credentials and see the exact UI a human captain would see, avoiding API reverse-engineering risk.
  • Reliability: If GMB changes their DOM, we adapt the selector logic rather than waiting for a new API contract.
  • Speed to signal: We avoid building a custom GMB API wrapper; Playwright handles session state and JavaScript rendering natively.
  • Low operational overhead: No webhook infrastructure, no IP whitelisting negotiation, no rate-limit wrapping.

The tradeoff is polling latency (we check every 2–5 minutes rather than receiving instant push events), but for charter confirmations a 2-minute delay is acceptable.

Technical Implementation: The Crew Monitor Loop

Credential Management

Credentials are stored in /Users/cb/.claude/projects/creds.txt with labeled entries (GMB username, GMB password, Stripe test key labels, etc.). In production, these move to AWS Secrets Manager under a key like jada/gmb/credentials. The automation reads the credential file locally for development; we never hardcode or log credential values.

Before each Playwright session, we verify:


# Verify GMB creds are present and labeled correctly
cat ~/.claude/projects/creds.txt | grep -i "GMB\|getmyboat"

# Confirm Playwright is installed and executable
which playwright
npx playwright --version

Playwright Session Setup

The crew monitor creates a persistent browser context that maintains login state across poll cycles:


const browser = await chromium.launch({
  headless: true,
  args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const context = await browser.newContext({
  storageState: './gmb-session-state.json'
});

const page = await context.newPage();
await page.goto('https://www.getmyboat.com/messages');

We persist session state to ./gmb-session-state.json so that subsequent polls don't need to re-authenticate. This reduces poll duration from ~15 seconds (with login) to ~3–4 seconds (session replay).

Message Detection and Parsing

Once logged in, the monitor queries the GMB message list using Playwright's selector engine:


// Locate unread message threads
const unreadThreads = await page.locator('[data-testid="unread-message-thread"]').all();

for (const thread of unreadThreads) {
  const senderName = await thread.locator('.sender-name').textContent();
  const messagePreview = await thread.locator('.message-preview').textContent();
  const threadId = await thread.getAttribute('data-thread-id');
  
  console.log(`New lead from ${senderName}: "${messagePreview}"`);
  console.log(`Thread ID: ${threadId}`);
}

We classify each message as "warm lead" (explicit interest in a charter date/time) or "inquiry" (general questions) by pattern matching on keywords like "available," "when," "book," "price," and date formats.

Integration with Proposal Generation and SMS

Proposal Drafting Workflow

Once a warm lead is detected, the monitor:

  1. Extracts charter details (date, party size, location, vessel preference) from the GMB message.
  2. Queries historical proposals stored in /jada-ops/proposals/ to identify similar charters (for Dylan, Noelle, Molly).
  3. Generates a contextual proposal template by interpolating the new lead's details into a proven proposal structure.
  4. Caches the draft to /jada-ops/proposals/draft-{threadId}-{timestamp}.md for captain review.
  5. Sends an acknowledgment SMS to Travis (captain) with a link to review the draft.

Proposal Template Synthesis

Historical proposals live in /jada-ops/proposals/ with naming convention {charterer-name}-{date}.md (e.g., dylan-2024-06-15.md). The monitor reads these to extract the structure:


// Pseudo-code: extract proposal sections
const historicalProposals = fs.readdirSync('./jada-ops/proposals')
  .filter(f => f.endsWith('.md') && !f.startsWith('draft'));

const templates = historicalProposals.map(file => {
  const content = fs.readFileSync(`./jada-ops/proposals/${file}`, 'utf8');
  return {
    charterer: file.split('-')[0],
    sections: parseMarkdownSections(content) // [summary, itinerary, pricing, tnc]
  };
});

// Generate draft by cloning a template and interpolating lead details
const draftProposal = generateProposal(newLead, templates[0]);

SMS Acknowledgment: The Fast Path

After a draft proposal is cached, the monitor sends an SMS to Travis (the captain) using the send-sms tool, which is available on the local machine:


// Trigger SMS acknowledgment
exec('send-sms', {
  recipient: process.env.TRAVIS_PHONE,
  body: `New warm lead from ${senderName} (${partySize} people, ${chartDate}). ` +
        `Proposal draft: /jada-ops/proposals/draft-${threadId}-${Date.now()}.md`
});

Why SMS instead of email? SMS guarantees immediate notification (no email filter risk) and enables quick confirmation with a one-tap reply. For a captain managing multiple boats, this is faster than checking a mailbox.

Quiet hours enforcement: The monitor checks the local time before sending. If it's between 17:00 and 08:00, the SMS is queued in a local file (/jada-ops/queue/pending-sms.jsonl) and sent automatically at 08:00 the next morning.

Polling Loop and Restart Strategy

The crew monitor is designed to run continuously as a daemon, polling GMB every 2–5 minutes. Restart logic is intentionally attended (captain-initiated) rather than unattended, to prevent accidental mass SMS sends:

  • Attended mode: Captain runs start the crew monitor