Skip to content

Instantly share code, notes, and snippets.

@bogorad
Last active January 10, 2026 15:41
Show Gist options
  • Select an option

  • Save bogorad/07d84eeb3e995249ba8f86aab1eddfed to your computer and use it in GitHub Desktop.

Select an option

Save bogorad/07d84eeb3e995249ba8f86aab1eddfed to your computer and use it in GitHub Desktop.
# Hermes Bot MVP - Technical Design

Hermes Bot MVP - Technical Design

Date: 2026-01-08
Status: Approved
Version: 2.0 (Simplified - No Scraping Required)

Executive Summary

Hermes Bot is a subscription service that notifies customers when specific Hermes products become available in their chosen country. The system leverages shengsho.com's email notifications which already contain complete product data (name, price, SKU, direct Hermes URL, images) - eliminating the need for web scraping entirely.

Key Insight: Shengsho emails include all necessary data pre-parsed. We simply parse the HTML email, match to customer preferences, and forward notifications immediately.

Key Technologies: Hono/JSX, HTMX, Postgres, Resend.com, Stalwart Mail Server, Cheerio (HTML parsing), Rootless Podman on Hetzner

Infrastructure: Fully automated with Terraform (Hetzner provisioning) and Ansible (server configuration)


1. System Architecture Overview

Single-Process Architecture

Hono Server (src/index.ts)

  • HTTP routes: landing page, auth (Apple/Google OAuth), subscription management
  • HTMX-powered UI (server-rendered JSX, no client-side JS except Stripe checkout)
  • Webhook endpoint: /webhooks/stalwart receives emails from Stalwart
  • Synchronous processing: Parse email HTML → Match customers → Send notifications (all in webhook handler)
  • Session-based auth using cookies (required for pure HTMX forms)
  • No background worker needed - everything happens in webhook request

Deployment on Hetzner VPS (Rootless Podman)

Infrastructure as Code:

  • Terraform: Provisions Hetzner VPS (CPX21/CPX31), firewall rules, DNS records for all domains
  • Ansible: Configures server (rootless podman, systemd units, SSL certs via certbot)
  • Idempotency: All scripts run multiple times safely, start-from-zero capable

Containerized Services:

  • Container 1: Stalwart mail server (receives emails on port 25/587/993)
  • Container 2: Postgres database
  • Container 3: Hono web app (connects to Postgres, exposes port 3000)
  • Container 4: Nginx reverse proxy (SSL termination, routes to Hono)
  • All managed via podman-compose or systemd quadlet units
  • Rootless setup (runs as non-root user)
  • Domain MX records → Hetzner server IP

Data Flow:

Shengsho Email → Stalwart → Webhook → Parse HTML → Match Customers → Send Notifications (Resend)

Processing Time: Sub-second from email arrival to customer notification (no queue delays)


2. Database Schema

Core Tables

-- Customer accounts (OAuth only)
CREATE TABLE IF NOT EXISTS customers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT NOT NULL UNIQUE,
  oauth_provider TEXT NOT NULL, -- 'apple' or 'google'
  oauth_id TEXT NOT NULL, -- provider's user ID
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(oauth_provider, oauth_id)
);

-- Active subscriptions (one row per country per customer)
CREATE TABLE IF NOT EXISTS subscriptions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
  country_code TEXT NOT NULL, -- 'FR', 'US', 'UK', etc.
  bag_collections TEXT[] NOT NULL, -- ['Birkin', 'Kelly', 'Evelyne']
  status TEXT NOT NULL DEFAULT 'active', -- 'active', 'cancelled', 'payment_failed'
  stripe_subscription_id TEXT UNIQUE,
  stripe_customer_id TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(customer_id, country_code)
);

-- Track which countries we've subscribed to on shengsho
CREATE TABLE IF NOT EXISTS shengsho_subscriptions (
  country_code TEXT PRIMARY KEY,
  dummy_domain TEXT NOT NULL, -- 'albert07.com'
  dummy_email TEXT NOT NULL, -- '[email protected]'
  subscribed_at TIMESTAMPTZ DEFAULT NOW(),
  last_notification_at TIMESTAMPTZ -- track activity
);

-- Log notifications sent to customers
CREATE TABLE IF NOT EXISTS notifications_sent (
  id BIGSERIAL PRIMARY KEY,
  customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
  product_name TEXT NOT NULL,
  product_sku TEXT,
  hermes_url TEXT NOT NULL,
  country_code TEXT NOT NULL,
  bag_collection TEXT, -- which collection matched (Birkin, Kelly, etc.)
  shengsho_email_data JSONB, -- raw email for debugging
  sent_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_notifications_customer ON notifications_sent(customer_id, sent_at);
CREATE INDEX IF NOT EXISTS idx_notifications_country ON notifications_sent(country_code, sent_at);

-- Server-side sessions for HTMX auth
CREATE TABLE IF NOT EXISTS sessions (
  id TEXT PRIMARY KEY, -- random session ID
  customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
  expires_at TIMESTAMPTZ NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);

-- Track applied migrations
CREATE TABLE IF NOT EXISTS schema_migrations (
  version INT PRIMARY KEY,
  applied_at TIMESTAMPTZ DEFAULT NOW()
);

Key Design Decisions

  • subscriptions.bag_collections is TEXT array for simple substring matching
  • notifications_sent.shengsho_email_data stores raw email as JSONB (debugging, audit trail)
  • UNIQUE(customer_id, country_code) prevents duplicate country subscriptions
  • No job queue needed - processing is synchronous and fast (<1s typically)
  • notifications_sent provides full audit trail for analytics and debugging

Migration Strategy

  • Pure SQL files in migrations/ directory (numbered: 001_initial.sql, 002_add_index.sql)
  • Applied via simple script that tracks applied migrations in schema_migrations table
  • Idempotent: Each migration uses CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS
  • Start-from-zero: ./scripts/reset-db.sh drops/recreates DB, runs all migrations

3. Email Processing Flow

Stalwart Webhook → Immediate Processing

1. Stalwart receives email from shengsho (e.g., to [email protected])

2. Webhook fires: POST /webhooks/stalwart with payload:

{
  "from": "[email protected]",
  "to": "[email protected]",
  "subject": "🇪🇸 Evelyne Restocking",
  "html": "<full email HTML with product details>",
  "text": "fallback text"
}

3. Hono handler processes synchronously:

app.post('/webhooks/stalwart', async (c) => {
  const { from, to, html, subject } = await c.req.json();
  
  // A. Validate & lookup country
  const country = await db.query(
    'SELECT country_code FROM shengsho_subscriptions WHERE dummy_email = $1',
    [to]
  );
  if (!country) return c.text('Unknown recipient', 200);
  
  // B. Parse product data from HTML
  const productData = parseShengshoeEmail(html);
  if (!productData.hermesUrl) return c.text('No product found', 200);
  
  // C. Match to active customers
  const matches = await matchCustomers(country.country_code, productData);
  
  // D. Send notifications immediately
  for (const customer of matches) {
    await sendNotification(customer, productData);
    await logNotification(customer, productData);
  }
  
  return c.text('OK', 200);
});

4. Return 200 OK (entire flow completes in <1 second)

Parsing Strategy

Shengsho emails contain structured HTML with all product details. Parse using Cheerio:

function parseShengshoeEmail(html) {
  const $ = cheerio.load(html);
  
  // Product link (direct Hermes URL)
  const hermesUrl = $('a[href*="hermes.com/"]').attr('href');
  
  // Product details (from structured divs)
  const productName = $('div[style*="font-size:16px"]').first().text().trim();
  const price = $('div[style*="font-size:12px"]').eq(0).text().trim();
  const sku = $('div[style*="font-size:12px"]').eq(1).text().trim();
  const imageUrl = $('img[src*="assets.shengsho.com"]').attr('src');
  
  // Extract country code from Hermes URL
  // Example: https://www.hermes.com/es/es/product/... → ES
  const countryMatch = hermesUrl?.match(/hermes\.com\/([a-z]{2})\//);
  const countryCode = countryMatch ? countryMatch[1].toUpperCase() : null;
  
  return {
    hermesUrl,
    productName,
    price,
    sku,
    imageUrl,
    countryCode
  };
}

Example parsed data from real email:

  • URL: https://www.hermes.com/es/es/product/bolso-evelyne16-amazone-H069426CCFN/
  • Product: "Bolso Évelyne 16 Amazone"
  • Price: "1,750 €"
  • SKU: "H069426CCFN"
  • Image: https://assets.shengsho.com/H069426CCFN.jpg
  • Country: "ES" (Spain)

Error handling:

  • No Hermes URL found → Log warning, return 200 OK (might be spam/test email)
  • Malformed HTML → Store raw email in JSONB, alert admin for manual review
  • Missing fields (price/SKU) → Continue processing (not critical for notification)

Stalwart Configuration

  • Catch-all for all domains: Accepts email to any address at registered domains
  • Simple routing: One webhook endpoint for all domains
  • Authentication: Shared secret in header (X-Webhook-Secret)
  • Error handling:
    • Unknown domain → log warning, return 200 OK (might be spam/test)
    • Malformed HTML → store raw email, mark job 'failed', alert admin

4. Customer Notification Flow

Matching & Sending

1. Query matching subscriptions:

async function matchCustomers(countryCode, productData) {
  // Get all active subscriptions for this country
  const subscriptions = await db.query(`
    SELECT c.id, c.email, s.bag_collections
    FROM subscriptions s
    JOIN customers c ON c.id = s.customer_id
    WHERE s.country_code = $1 
      AND s.status = 'active'
  `, [countryCode]);
  
  // Filter by bag collection match (substring matching)
  return subscriptions.filter(sub => {
    return sub.bag_collections.some(collection => 
      productData.productName.toLowerCase().includes(collection.toLowerCase())
    );
  }).map(sub => ({
    ...sub,
    matchedCollection: sub.bag_collections.find(c => 
      productData.productName.toLowerCase().includes(c.toLowerCase())
    )
  }));
}

2. Send notifications via Resend.com:

async function sendNotification(customer, productData) {
  await resend.emails.send({
    from: '[email protected]',
    to: customer.email,
    subject: `🛍️ New ${customer.matchedCollection} - ${productData.countryCode}`,
    text: `
New Hermes ${customer.matchedCollection} available!

${productData.productName}
${productData.price || 'Price not listed'}
SKU: ${productData.sku}

View now: ${productData.hermesUrl}

---
Manage your subscription: https://hermes-alerts.com/dashboard
Unsubscribe: https://hermes-alerts.com/unsubscribe
    `
  });
}

3. Log notification:

async function logNotification(customer, productData) {
  await db.query(`
    INSERT INTO notifications_sent (
      customer_id, product_name, product_sku, 
      hermes_url, country_code, bag_collection,
      shengsho_email_data
    ) VALUES ($1, $2, $3, $4, $5, $6, $7)
  `, [
    customer.id,
    productData.productName,
    productData.sku,
    productData.hermesUrl,
    productData.countryCode,
    customer.matchedCollection,
    productData.rawHtml // full email for audit
  ]);
}

Email Template (MVP)

Minimal text + link format:

New Hermes [Collection] available in [Country]!

[Product Title]
[Price]

Click to view: [Hermes URL]

---
Manage your subscription: [Dashboard URL]
Unsubscribe: [Unsubscribe URL]

Key characteristics:

  • Plain text (no HTML for MVP)
  • Fast to generate
  • Loads anywhere (email clients, mobile)
  • Professional but minimal

Edge Cases

  • No matching customers: Log for analytics, return 200 OK (email processed successfully)
  • Resend API fails: Retry 3 times with exponential backoff, then log error and alert admin
  • Customer email bounces: Resend webhook handles (disable their subscription automatically)
  • Multiple collections match: Send one email mentioning primary match (first found)
  • Duplicate products: Log prevents duplicate notifications (check recent sends by SKU/customer)

5. Error Handling & Monitoring

Failure Scenarios & Responses

1. Email Parsing Failures:

  • No Hermes URL found: Log warning, return 200 OK (might be spam/test)
  • Malformed HTML: Store raw email in JSONB, alert admin for review
  • Missing country lookup: Unknown recipient domain, log and return 200 OK
  • Invalid product data: Log error with raw HTML, alert admin if pattern changes

2. Email Sending Failures (Resend API):

  • Rate limit hit: Exponential backoff (1s, 2s, 4s), retry up to 3 times
  • API error: Retry 3 times with backoff
  • After 3 fails: Log error, alert admin, continue processing (don't block webhook)

3. Webhook Issues:

  • Stalwart can't reach Hono: Stalwart queues email, retries automatically (handles downtime)
  • Invalid email format: Log error, alert admin, return 200 OK (don't crash webhook)
  • Unknown domain: Log warning, return 200 OK (might be spam)
  • Processing timeout: Webhook has 30s timeout, log if parsing takes >5s (investigate)

4. Database Issues:

  • Connection failure: App crashes, systemd restarts automatically
  • Query errors: Log full context, alert admin, return 500 (Stalwart will retry)
  • Transaction rollback: Notification sends are idempotent (check recent sends first)

Admin Dashboard

Location: /admin route (password protected via env var or OAuth whitelist)

System Health (auto-refresh every 30s):

  • Last webhook received: Timestamp per country (red if >24h ago)
  • Webhook processing: Average response time, recent errors
  • Email sending: Success rate (last 24h), Resend API status
  • Database stats: Connection status, recent query latency

Business Metrics:

  • Active subscriptions by country (table: FR: 12, US: 8, UK: 5)
  • Revenue: Total MRR, subscriptions by status
  • Notifications sent (last 24h/7d/30d)
  • Top products: Most notified SKUs, trending collections

Operational Logs:

  • Recent notifications (last 50): product, country, customer count, sent time
  • Recent emails received: From shengsho, parsing status
  • Shengsho activity: Last notification received per country

Manual Actions:

  • Button: "Test notification" (send test email to admin)
  • Form: "Add country subscription" (track new shengsho subscription)
  • Button: "Parse test email" (upload HTML, see parsed output)

Monitoring Alerts (MVP - Email Based)

Alert Conditions:

  • Parsing failures > 5 in 1 hour (Shengsho changed email format?)
  • No emails received in 24 hours for any active country (shengsho subscription dead?)
  • Email send failures > 10 in 1 hour (Resend API issues?)
  • Webhook response time > 5s average (database slow?)
  • Application crash/restart detected (systemd notification)

Alert Delivery:

  • Email to admin
  • Subject prefix: [Hermes-Bot] for easy filtering

Future Monitoring (Post-MVP):

  • Add metrics endpoint for Prometheus/Grafana
  • Track: webhooks/minute, parse time, email delivery rate, customer growth
  • Uptime monitoring via external service (UptimeRobot, Better Uptime)

7. Multi-Domain Management

Anti-Detection Strategy

Domain Selection:

  • Use random, personal-looking domains: albert07.com, myfavoritepuppy17.org, sunnydays2024.net
  • Avoid: Business terms (alert, notify, bag, hermes, luxury, track, watch)
  • Mix TLDs: .com, .org, .net, .io (look like personal hobby sites)
  • Register upfront: 15-20 domains, use as needed per country
  • Goal: Each looks like unrelated personal site to shengsho

Domain Setup (Manual, One-Time per Country):

  1. Pick unused domain from pool (e.g., myfavoritepuppy17.org for France)

  2. Terraform adds DNS records:

    • MX record → Hetzner server IP
    • SPF/DKIM records for email deliverability
    • Optional: A record → static HTML page (adds legitimacy)
  3. Manually subscribe to shengsho.com:

  4. Record in database:

    INSERT INTO shengsho_subscriptions (country_code, dummy_domain, dummy_email)
    VALUES ('FR', 'myfavoritepuppy17.org', '[email protected]');

Email Routing

Stalwart Catch-All Configuration:

  • Accepts email to any address at registered domains
  • Webhook includes full recipient email
  • Hono looks up domain in shengsho_subscriptions table
  • Maps to country_code for processing

Customer-Facing Domain:

  • Single professional brand: hermes-alerts.com (or similar)
  • All customer emails FROM: [email protected]
  • Customers NEVER see: Dummy domains (albert07.com, etc.)
  • Separation: Dummy domains = shengsho-only, look unrelated

Bonus Anti-Detection

Optional Static Pages:

  • Add simple HTML page to each dummy domain
  • Examples: "My Photography Blog", "Puppy Pictures", "Travel Diary"
  • Makes domain look legitimate if shengsho investigates
  • Can use same template, just swap title/images
  • Host via Nginx on same server (minimal effort)

Scaling to New Countries

Workflow:

  1. Customer wants country not in shengsho_subscriptions table
  2. Admin dashboard shows: "Action needed: Subscribe to shengsho for country XX"
  3. Admin manually:
    • Pick unused dummy domain from pool
    • Subscribe to shengsho for that country (all bags)
    • Add row to shengsho_subscriptions via dashboard form
  4. Customer immediately starts receiving notifications (automatic from this point)

Domain Pool Management:

  • Track in separate table: domain_pool (domain, status='available'/'in_use', purpose)
  • Admin dashboard shows available domains
  • Alert when <5 domains remain available

8. Customer Journey

Sign-Up Flow (Configure First, Then Payment)

1. Landing Page:

  • Value proposition: "Never miss new Hermes products in your country"
  • CTA: "Get Started"

2. Authentication:

  • "Sign in with Apple" / "Sign in with Google" (OAuth only)
  • No email/password forms (simplicity, security)
  • Creates customer record in database

3. Configuration:

  • Select country from dropdown (show only countries where shengsho covers)
  • Select bag collections: Checkboxes for ["Birkin", "Kelly", "Evelyne", "Constance", "Picotin", etc.]
  • Preview: "You'll receive notifications for: Birkin, Kelly in France"

4. Payment (Stripe Checkout):

  • Show price: "$X/month for France notifications"
  • Click "Subscribe" → Redirect to Stripe Checkout
  • Stripe handles: Card entry, Apple Pay, payment processing
  • Webhook confirms payment → activate subscription

5. Success:

  • Redirect to dashboard: "Your subscription is active!"
  • Show: Selected country, bag collections, manage/cancel options

Dashboard (HTMX-Powered)

Customer capabilities:

  • View active subscriptions (country + bag collections)
  • Add/remove bag collections (within same country subscription)
  • Add new country (triggers new Stripe subscription)
  • Cancel subscription (cancels at period end)
  • Update billing info (redirect to Stripe portal)
  • Unsubscribe from emails (soft delete)

No JavaScript required:

  • All forms use HTMX: hx-post, hx-get, hx-swap
  • Server renders updated HTML fragments
  • Smooth UX without client-side complexity

6. Tech Stack Summary

Component Technology Purpose
Web Framework Hono Fast, lightweight, edge-compatible
Templating Hono JSX Server-side rendering
Frontend HTMX Dynamic UI without JavaScript
Authentication OAuth (Apple/Google) Secure, passwordless login
Database Postgres Reliable, powerful querying
HTML Parsing Cheerio Parse Shengsho email HTML
Email Receiving Stalwart Self-hosted mail server with webhooks
Email Sending Resend.com Reliable transactional email API
Payments Stripe Subscription billing, Apple Pay
Containers Rootless Podman Secure, daemonless containers
Infrastructure Terraform + Ansible Automated provisioning & configuration
Hosting Hetzner VPS Cost-effective European hosting

7. Implementation Phases

Overview: LLM-Guided Implementation

This guide assumes implementation via LLM CLI tools (Factory/Droid, Claude CLI, etc.) that can read files, execute commands, and make edits directly.


Parallelization Map

Phase 1 (Infrastructure)
├── [PARALLEL] Terraform VPS + DNS ───────────────┐
├── [PARALLEL] Ansible playbook draft ────────────┤
└── [PARALLEL] Domain registration (manual) ──────┘
                         │
              [SEQUENTIAL] Deploy & verify
                         │
Phase 2 + Phase 3 ← Start in parallel sessions
├── [PARALLEL] Hono routes + HTMX
├── [PARALLEL] OAuth flows
├── [PARALLEL] Stripe integration
├── [PARALLEL] Webhook handler + email parsing + notifications
                         │
Phase 4 (Admin & Testing) ← Can overlap
├── [PARALLEL] Dashboard UI
├── [PARALLEL] Monitoring alerts
├── [PARALLEL] E2E testing
                         │
Phase 5 (Launch) ← Sequential validation

Key Parallelization Opportunities:

  • Multiple LLM sessions: Generate Terraform + Ansible simultaneously
  • Phase 2 & 3 can proceed fully in parallel (no shared dependencies)
  • Phase 3 combines parsing + matching + sending (no worker needed)
  • Admin dashboard (Phase 4) can start once DB schema exists

LLM CLI Session Management

Starting a task:

Read docs/plans/2026-01-08-hermes-bot-mvp-design.md, 
then implement [component] following Section [N].

Same session when:

  • Iterating on errors from LLM's own code
  • Adding features to files LLM just created
  • Debugging issues with full context

Fresh session when:

  • New phase/component
  • LLM stuck repeating same mistake
  • Switching to different part of codebase

Phase 1: Infrastructure

Dependencies: None (starting point)

Parallel Tasks:

Task LLM Prompt Verification
Terraform VPS "Read the design doc Section 1 and 11. Create Terraform for Hetzner CPX31 with firewall for ports 22,25,80,443,587,993" terraform plan shows server + firewall
Terraform DNS "Add DNS module for MX, SPF, DKIM records. Reference Section 7 for domain strategy" Plan shows DNS resources
Ansible draft "Create Ansible playbook for rootless podman setup per Section 1. Target Debian 12" ansible-playbook --check passes

Sequential Tasks:

Task LLM Prompt Verification
Deploy "Run terraform apply, then ansible-playbook. Debug any failures" SSH to server works
Stalwart "Configure Stalwart catch-all per Section 3. Set webhook to /webhooks/stalwart" Test email arrives in logs

Handoff Checklist:

  • SSH to server works
  • podman ps shows containers running
  • Stalwart receives test email
  • Postgres accepts connections

Phase 2: Core Application

Dependencies: Phase 1 infrastructure running

Parallel Tasks:

Task LLM Prompt Verification
Hono skeleton "Create Hono app with JSX per Section 9. Routes: /, /login, /dashboard, /webhooks/stalwart" curl localhost:3000 returns HTML
OAuth "Implement Apple/Google OAuth per Section 8. Use session table from Section 2" OAuth redirect works
Stripe "Add Stripe checkout per Section 8. Webhook for subscription.created" Test checkout completes
DB queries "Create customer/subscription CRUD using schema from Section 2" Insert/select works

Handoff Checklist:

  • Full login flow works (OAuth → session → dashboard)
  • Subscription creates in DB with Stripe webhook
  • Dashboard displays customer subscriptions
  • HTMX forms submit without page reload

Phase 3: Email Processing & Notifications

Dependencies: Hono app running, DB schema exists

Parallel Tasks:

Task LLM Prompt Verification
Webhook handler "Implement /webhooks/stalwart per Section 3. Synchronous processing: parse → match → send" POST returns 200 in <1s
Email parser "Parse shengsho emails with cheerio per Section 3. Extract all product data" Test HTML extracts complete data
Customer matching "Match product to subscriptions per Section 4. Substring matching on collections" Correct customers matched
Notifications "Send via Resend per Section 4. Log to notifications_sent table" Test email received

Handoff Checklist:

  • Test email → notification sent to matching customers
  • Parsed data includes URL, name, price, SKU, country
  • Only customers with matching collections notified
  • Notifications logged in database
  • Unknown domains log warning but don't crash
  • Processing completes in <1 second

Phase 4: Admin & Monitoring

Dependencies: Core app + email processing functional

Parallel Tasks:

Task LLM Prompt Verification
Dashboard "Create /admin with stats per Section 5. Password protect via env var" Dashboard loads
Health checks "Show webhook status, email sending, notifications sent per Section 5" Metrics display correctly
Alerts "Email admin when parsing failures > 5/hour per Section 5" Test alert triggers
Domain mgmt "UI to track shengsho_subscriptions per Section 7" Can add/view domains

Handoff Checklist:

  • Admin can view system health (webhook stats, email delivery)
  • Recent notifications display with details
  • Parse test email button works
  • Alerts fire when thresholds exceeded
  • Domain management UI functional

Phase 5: Testing & Launch

Sequential Tasks:

Task LLM Prompt Verification
E2E test "Write E2E test: signup → subscribe → receive notification" Full flow passes
Load test "Simulate 50 concurrent webhook deliveries. Check for race conditions" No duplicate notifications
Security "Review OAuth, Stripe webhooks, email parsing for vulnerabilities" No critical issues
Runbook "Document ops procedures: restart app, parse test emails, add country" Runbook complete

Handoff Checklist:

  • E2E test passes reliably
  • Load test shows no race conditions or duplicate sends
  • Security review complete (no exposed secrets)
  • Operations runbook documented
  • Beta customers invited

Directing the LLM: Communication Patterns

Start of task:

Read the design doc at docs/plans/2026-01-08-hermes-bot-mvp-design.md.
Implement [component] following Section [N]. 
Create files in [directory].

When LLM misses requirements:

Check Section [N] again - you missed [specific requirement].
Update the implementation.

When code breaks:

[error message]
Fix this. The code should match Section [N] behavior.

When stuck:

Read Section [N] and Section [M] together.
Explain how [component A] connects to [component B], then implement.

Verify design compliance:

Compare your implementation against Section [N].
List anything that deviates from the design.

Verification Commands Per Phase

Phase Quick Check Full Check
1 terraform plan, ansible --check SSH works, podman ps shows containers
2 curl localhost:3000 Complete OAuth + Stripe flow
3 curl -X POST localhost:3000/webhooks/stalwart Job appears in DB with correct data
4 SELECT * FROM scrape_jobs WHERE status='completed' Notification email arrives
5 curl localhost:3000/admin All metrics display, alerts fire
6 Run E2E script Beta user completes full journey

Recovery Patterns

Situation Tell the LLM
Wrong library "Use [X] from Section 9, not [Y]. Refactor."
Missing feature "Section [N] requires [feature]. Add it."
Broken code "Debug this error: [message]. Check against Section [N]."
Stuck in loop Start fresh session: "Read Section [N] fresh and reimplement [component]"
Partial work "Continue from [last working file]. Next: [specific task]."

Anti-Patterns to Avoid

Don't: Ask LLM to implement everything at once
Do: One task at a time, verify before next

Don't: Accept first output without testing
Do: Run verification command, iterate if broken

Don't: Let LLM invent architecture
Do: Always reference specific design sections

Don't: Debug forever in same session
Do: Fresh session after 3 failed attempts

Don't: Ignore design doc constraints
Do: Quote relevant sections when redirecting LLM


8. Risks & Mitigations

Risk Impact Mitigation
Shengsho detects/blocks domains High Use 15-20 innocent-looking domains, rotate slowly
Shengsho changes email format Medium Store raw JSONB, update parser, reprocess old emails
Shengsho stops service High Have backup: direct Hermes scraping (complex, future)
Email parsing breaks Medium Alert on parse failures, manual review, update selectors
Webhook overload (spam/attacks) Low Rate limiting, validate sender, monitor patterns
Database connection exhaustion Medium Connection pooling, monitor query performance
Resend API limits hit Low Monitor send rate, implement backoff, upgrade plan if needed

9. Future Enhancements (Post-MVP)

  1. Advanced Filtering:

    • Size-specific notifications (e.g., "Birkin 25 only")
    • Color preferences (e.g., "black or gold only")
    • Price range filters
    • Parse additional fields from Shengsho emails
  2. Mobile App:

    • Push notifications (instant, more reliable than email)
    • Native iOS/Android apps
    • In-app subscription management
  3. Analytics Dashboard:

    • Customer engagement metrics
    • Product popularity tracking (trending SKUs)
    • Revenue analytics and forecasting
    • Email parsing success rates
  4. Direct Hermes Scraping (Backup Plan):

    • Eliminate shengsho dependency entirely
    • Scrape Hermes directly (requires Puppeteer + proxies)
    • More complex, higher cost, but full control
  5. Multi-Brand Support:

    • Expand to Chanel, Louis Vuitton, etc.
    • Same architecture: email-based monitoring
    • Subscribe to multiple notification services
  6. Auto-Checkout Service:

    • Premium tier: Automated purchase attempts
    • Requires sophisticated scraping + payment handling
    • High value, high complexity, regulatory considerations

Conclusion

This design provides a solid foundation for Hermes Bot MVP. The architecture prioritizes:

  • Extreme Simplicity: Single-process synchronous handling, no job queue, no scraping
  • Speed: Sub-second notification delivery from Shengsho email → customer inbox
  • Reliability: Minimal moving parts, straightforward error handling, idempotent operations
  • Low Cost: No proxy/captcha costs, minimal infrastructure (4 containers total)
  • Maintainability: All logic in one place, easy to debug, HTML parsing vs browser automation

Key Innovation: Leverage Shengsho's pre-parsed product data instead of scraping - eliminates 70% of original complexity and cost.

The manual shengsho subscription approach validates the business model with minimal technical risk. If Shengsho becomes unreliable, the architecture can evolve to direct Hermes scraping (add Puppeteer + proxies as needed).

Next Steps: Begin Phase 1 implementation (infrastructure setup).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment