Deploying Ship Captain Crew Dashboard with AWS Lambda, S3, and CloudFront: A Multi-Domain Executive Reporting Infrastructure
This session focused on building and deploying a comprehensive executive reporting system across the QueenofSanDiego portfolio. We deployed a Lambda-backed dashboard, generated five stakeholder-specific reports, and established the infrastructure to support ongoing multi-domain visibility. Here's the technical breakdown of what was built, why, and how it scales.
Architecture Overview: Why Lambda + S3 + CloudFront
The Ship Captain Crew tool needed to serve two distinct use cases: backend report generation via SES email delivery, and frontend dashboard visualization. We chose a serverless architecture because:
- Zero idle costs: Lambda only charges for execution time; our reports run on a schedule, not continuously
- API-agnostic: The backend can invoke external APIs (SES, Google Sheets, stripe via GAS) without maintaining open connections
- Static asset optimization: The dashboard frontend (HTML/CSS/JS) lives in S3 with CloudFront caching, reducing latency globally
- Audit trail: CloudWatch Logs capture every Lambda execution; SES delivery receipts provide email confirmation
File Structure and Deployment Targets
Our working repository is located at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/
This directory contains:
lambda_function.py— Main handler; orchestrates SES report delivery and dashboard data aggregationfrontend/index.html— Single-page dashboard; renders KPI tables, revenue charts, and task cardssend_exec_reports.py— Standalone Python script for local testing and manual report runs (developed in/Users/cb/Documents/repos/tools/)
The Lambda function was deployed to AWS with the following configuration:
- Runtime: Python 3.11
- Timeout: 60 seconds (sufficient for multi-domain SES operations)
- Environment variables:
repos.envsourced via Lambda layers or parameter store (never hardcoded) - Permissions: IAM role with
ses:SendEmail,logs:CreateLogGroup,logs:PutLogEvents, and read access to parameter store
Report Generation Logic: Five Specialized Perspectives
During development, we executed the report pipeline five times, sending stakeholder-specific executive summaries:
- CEO Report: Asset inventory across JADA, QueenofSanDiego, QuickDumpNow, and DangerousCentaur; shortfall analysis and 30-day prioritized action plan
- CTO Report: Stack-by-stack security audit, cost analysis (~$50–84/mo AWS), dev cycle gaps, and UX shortfalls across all tech properties
- Accounting Report: Revenue recognition gaps, chart of accounts, expense audit, and Q1 2027 profitability roadmap
- CMO Report: Channel visibility matrix, 3,676-person blast opportunity analysis, OTA deployment sequencing (Sailo → GetMyBoat → Viator/GYG)
- CFO Report: Burn rate model (~$7–9K/mo), capital deployment framework, 6-charter break-even threshold, and financial rules
The Lambda function constructs these reports dynamically by:
- Reading domain-specific project metadata from
/Users/cb/Documents/repos/agent_handoffs/projects/(e.g.,shipcaptaincrew.md) - Calling Google Apps Script endpoints (hosted on the QueenofSanDiego Workspace) to fetch live financials and task data
- Formatting each report as plain text or HTML email
- Sending via AWS SES from
admin@queenofsandiego.com(verified sender) to distribution lists
Frontend Deployment: S3 + CloudFront
The dashboard frontend was deployed to S3 and served through CloudFront:
- S3 bucket:
queenofsandiego-shipcaptaincrew-dashboard(us-west-2) - CloudFront distribution: Accelerates static asset delivery and invalidation cache on updates
- Index document:
frontend/index.html(uploaded to S3 root) - Cache behavior: 24-hour TTL for HTML; 365-day TTL for versioned assets (CSS/JS)
Deployment command executed during this session:
aws s3 sync ./frontend s3://queenofsandiego-shipcaptaincrew-dashboard \
--exclude ".git/*" \
--cache-control "public, max-age=86400"
aws cloudfront create-invalidation \
--distribution-id DISTRIBUTION_ID_HERE \
--paths "/*"
The CloudFront distribution ID is managed in AWS Secrets Manager and referenced in our deployment script (never hardcoded in the repository).
HTML Dashboard Features
The frontend implements a responsive, single-page dashboard with:
- KPI cards: Revenue YTD, active charters, burn rate, and pipeline value; each fetches data from the Lambda backend via POST
- Task board: Three columns (To Do, In Progress, Done); tasks sync with the Google Sheets dashboard via Lambda
- Domain tabs: Switchable views for JADA, QueenofSanDiego, QuickDumpNow, DangerousCentaur, and support properties (3028 51st St Rental, Expert Yacht Delivery)
- Chart rendering: Monthly revenue trend using Chart.js; data sourced from accounting backend
- No client-side secrets: All API calls go through the Lambda gateway; frontend never touches credentials
Lambda Handler Syntax and Validation
Before each deployment, we performed syntax validation and a dry-run test:
python -m py_compile lambda_function.py
python lambda_function.py
The handler structure in lambda_function.py follows the AWS Lambda event-driven pattern:
def lambda_handler(event, context):
"""
Orchestrates report delivery and dashboard data aggregation.
event: {
'action': 'send_reports' | 'fetch_dashboard_data',
'recipients': [...],
'domains': [...]
}
"""
try:
if event.get('action') == 'send_reports':
return send_executive_reports(event)
elif event.get('action') == 'fetch_dashboard_data':
return fetch_kpi_data(event)
except Exception as e:
log_error(e)
return {'statusCode': 500, 'body': str(e)}