Building a Warm-Lead Auto-Acknowledgment System for GetMyBoat: Architecture, Messaging Patterns, and Notification Routing

This post documents the technical architecture and implementation decisions for a warm-lead message monitor that automatically acknowledges incoming inquiries on GetMyBoat and drafts templated proposals for human approval. The system prioritizes speed, auditability, and controlled outbound messaging—critical constraints when automating customer communication.

What Was Built

A multi-layer monitoring and response system that:

  • Polls the GetMyBoat Messages API (via Playwright browser automation) for new warm leads
  • Sends immediate, lightweight acknowledgment messages to customers
  • Generates proposal drafts based on historical pricing and charter templates
  • Routes all outbound SMS and email to a human approval gate before send
  • Logs all state changes to DynamoDB for auditability and drift detection

The system operates in two modes: attended (human approves each message before send) and unattended polling (scheduled agent with approval gates). We deliberately chose attended mode for initial deployment to eliminate risk of incorrect customer messages sent overnight.

Technical Architecture

Message Polling Layer

The core polling mechanism uses Playwright to authenticate to GetMyBoat and extract new messages from the inbox. This runs on the development Mac initially; production deployment targets a Lambda function with Playwright layer.


# Playwright session initialization
# Authenticates using creds.txt (GMB credentials stored locally, masked in logs)
# Navigates to GetMyBoat inbox
# Extracts sender ID, message text, timestamp, charter context

playwright_session.goto("https://www.getmyboat.com/messages")
messages = extract_new_messages(since=baseline_timestamp)
# Filter: warm leads only (price inquiries, availability checks)

Why Playwright instead of direct API? GetMyBoat's message API has rate limits and requires repeated OAuth flows. Browser automation allows us to mirror the exact UX a human operator sees, making debugging trivial—Sergio can open the same browser, check what we saw, and spot discrepancies immediately.

State Management & Baseline Tracking

A baseline timestamp is maintained in local state and in DynamoDB (table: jada-gmb-monitor-state) to ensure we never miss a message and never re-process one. This is critical; double-acknowledging a customer is worse than being slow.


# Check baseline before each poll cycle
baseline = read_from_dynamodb("jada-gmb-monitor-state", "latest_processed_ts")
# Poll messages received after baseline
new_messages = playwright_session.messages_after(baseline)
# Process each, then atomically update baseline
update_dynamodb_baseline(max_timestamp_from_batch)

Each message is stored in DynamoDB with status flags: received → ack_drafted → ack_approved → ack_sent → proposal_drafted → proposal_approved → proposal_sent.

Acknowledgment Generation

The acknowledgment layer generates a lightweight, context-aware reply. For example, if the incoming message asks about a specific date or boat, the ack references it:


# Template-based generation
ack_template = "Thanks for reaching out! We received your message about {{charter_type}} on {{date}}. We'll get back to you with details within 2 hours."
ack_text = ack_template.format(
    charter_type=inferred_from_message_context,
    date=extracted_date_or_"availability"
)
# Draft stored in DynamoDB with status "ack_drafted"
# Awaits human approval before send

Proposal Drafting Engine

Historical proposals (Dylan, Noelle, Molly, Mollie) are parsed from ~/jada-ops/proposals/ and stored in DynamoDB table jada-proposal-templates. Each proposal contains:

  • Charter type and boat assignment
  • Duration and pricing (extracted via regex from proposal PDFs and .txt files)
  • Inclusions, exclusivity terms, cancellation policy
  • Captain/crew assignments

When a new warm lead arrives, the engine matches it against templates by charter type, group size, and date range, then generates a first draft with pricing adjusted for market conditions:


# Match incoming request to closest historical proposal
templates = query_dynamodb("jada-proposal-templates", 
    charter_type=lead.charter_type,
    party_size_range=[lead.party_size - 2, lead.party_size + 2])
# Use most recent pricing as baseline
base_price = templates[0].pricing
# Adjust if custom requirements detected (multi-day, special requests)
adjusted_price = calculate_adjusted_price(base_price, lead.requirements)
# Generate draft proposal document
draft = render_proposal_template(
    client_name=lead.sender_name,
    charter_details=lead.details,
    pricing=adjusted_price,
    availability=lead.requested_dates
)
# Store with status "proposal_drafted" in DynamoDB

Approval Gate & Outbound Messaging

All outbound messages (SMS and email) require human approval before send. This is non-negotiable for customer-facing communication.

SMS Path (via send-sms tool):


# Acknowledgment is drafted and stored in state
# CLI presents: "Ready to send ack to [phone]. Approve? (y/n)"
# On approval, send-sms executable (located at /usr/local/bin/send-sms) fires
/usr/local/bin/send-sms --to=+1-XXX-XXX-XXXX --message="Your drafted ack text"
# Result logged to DynamoDB with timestamp and status "ack_sent"

Email Path (GetMyBoat reply):

Proposal drafts are written to ~/jada-ops/drafts/[lead-id]-proposal.txt and presented to you for review before Playwright clicks the "Send" button in the GMB UI.

Quiet Hours & Rate Limiting

SMS messages respect quiet hours: nothing leaves before 08:00 or after 17:00. This prevents crew fatigue and regulatory issues. The polling loop checks the system clock before dispatching:


def should_send_sms(timestamp):
    hour = timestamp.hour
    return 8 <= hour < 17  # Only send 08:00–17:00

If a message is drafted outside quiet hours, it queues in DynamoDB with status ack_drafted_waiting_quiet_hours and is automatically re-presented at 08:00.

Infrastructure & Deployment Strategy

Development (Current)

All processes run on your Mac. Session files and state live in:

  • ~/.claude/projects/-Users-cb/memory/jada-getmyboat-lead-automation.md — session memory and audit log
  • ~/jada-ops/proposals/ — historical proposals (Giovanna, Dylan, Mollie, etc.)
  • ~/jada-ops/draf