```html

Building an Attended SMS Confirmation System for Charter Operations: Architecture and Quiet-Hours Enforcement

When managing a charter booking platform with real-time crew confirmations, the pressure to automate everything can lead to brittle, risky systems. This post documents why we chose a deliberately constrained approach: an attended confirmation workflow with quiet-hours enforcement, rather than fully unattended automated SMS dispatch.

The Problem: Crew Confirmations at Scale

GetMyBoat lead automation requires rapid acknowledgment of inbound charter inquiries and crew confirmation flows. A warm lead arrives; we need to immediately acknowledge receipt and draft a personalized proposal based on historical patterns from similar charters (Dylan, Noelle, Molly bookings). The temptation is obvious: fully automate SMS dispatch to crew and staff. The risk is equally obvious: an SMS sent at 3 AM to Travis by mistake, or a confirmation drafted with stale data, lives forever in someone's phone and cannot be recalled.

We chose attended confirmation instead: the system drafts messages and proposal content, the human operator glances and taps send. No unattended overnight dispatch. Same operational speed for the human (one tap); zero risk of irreversible outbound errors.

Architecture: Three-Layer Confirmation Flow

The system is built in three discrete layers:

  • Detection Layer: Monitor for inbound GetMyBoat notifications via Playwright browser automation, triggered on a polling schedule
  • Draft Layer: Synthesize SMS acknowledgments and proposals by querying historical charter data (DynamoDB crew rosters, past proposals, Stripe revenue records)
  • Human Gate Layer: Present drafted content to the operator for review and one-tap send via the send-sms tool

This architecture ensures no message leaves the system without human eyes. It also means the system can safely run unattended detection (Playwright polling), but requires attended action for dispatch.

Technical Implementation Details

Tool: send-sms Executable

The confirmed delivery mechanism is a native send-sms executable, verified to exist at session start. This tool wraps iMessage dispatch and is invoked from the agent context with a simple contract:

send-sms --to <phone> --message "<text>" --verify

The --verify flag ensures the operator sees the exact text before commit. No parameters are cached or auto-filled; every invocation is explicit and reviewable.

Quiet Hours Enforcement

A critical safety constraint: no SMS dispatch to crew between 17:00 and 08:00 local time. This is implemented as a conditional guard in the confirmation gate:


if current_hour < 8 or current_hour >= 17:
    status = "QUEUED_FOR_QUIET_HOURS"
    defer_until = next_day_08_00
    return confirmation_draft + defer_notice
else:
    return confirmation_ready_for_send

Messages drafted during quiet hours are cached in the operator's session context with an explicit defer notice. They are never auto-sent; they wait for morning operator approval.

Draft Generation: Proposal Synthesis from Historical Data

When a warm lead arrives, the system queries three sources to synthesize a contextually relevant proposal:

  • DynamoDB Charter Table: Roster, pricing, duration, vessel details for past charters (Dylan, Noelle, Molly bookings)
  • iCloud jada-ops/proposals/: Existing proposal PDFs and email templates used in prior deals
  • Stripe Integration: Revenue and payment terms from historical charters, stored via the shipcaptaincrew Lambda

The synthesis algorithm is simple: keyword-match the inbound lead against historical charter attributes (vessel class, party size, duration, seasonal demand), then construct a proposal shell by:

  1. Selecting the most similar historical proposal as a template
  2. Substituting lead-specific details (dates, party count, budget signals)
  3. Pulling Stripe payment terms from comparable charters
  4. Presenting the draft to the operator with placeholders flagged for review

The output is never sent; it is always human-reviewed before dispatch.

Infrastructure and Credential Management

GetMyBoat Browser Automation (Playwright)

GetMyBoat notifications are polled via Playwright, which requires:

  • Playwright installed and verified at session start
  • GMB login credentials stored in creds.txt with labeled sections (GMB username, GMB password)
  • No hardcoded credentials in any script or configuration file

The polling loop (deferred until restart) will be structured as:


# Pseudo-code for crew monitor polling
while True:
    try:
        page = await browser.goto("https://getmyboat.com/messages")
        new_messages = parse_unread_messages(page)
        for msg in new_messages:
            lead_data = extract_lead_metadata(msg)
            proposal_draft = synthesize_proposal(lead_data)
            cache_for_operator_review(proposal_draft)
            send_operator_notification("New lead queued for review")
        sleep(300)  # Poll every 5 minutes
    except Exception as e:
        log_error(e)
        sleep(600)  # Back off on error

Playwright credentials are injected from creds.txt at runtime; no credentials are logged or committed.

DynamoDB and Lambda Integration

Charter metadata and crew roster are queried from DynamoDB tables (hosted on AWS account tied to the EC2 production environment). The shipcaptaincrew Lambda function handles Stripe integration and exposes a simple REST endpoint for historical pricing lookups.

All queries use IAM role-based authentication; no hardcoded keys are present in the monitoring scripts.

Key Decisions and Trade-offs

Decision 1: Attended vs. Unattended Dispatch
We chose attended confirmation despite the operational overhead. SMS dispatch is effectively irreversible once sent to a real phone. A single wrong message to Travis (or any crew member) at 3 AM damages trust and cannot be recalled. The one-tap send is still fast for the operator; the risk reduction justifies the tiny latency.

Decision 2: Session-Based Agent vs. Scheduled Daemon
The agent is session-bound. It does not wake itself at 08:00 to send queued messages. The operator must explicitly resume the session and approve dispatch. This is deliberate: unattended wake-and-send is exactly the kind of edge case that causes bugs. Morning interaction is a feature, not a limitation.

Decision 3: Proposal Synthesis Over Manual Templates
Rather than maintain static proposal templates, we synthesize dynamically from historical charters. This reduces template debt, ensures consistency with past pricing, and makes it easier to A/B test proposal content. The operator always sees the draft before it goes out, so inconsistencies are caught.

What's Next

Phase 1 (ready now) is the attended confirmation framework for Travis and inbound lead acknowledgment. Phase 2 (pending operator readiness) is the full Playwright polling loop to automatically detect GMB messages and queue drafts. Phase 3