```html

Building a Dynamic Artist Celebration System: Real-Time Content Updates Across Multi-Tenant Event Subdomains

Overview: The Nike Philosophy Applied to Live Events

Queen of San Diego's Rady Shell at Jacobs Park hosts world-class artists throughout the season. Rather than passively listing performers, we implemented a dynamic artist celebration system inspired by Nike's athlete-centric marketing philosophy: celebrate the artist, and the experience sells itself.

This required building a real-time content injection pipeline that updates artist spotlight sections on every event page three times daily until the concert date. The architecture spans Google Apps Script (GAS), Python-based HTML rendering, S3 distribution, and CloudFront cache invalidation—all coordinated to deliver fresh artist content without manual intervention.

Architecture Overview

The system consists of three interconnected layers:

  • Content Generation Layer: Google Apps Script service querying artist data and generating spotlight content via Claude API
  • Static Site Layer: Python renderer injecting dynamic sections into pre-built event HTML templates and syncing to S3
  • Delivery Layer: S3 buckets per event subdomain with CloudFront distributions for global CDN delivery

Technical Implementation

Google Apps Script: ArtistCelebrationsService.gs

Created a new GAS module at /Users/cb/Documents/repos/sites/queenofsandiego.com/rady-shell-events/apps-script-replacement/ArtistCelebrationsService.gs that serves as a dedicated microservice for artist content generation.

Key responsibilities:

  • Fetches artist metadata from the events data sheet (artist name, biography, performance date)
  • Calls Claude API to generate a fresh, contextual celebration paragraph highlighting the artist's significance
  • Returns JSON-formatted spotlight content for consumption by the render pipeline
  • Implements caching logic to avoid redundant API calls within a 4-hour window per artist

The service exposes a single endpoint via GAS deployment:

GET /exec?action=getArtistCelebration&artistId=STRING
Response: {
  "artistId": "STRING",
  "name": "STRING", 
  "celebration": "STRING (1-2 paragraphs of marketing copy)",
  "generatedAt": "ISO 8601 timestamp",
  "performanceDate": "DATE"
}

Modified Code.gs to add routing logic that maps incoming requests to the ArtistCelebrationsService, following the existing pattern of custom endpoints for the event site infrastructure.

Static Site Rendering: Python Pipeline

Updated render_event_sites.py to include artist spotlight injection into the HTML generation phase. This runs during site builds and before S3 sync operations.

Created inject_artist_spotlight.py as a standalone utility that:

  • Parses the events.json manifest to identify all upcoming events and their associated artists
  • Calls the GAS ArtistCelebrationsService endpoint for each artist
  • Injects the returned celebration content into a designated <section class="artist-celebration"> block in each event's HTML template
  • Handles graceful degradation if the GAS endpoint is unavailable (falls back to cached content or placeholder)

Integration point in render_event_sites.py:

# After building base HTML from template
event_html = render_event_template(event_data)

# Before uploading to S3
event_html = inject_artist_celebration(
    event_html=event_html,
    artist_id=event_data['artist_id'],
    gas_endpoint=os.environ['GAS_ENDPOINT_URL']
)

upload_to_s3(bucket=event_bucket, key='index.html', content=event_html)

Infrastructure: S3 and CloudFront Distribution

S3 Bucket Structure:

Each event subdomain (e.g., event-2024-spring.queenofsandiego.com) has a corresponding S3 bucket following the naming convention:

s3://qos-rady-shell-events-[EVENT_SUBDOMAIN]/

Discovery command lists all event buckets:

aws s3 ls | grep qos-rady-shell-events

Content Upload Strategy:

The Python sync process uploads refreshed event HTML files to each bucket's root:

aws s3 cp event-index.html s3://qos-rady-shell-events-event-spring-2024/index.html \
  --cache-control "max-age=3600" \
  --content-type "text/html; charset=utf-8"

Short cache headers (1 hour) allow the CDN to serve recent content while reducing origin load. The GAS service updates run three times daily (6am, 12pm, 6pm PT), with corresponding site renders scheduled via cron.

CloudFront Cache Invalidation

After each upload batch, we invalidate the CloudFront distribution for that subdomain to ensure fresh content reaches users immediately:

aws cloudfront create-invalidation \
  --distribution-id DIST_ID \
  --paths "/*"

The distribution ID for each event subdomain is managed in Route53, where ALIAS records point to CloudFront distributions. We discovered distribution IDs by listing all CF distributions and matching against event subdomain hostnames.

Example workflow for one event:

  • 6:00am: GAS scheduler triggers ArtistCelebrationsService for all upcoming events
  • 6:05am: Python render pipeline calls GAS endpoint, injects content into HTML, uploads to S3
  • 6:06am: CloudFront invalidation for distribution tied to that event's subdomain
  • 6:10am: Invalidation complete; users see refreshed artist spotlight within their regional cache refresh window

Key Design Decisions

Why Google Apps Script for content generation? The event metadata already lives in Google Sheets; GAS has native Sheets integration and can be deployed as a web service with minimal infrastructure. This avoided introducing a separate backend service.

Why Python for the render pipeline? The site renderer already used Python for template processing and S3 sync. Extending it with GAS integration kept the deployment pipeline in one language and allowed atomic HTML-generation-plus-injection operations.

Why CloudFront invalidation per upload? Artist spotlights are dynamic marketing content with limited shelf-life (updated 3x daily). Paying for edge invalidations ensures stale content doesn't linger for hours. The cost is negligible at this scale.

Why cache-control max-age=3600 on S3 objects? Balances freshness against origin load. If GAS is unavailable for one update cycle, the old content remains valid for one hour, preventing error pages. CloudFront invalidation still ensures users get fresh content within minutes of a successful update.

Deployment and Verification

Verified the artist spotlight injection by fetching sample event pages and confirming the <section class="artist-celebration"> block contained dynamically generated content rather than placeholder text.