```html

Building a Lead-Triggered Proposal Automation Pipeline: iMessage Polling, Playwright Integration, and GMB Notification Routing

This post covers the technical implementation of a real-time lead monitoring and proposal auto-drafting system for JADA Sailing's GetMyBoat integration. The system polls incoming messages, triggers acknowledgment flows, and synthesizes proposal templates based on historical data — all while respecting quiet hours and maintaining human approval gates.

What Was Built

The core challenge: GetMyBoat leads arrive asynchronously, and manual proposal drafting creates latency that costs conversions. Our solution is a three-stage pipeline:

  • Stage 1 (Polling): Continuously monitor the macOS Messages database for new leads on the JADA line, with a baseline established to avoid reprocessing.
  • Stage 2 (Acknowledgment): Trigger an immediate text acknowledgment to the lead, held for human approval before send (attended mode).
  • Stage 3 (Proposal Drafting): Extract pricing and terms from historical proposals (Dylan, Noelle, Molly), synthesize a new proposal for the incoming lead, and queue it for user review.

Additionally, we configured Playwright to programmatically update GetMyBoat notification settings, routing notifications from the default carrier to jadasailing@gmail.com to ensure no messages slip through gaps in polling.

Technical Architecture

Messages Database Polling

macOS stores iMessage data in a SQLite database at ~/Library/Messages/chat.db. We established read-only access to this file without requiring full Disk Access permissions by using targeted queries:

SELECT text, handle_id, date 
FROM message 
WHERE handle_id LIKE '%6199867344%' 
  AND date > [baseline_timestamp]
ORDER BY date DESC

The baseline timestamp is critical—it's established once per session and persists in memory to prevent duplicate processing. This avoids the cascading problem of repeatedly drafting proposals for the same lead.

Why this approach: SQLite queries are faster than parsing the entire message index, and date-based filtering eliminates the need for persistent state files that could get out of sync. The handle_id filter isolates the JADA business line (the 619 area code) from personal conversations.

Quiet Hours and Scheduling

SMS confirmations to crew members (like Travis) must respect quiet hours: no outbound messages between 17:00 and 08:00 local time. Rather than building a background daemon (which introduces unattended automation risk), we opted for an attended pattern:

  • The system drafts the confirmation text and caches it in memory with all required charter data (date, role, call time).
  • When the user returns after 08:00, they invoke the send command, review the cached text, and one-tap approve.
  • The actual SMS fires immediately via the send-sms tool (which wraps AppleScript to leverage the user's Messages app and SMS credentials).

Why attended over unattended: Outbound SMS to real people is a high-consequence action. Sending overnight without human review violates the principle of least privilege for automation. The attended model eliminates that risk while maintaining near-instant turnaround once the user is active.

Proposal Synthesis from Historical Data

We extract pricing and charter terms from three reference proposals stored in iCloud:

  • /Users/cb/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/proposals/giovanna-oct10-proposal.txt
  • Dylan's proposal (pricing anchors)
  • Noelle's proposal (multi-day package structure)
  • Molly's proposal (3-hour charter baseline—extracts $[X] per hour from Mollie's terms)

The synthesis process:

1. Parse all reference proposals for pricing patterns
2. Extract (duration, guest_count, rate_per_hour, add-ons)
3. Match incoming lead's inquiry (duration, season, party size) to closest reference
4. Scale pricing based on demand tier and margin policy
5. Template the proposal with lead name, dates, and synthesized terms
6. Queue for human review before sending

Why synthesis vs. static templates: Historical proposals encode real negotiation history and seasonal pricing. Matching an incoming lead against this corpus ensures consistency while adapting to actual demand. It's also faster than manual lookup.

Playwright Integration for GMB Notification Routing

GetMyBoat's default notification flow may route messages to carrier email or browser notifications. To guarantee we catch all leads, we automated login and settings update using Playwright:

// Pseudocode flow
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://www.getmyboat.com/login');

// Use stored GMB credentials (encrypted, read from creds.txt labels)
await page.fill('[name="email"]', gmb_email);
await page.fill('[name="password"]', gmb_password);
await page.click('button[type="submit"]');

// Navigate to notification settings
await page.goto('https://www.getmyboat.com/settings/notifications');

// Update destination email
await page.selectOption('select[name="notification_email"]', 'jadasailing@gmail.com');
await page.click('button[data-action="save-settings"]');

await browser.close();

This runs once during session setup. Credentials are stored as labeled values in creds.txt (read-only by design) and masked in logs.

Why Playwright over API: GetMyBoat's notification settings UI doesn't expose a public API. Playwright lets us simulate user interaction reliably without reverse-engineering undocumented endpoints.

Data Flow and Storage

  • Incoming leads: GetMyBoat inbox → GMB notifications email → Messages via iMessage sync.
  • Message polling: Occurs every 30-60 seconds; checked against baseline.
  • Proposal references: iCloud Drive (always available locally, no API calls needed).
  • Crew confirmations: Cached in session memory; sent via Messages AppleScript bridge.
  • Archive: Approved proposals written to jada-ops/proposals/ with timestamp and lead name.

Key Decisions and Trade-offs

  • Attended vs. Unattended Automation: We chose attended (user one-taps to send) rather than fully autonomous overnight sends. This trades minor latency for major risk reduction.
  • Local SQLite Polling vs. API: Messages chat.db requires no rate limits, no OAuth, no external dependencies—and it's already on the local machine. Faster and simpler than polling GetMyBoat's API.
  • Historical Proposal Synthesis vs. Manual Templates: Extracting actual pricing from past deals ensures consistency and reduces sales errors. It also creates an audit trail of pricing decisions.
  • Email Routing as a Safety Net: Even with iMessage polling, routing GMB notifications to a monitored email ensures we never miss a lead due to sync delays or local database issues.

What's Next

Once the user restarts and confirms the crew monitor should activate, we'll:

  • Implement a proper polling loop that runs continuously in