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-smstool (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 queued1: Recipient blocked or invalid format2: 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:
- Operator (you) returns to session after 08:00
- Agent retrieves cached charter context for Travis (date, role, call time, rate confirmation if applicable)
- Agent drafts exact SMS text for review
- Operator reads the draft and either approves or edits
- 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:
- Checks quiet hours (should be safe at 08:00+)
- Retrieves the cached object (no DynamoDB call)
- Generates the exact SMS from template + cached values
- 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)