Webhooks — Shopify pushes to us
📨 inbound — Shopify initiates
app/uninstalled
Merchant removes the app from their store. We delete their merchant row and all dependent data (products, competitors, briefings) via cascade.
shop/redact
GDPR: Shopify asks us to permanently delete all data for a shop. Same as uninstall — full merchant + dependents deletion.
customers/data_request
GDPR: A customer asked Shopify what data an app holds on them. We respond that we store no customer PII — only aggregate order counts.
customers/redact
GDPR: Delete all data for a specific customer. We have no customer-level data to delete — acknowledged with 200.
app_subscriptions/update
A billing subscription changed — activated, cancelled, or frozen. We update the merchant's plan and paywall status accordingly.
GraphQL — we pull from Shopify
→ outbound — we initiate
products.list
Fetch the merchant's full product catalogue so they can pick which SKUs to track during onboarding.
SignalS1Orders — realized prices
Orders from last 30 days. Calculates the average realized unit price per product after discounts are applied.
SignalS2Orders — discount depth
Orders from last 30 days. Calculates what % of order value is discounted and whether discounting is structural or promotional.
SignalS3Orders — refund rate
Orders from last 30 days. Counts how many had refunds — a proxy for product-market fit and quality issues.
SignalS5Orders — UTM channel split
Orders from last 30 days. Breaks down where orders came from: paid social, organic, email, direct.
SignalS6Orders — new vs returning
Orders from last 30 days. Ratio of first-time buyers to returning customers.
HeroOrders — hero product inference
Orders from last 30 days. Identifies the highest-revenue product among the merchant's confirmed matched SKUs.
Billing API — create + verify charge
Creates a recurring charge when a merchant upgrades. Verifies the charge status when Shopify redirects back after the merchant approves or declines.
Traffic & Compute
ALB — shelf-alb
Public HTTPS entry point. Terminates TLS, routes all traffic to the Remix container on port 3000. HTTP → 301 HTTPS redirect on port 80.
ECS Fargate — shelf-remix-service
Long-running service that serves the Remix app. Always on — handles every merchant request, webhook, and OAuth flow. Pulls image from ECR on deploy.
ECS Fargate — shelf-crawl-task
On-demand task, not a service. Spun up per crawl job via ECS.RunTask() — runs the Python pipeline, writes the briefing, then exits. Cold start ~30s.
ECS Fargate — shelf-schema-runner
One-shot migration task. Runs db/schema.sql on deploy when schema changes are needed, then exits. Quiet the rest of the time.
Data & Storage
RDS PostgreSQL — shelf-db
The only persistent store. Holds all 5 app tables plus pg-boss job queue. Private subnet — no public access. Both ECS tasks connect via DATABASE_URL from Secrets Manager.
ECR — shelf-remix + shelf-crawl
Docker image registry. GitHub Actions builds and pushes both images on every main branch deploy. ECS pulls the latest image tag when launching tasks.
S3 + CloudFront — marketing site
Static site hosting for shelfplugin.com. Separate from the app — different repo, different deploy role. CloudFront sits in front for HTTPS + caching.
Security & Config
Secrets Manager
Runtime secret injection. ECS tasks declare secrets in their task definition — the execution role fetches them at container start and injects as env vars. Nothing hardcoded in images.
IAM Roles
Four roles, each scoped to minimum permissions. Execution role fetches secrets + pulls ECR. Task role can trigger crawl ECS tasks. Deploy role can push images and update services.
Security Groups — traffic rules
Four SGs forming a layered perimeter. Internet hits ALB only. ALB → Remix only. Remix + Crawl → RDS only. Crawl has no inbound — outbound-only for web crawling.
Observability
CloudWatch Logs
Three log groups — one per ECS task. The Remix log is your production debugging surface: auth flows, webhook handler output, loader errors. Crawl log shows pipeline progress and Layer 4 output.
Langfuse — Layer 4 traces
Captures every Claude API call from the crawl pipeline: full prompt + response, token counts, latency, merchant ID, model version. The dataset for evaluating prompt quality over time.
Deploy Pipeline
GitHub Actions → ECR → ECS
Push to main triggers the deploy workflow. Builds both Docker images, pushes to ECR, then updates the ECS service to pull the new image. Zero-downtime rolling deploy via ECS service update.
DNS & TLS — Route 53 + ACM
Route 53 hosts the shelfplugin.com zone. ACM wildcard cert covers both root and all subdomains. ALB does TLS termination — containers only see plain HTTP on port 3000.
🖼 UI Components
BriefingCard
card renderer
BriefingSection
briefing layout wrapper
MarketReadSection
market summary
StateIndicatorBar
crawl status · freshness
🗺 Routes
/app
app._index.tsx
LAUI
⚡ user
/app/onboarding/*
6 step routes
LAUI
⚡ user
/app/billing
app.billing.tsx
LAUI
⚡ user
/api/sidekick
api.sidekick.tsx
L
⚡ Sidekick calls
/api/crawl-status
api.crawl-status.tsx
L
↺ polled ~2s
/webhooks
webhooks.tsx
A
📨 inbound
/app/settings/*
3 settings routes
LAUI
⚡ user
/app/billing/callback
app.billing.callback.tsx
L
⚡ Shopify redirect
/api/sidekick/*
market · peers · comparisons
L
⚡ Sidekick calls
/auth/*
auth.*.tsx
LA
⚡ OAuth redirect
📦 Lib / Models
session.server
Shopify auth · token
db.server
pg pool · query wrapper
boss.server
pg-boss client
types.ts
Merchant · Briefing · Plan
shopify-billing.server
plan helpers
🗄 Database
merchants
session · plan · config
products
tracked SKUs
competitors
URLs · homepage extras
briefings
AI output · per cycle
crawl_jobs
pg-boss queue
🤖 AI Layer
crawl pipeline
Python · ECS task
Layer 4 prompt
crawl/prompts/
claude-3-5-sonnet
anthropic model
briefing schema
JSON output shape
🌐 External
Shopify Admin API
product catalogue
Shopify Billing API
subscriptions
Shopify OAuth
app install · token
Shopify Webhooks
inbound events
Anthropic API
claude-3-5-sonnet
Playwright / web
headless crawl
← Hover any node
to inspect it
to inspect it