Skip to content

How it fits together

import { Aside } from ‘@astrojs/starlight/components’;

Melbora runs on AWS and ships everything client-facing through Vercel. At a glance:

┌─────────────┐ JWT ┌──────────────────┐
│ Portal │ ─────────▶ │ API Gateway │
│ (Vite SPA) │ │ (us-east-1) │
└─────────────┘ └────────┬─────────┘
┌─────────────┐ vc_pat_ │
│ CLI / SDK │ ─────────────────────┤
│ MCP / cURL│ │
└─────────────┘ ▼
┌──────────────────┐
│ Lambda (api.ts) │
│ custom Router │
└────────┬─────────┘
┌────────────┬───────────────┼───────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────────┐ ┌──────────┐ ┌─────────────┐ ┌────────────┐ ┌─────────────┐
│ DynamoDB │ │ Secrets │ │ SQS │ │ Cognito │ │ Integrations│
│ (~12 tables) │ │ Manager │ │ (provision, │ │ (auth) │ │ GH, Vercel,│
│ │ │ │ │ webhooks) │ │ │ │ Shopify,…) │
└──────────────┘ └──────────┘ └──────┬──────┘ └────────────┘ └─────────────┘
┌──────────────────┐
│ Worker Lambdas │
│ (provisioning, │
│ webhook, SEO) │
└──────────────────┘

Portal (packages/portal) — Vite + React + React-Router SPA served at vantageconnections.dev. Cognito JWT auth. Two role groups: admin (operators) and client (end customers managing their own site). The Blueprint canvas, the new-website wizard, the content editor, the API key minter all live here.

API (packages/api) — a single Lambda function (handlers/api.ts) fronted by API Gateway. Routes are registered in packages/api/src/routes/*.ts, collected into one custom Router at startup. Two auth modes:

  • JWT for portal traffic (paths without /v1/ prefix)
  • PAT (vc_pat_* bearer tokens) for SDK / CLI / MCP traffic (paths under /v1/)

Same route handlers run for both — the router just resolves a different AuthContext upstream.

Infrastructure (packages/infra) — AWS CDK definition of the whole stack. DDB tables, the API Gateway, the Lambdas, SQS queues, Cognito user pools, Secrets Manager entries, IAM roles. cdk deploy ships it.

Data layer — about a dozen DynamoDB tables, accessed via packages/api/src/repositories/*.ts. The naming is vantage-<entity>: vantage-websites, vantage-website-services, vantage-domains, vantage-clients, vantage-provisioning-jobs, etc. No relational DB.

Integrations (packages/api/src/integrations/*.ts) — wrappers around each third-party API: GitHub, Vercel, Shopify Admin GraphQL, Cal.com, Resend, OpenRouter, Stripe. Each module hides the auth flow and exposes a few high-level operations.

Worker Lambdas — SQS-triggered for long-running work that can’t fit in an API request:

  • Provisioning worker drives the multi-step pipeline for new websites and feature toggles
  • Shopify webhook worker processes webhook deliveries (GDPR ack, APP_UNINSTALLED cleanup)
  • SEO cron runs scheduled scans + GEO probes

When you run vantage websites list:

  1. CLI loads ~/.vantage/credentials.json, picks up the vc_pat_* token
  2. SDK sends GET /v1/websites with Authorization: Bearer vc_pat_...
  3. API Gateway forwards to the API Lambda
  4. Router sees /v1/ prefix → strips it, switches to PAT auth mode
  5. PAT resolver hashes the token, looks it up in vantage-api-keys, resolves to a Cognito sub + role
  6. Route matched: GET /websites (admin-only) → admin role required
  7. Handler queries vantage-websites (scan or query depending on role)
  8. JSON serialized, returned

Round-trip is typically 80-200ms warm, 400-800ms cold start.

Provisioning lifecycle (CLI → SQS → worker)

Section titled “Provisioning lifecycle (CLI → SQS → worker)”

When you run vantage websites create:

  1. API handler validates input, creates the Website row in vantage-websites with status: "provisioning"
  2. API enqueues a ProvisioningJob message to the SQS queue
  3. API returns the Website immediately — the deploy hasn’t started yet
  4. SQS triggers the provisioning worker Lambda
  5. Worker walks the orchestrator pipeline step-by-step:
    • github_create_repo (from template)
    • github_invite_collaborator
    • vercel_create_project
    • vercel_set_env_vars
    • vercel_initial_deploy
    • finalize → flips status: "active"
  6. Each step persists progress to vantage-provisioning-jobs so the portal can show live state
  7. Failures retry per-step; permanent failures mark the job failed with a reason

See Provisioning for the full step-by-step.

Webhook lifecycle (third party → SQS → worker)

Section titled “Webhook lifecycle (third party → SQS → worker)”

When Shopify sends an APP_UNINSTALLED webhook:

  1. POST hits /webhooks/shopify (public route, no JWT/PAT)
  2. Handler verifies the HMAC signature with the shared webhook secret
  3. Cross-checks the X-Shopify-Shop-Domain header against the websiteId in the URL — defense against cross-tenant webhook spoofing
  4. Enqueues the verified payload to SQS
  5. Returns 200 OK immediately (Shopify cuts you off if you delay)
  6. Webhook worker Lambda processes the payload: deletes the WebsiteService row, revokes secrets, log-acks GDPR compliance webhooks

Three auth flavors. Portal uses Cognito JWT — sign in with email, JWT issued, sent on every request. SDK / CLI / MCP use personal access tokens (vc_pat_*) minted in the portal under Settings → API keys; bearer-authed against /v1/*. Webhooks use shared secrets verified per-message — no Cognito, no PAT. The router resolves the appropriate AuthContext upstream so handlers don’t care which mode they’re running in.

StateWhere
Websites, Services, Domains, JobsDynamoDB
Auth users + groupsCognito
OAuth tokens (Shopify, etc.)Secrets Manager
Webhook payloads in flightSQS
CMS content documentsDynamoDB (vantage-website-content)
Blueprint node positionsDynamoDB (vantage-website-blueprint-layout)
Vercel deploy stateVercel (we don’t mirror)
GitHub repo stateGitHub (we don’t mirror)

The general principle: Melbora owns the platform’s view of each integration; the integration owns its own state. We don’t re-store deploys, commits, products — we query the source of truth on demand and cache only what’s hot.