```html

Automating GetMyBoat Inquiry Handling in JADA's AWS + Google Apps Script Stack

Sail JADA's booking pipeline has matured significantly since we first deployed platform_inbox_scraper.py and CalendarSync.gs. Confirmed bookings now flow automatically from GetMyBoat → Gmail → Google Calendar → crew dispatch. However, one critical gap remains: inquiry triage and provisional booking holds are still manual. This post documents the technical strategy to close that gap by extending the existing scraper infrastructure to handle the inquiry→response→hold→confirmation lifecycle.

The Problem: Manual Inquiry Handling

GetMyBoat inquiry emails land in the JADA Gmail inbox with predictable subject and body patterns. Currently, a human operator (previously Carole) must:

  • Read the inquiry
  • Check availability manually in Google Calendar
  • Draft a response with pricing and terms
  • Send the response via GetMyBoat's reply interface
  • Create a tentative calendar hold to block the slot during negotiation
  • Track the conversation thread until payment confirms

This sequential process is slow, error-prone, and creates operational bottlenecks when the primary operator is unavailable. The existing automation stack is built and ready to extend—we just need to wire up the inquiry branch.

Technical Architecture: Extending platform_inbox_scraper.py

The current platform_inbox_scraper.py uses Gmail API with label-based filtering to identify confirmed booking notifications from GetMyBoat and Boatsetter. It parses vessel type, dates, party size, and customer contact, then dispatches crew SMS via Twilio.

To handle inquiries, we add a parallel detection pipeline:


# Pseudocode structure (platform_inbox_scraper.py extension)

def classify_getmyboat_email(email_body, subject):
    """
    Returns: 'inquiry', 'confirmation', 'payment', or 'other'
    """
    if 'has sent you an inquiry' in email_body:
        return 'inquiry'
    elif 'Your booking is confirmed' in email_body:
        return 'confirmation'
    elif 'payment received' in email_body or 'payment processed' in email_body:
        return 'payment'
    return 'other'

def extract_inquiry_data(email_body):
    """
    Regex-extract customer name, email, dates, vessel, party size, message
    """
    customer_name = re.search(r'Inquiry from: (.+?)\n', email_body).group(1)
    customer_email = re.search(r'Contact: (.+?@.+?)\n', email_body).group(1)
    requested_dates = parse_date_range(email_body)
    vessel_type = extract_vessel_preference(email_body)
    party_size = extract_party_size(email_body)
    customer_message = extract_message_text(email_body)
    
    return {
        'customer_name': customer_name,
        'customer_email': customer_email,
        'dates': requested_dates,
        'vessel': vessel_type,
        'party_size': party_size,
        'message': customer_message,
        'inquiry_id': generate_uuid()
    }

def check_availability(start_date, end_date, vessel_type):
    """
    Query CalendarSync's source-of-truth Calendar
    Return: available_vessels (list) and conflict dates (list)
    """
    calendar = service.events().list(
        calendarId='primary',
        timeMin=start_date.isoformat(),
        timeMax=end_date.isoformat()
    ).execute()
    
    # Filter for the requested vessel type
    conflicts = [e for e in calendar['items'] 
                 if vessel_type in e.get('summary', '')]
    
    return len(conflicts) == 0, conflicts

def generate_auto_response(inquiry_data, availability_status):
    """
    Draft professional response with pricing tier
    """
    if availability_status:
        response_text = f"""
Dear {inquiry_data['customer_name']},

Thank you for your interest in chartering {inquiry_data['vessel']} 
from {inquiry_data['dates']['start']} to {inquiry_data['dates']['end']}.

Good news—those dates are available! Here's our pricing:
- {inquiry_data['vessel']}: ${PRICING_TIERS[inquiry_data['vessel']]} per day
- Party size: {inquiry_data['party_size']} guests (confirmed)

To secure your booking, we require a 25% deposit via GetMyBoat's 
secure payment portal. Full terms are attached.

Please confirm within 24 hours to hold your slot.

Best,
Sail JADA
        """
    else:
        response_text = f"""
Dear {inquiry_data['customer_name']},

Thank you for your inquiry. Unfortunately, {inquiry_data['vessel']} 
is unavailable for your requested dates ({inquiry_data['dates']['start']} 
to {inquiry_data['dates']['end']}).

We have availability on the following alternative dates:
{generate_alternative_dates(inquiry_data['dates'])}

Please let us know if any work for you.

Best,
Sail JADA
        """
    
    return response_text

def create_provisional_calendar_hold(inquiry_data, hold_duration_hours=24):
    """
    Create tentative event in Google Calendar to block slot during negotiation
    Uses custom properties to track inquiry ID and pending-payment status
    """
    event = {
        'summary': f"[INQUIRY] {inquiry_data['vessel']} - {inquiry_data['customer_name']}",
        'description': f"Inquiry ID: {inquiry_data['inquiry_id']}\nMessage: {inquiry_data['message']}",
        'start': {'dateTime': inquiry_data['dates']['start']},
        'end': {'dateTime': inquiry_data['dates']['end']},
        'transparency': 'transparent',  # Don't count toward occupancy yet
        'extendedProperties': {
            'private': {
                'inquiry_id': inquiry_data['inquiry_id'],
                'status': 'pending_response',
                'customer_email': inquiry_data['customer_email'],
                'created_at': datetime.now().isoformat()
            }
        },
        'reminders': {
            'useDefault': False,
            'overrides': [
                {'method': 'email', 'minutes': 24 * 60}  # 24-hour follow-up
            ]
        }
    }
    
    calendar_event = service.events().insert(
        calendarId='primary',
        body=event
    ).execute()
    
    return calendar_event['id']

Infrastructure: Gmail API and Calendar Sync Coordination

The enhanced scraper runs on the same AWS Lambda schedule as today (10-minute polling cycle). It authenticates to Gmail API using the existing service account credentials stored in AWS Secrets Manager under the key /jada/gmail-service-account-key.

Key modifications:

  • New Gmail filter label: Create a label GetMyBoat/Inquiry in Gmail; configure GetMyBoat to tag inquiries with this label. This prevents inquiry emails from being processed as confirmed bookings.
  • Calendar extended properties: CalendarSync.gs already supports reading custom event properties. We add a check: if an event has extendedProperties.private.inquiry_id, CalendarSync treats it as a provisional hold (non-binding) rather than a confirmed slot.
  • SQS queue for inquiry processing: Instead of blocking Lambda execution on API calls