```html

Building an Attended SMS Confirmation Flow with Quiet Hours: Architecture and Safety Decisions

This post documents the technical implementation of a controlled SMS confirmation system for crew scheduling, with particular emphasis on respecting quiet hours and maintaining human oversight on all outbound communications. The work addresses a specific operational need: confirming crew availability (in this case, Travis for a charter) without introducing risk through unattended automated sends.

What Was Done

We established an attended SMS confirmation workflow that:

  • Leverages the existing send-sms tool (executable in the development environment)
  • Enforces quiet hours (17:00–08:00 local time) to prevent SMS delivery outside acceptable windows
  • Requires explicit human approval before any SMS leaves the system
  • Caches charter/confirmation context to eliminate re-fetching latency during send time
  • Maintains a clear audit trail of what was confirmed and when

Technical Architecture

Data Layer

Confirmation data flows from three primary sources:

  • DynamoDB (charters table): Stores charter metadata including crew assignments, dates, call times, and roles
  • iCloud jada-ops/: Proposals directory with historical confirmation templates (Dylan, Noelle, Molly examples)
  • EC2 ~/repos/: jada-heritage-research.md and voice patterns for consistent messaging tone

The attended flow does not automatically query these on send—instead, we cache the relevant charter context in memory at the beginning of the session. This prevents drift between what was confirmed in conversation and what the SMS actually states.

SMS Execution Layer

The send-sms tool is a shell executable that wraps outbound SMS logic. Command structure (no credentials shown):

send-sms --recipient <phone_number> --message "<message_text>" --sender jada

The tool checks recipient against a blocklist before execution (prevents accidental sends to internal numbers or test accounts). Return codes:

  • 0: Send successful, SMS queued
  • 1: Recipient blocked or invalid format
  • 2: Rate limit exceeded (prevents SMS flooding)
  • 3: Configuration missing (credentials file not found)

Quiet Hours Enforcement

Before any SMS is drafted, the system checks current local time against the quiet window (17:00–08:00). This is a hard block—no override flags, no exceptions except by explicit user override statement in the current session. Implementation pseudocode:

function can_send_sms(current_time, quiet_start=17:00, quiet_end=08:00):
  if quiet_start <= current_time < quiet_end:
    return False  // In quiet hours
  return True     // Safe to proceed

The check happens before drafting the message, not after. This prevents the operator from accidentally tapping send and then realizing it was 23:45.

Confirmation Workflow: Attended vs. Unattended

This is where the operational safety decision matters most. We evaluated two architectures:

Attended (Implemented)

Flow:

  1. Operator (you) returns to session after 08:00
  2. Agent retrieves cached charter context for Travis (date, role, call time, rate confirmation if applicable)
  3. Agent drafts exact SMS text for review
  4. Operator reads the draft and either approves or edits
  5. Operator executes send with one-tap confirmation

Why this approach: An SMS to a real person is a hard-to-reverse outbound action. If the message is wrong—wrong date, wrong time, wrong rate—the damage is immediate and requires phone calls to fix. The latency between drafting and sending is negligible for a confirmation that can reasonably wait until morning. The human glance-over costs nearly zero (5–10 seconds) and catches errors before they propagate.

Unattended (Rejected)

Alternative that was explicitly not implemented:

  • Set up a scheduled Lambda or cron job to fire the SMS at 08:00 tomorrow automatically
  • Cache the message text and crew phone number in a persistent queue
  • Execute the send without further human interaction

Why rejected: This removes the human checkpoint entirely. If the cached data is stale, if the phone number is wrong, if the confirmation context changed overnight—the message still goes out. Recovery requires texting Travis to correct and apologize. For a system where confirmations are safety-critical (crew scheduling), the marginal speed gain (operator delay of ~30 seconds to come back and tap approve) does not justify the risk.

Data Caching Strategy

To make the morning send instant, we cache confirmation context in the session memory under a structured key:

confirmations.travis = {
  charter_id: "CHR-2024-001",
  date: "2024-02-15",
  call_time: "06:00",
  role: "deckhand",
  phone: "+1-XXX-XXX-XXXX",
  proposal_id: "PROP-20240214-001",
  tone: "warm",  // from jada-heritage-research.md
  context: "follow-up from Dylan/Noelle/Molly proposals"
}

When you say "send Travis's confirmation" tomorrow, the system:

  1. Checks quiet hours (should be safe at 08:00+)
  2. Retrieves the cached object (no DynamoDB call)
  3. Generates the exact SMS from template + cached values
  4. Presents text for approval

This eliminates the re-fetch latency and ensures the confirmation matches what you actually discussed today.

Integration with Existing Tooling

This system does not require new AWS resources, new Lambda functions, or new DynamoDB tables. It uses:

  • send-sms (existing executable, already verified in PATH)
  • Session memory (in-agent caching, no persistence layer needed)
  • Local time checks (system clock, no NTP calls)
  • Proposal templates from iCloud (read-only, no writes)

Infrastructure footprint: zero. Risk surface: minimal (one SMS tool, human-gated execution).

What's Next

The immediate next steps are:

  • Tomorrow morning (08:00+): You come back and say "send Travis's confirmation." Agent drafts the message, you review, you tap send.
  • After Travis confirms: Begin the getmyboat.com message monitor (Playwright + GMB login automation) to catch warm leads and draft proposals based on Dylan/Noelle/Molly templates.
  • Longer term: Once the GMB monitor is stable, we can revisit attended vs. unattended for that workflow and determine if lead acknowledgments should auto-fire (lower risk: acknowledgment-only, no binding commitment)