Enquire docs
    Enquire docs
    Getting startedUser guide
    ArchitectureBrowser toolsProcedures
    Claude Code integrationExtension guide
    DeploymentRoadmapBenchmarks
    Operations

    Deployment

    Self-host bridge, API, and Postgres on Railway; web on Vercel.

    Three services, two providers:

    • @enqr/api + Postgres → Railway (one project, two services + plugin)
    • @enqr/bridge → Railway (same project, third service)
    • @enqr/web → Vercel

    DNS layout (assumes you own enqr.dev):

    ServiceDomainProvider
    APIapi.enqr.devRailway
    Bridgebridge.enqr.devRailway (HTTP /mcp, WSS /ws)
    Web dashboardapp.enqr.devVercel
    Marketingenqr.devVercel (or other)

    0. One-time setup

    Edit on GitHub

    Extension guide

    Install, configure, and use the Enquire Chrome extension.

    Roadmap

    What's shipped, what's next, why we don't bundle Chromium.

    On this page

    0. One-time setup1. Railway — create the project1a. Add Postgres1b. Add API service1c. Add Bridge serviceVerify Railway2. Vercel — deploy the web dashboardVerify Vercel3. DNS4. Extension build for Chrome Web Store5. End-to-end smoke testEnv var reference@enqr/api@enqr/bridge@enqr/webExtension build (baked at npm run build:store)RollbackTroubleshooting
    # Strong secret for Better Auth — keep it secret, save in 1Password.
    openssl rand -base64 32
    # → e.g. "1+dZQ+juERCuaRGv9GG9xARd/6XjSlcKAvTJfVWLxSE="

    You'll paste this into Railway as BETTER_AUTH_SECRET for the API service.


    1. Railway — create the project

    cd /Users/dom/work/development/projects/enquire
    railway login          # if not logged in
    railway init           # creates a new project, pick name "enquire"

    This creates an empty project. We'll add three services to it:

    1a. Add Postgres

    railway add --plugin postgres

    Railway provisions a Postgres instance and surfaces ${{Postgres.DATABASE_URL}} as a service-link variable.

    1b. Add API service

    # From the api package directory, link the service to its config
    cd packages/api
    railway service create api
    railway link
    # Railway auto-reads packages/api/railway.toml.
    
    # Set env vars in the dashboard or via CLI:
    railway variables --set "DATABASE_URL=\${{Postgres.DATABASE_URL}}"
    railway variables --set "AUTH_PROVIDER=better-auth"
    railway variables --set "AUTH_API_BASE_URL=https://api.enqr.dev/auth"
    railway variables --set "BETTER_AUTH_SECRET=<paste-32-byte-secret>"
    railway
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    Migrations run on boot via RUN_MIGRATIONS_ON_BOOT=true (default). Watch the deploy log:

    railway logs --tail
    # Should see "migrations up-to-date" then "api listening port=8080"

    1c. Add Bridge service

    cd ../mcp-browser-bridge
    railway service create bridge
    railway link
    
    railway variables --set "AUTH_PROVIDER=better-auth"
    railway variables --set "AUTH_API_BASE_URL=https://api.enqr.dev/auth"
    railway variables --set "ENQR_API_BASE_URL=https://api.enqr.dev"
    railway variables --set "ANTHROPIC_ORG_KEY=sk-ant-..."   # only if /proxy/anthropic in use
    railway variables --set "LOG_LEVEL=info"
    railway variables --set
    
    
    

    The bridge listens on $PORT (Railway-injected) — no manual port config needed.

    Verify Railway

    curl https://api.enqr.dev/health
    # {"status":"ok"}
    
    curl https://bridge.enqr.dev/healthz
    # {"status":"ok","tunnels":0,"pending":0}
    
    # Sign up a test user
    curl -i -X POST https://api.enqr.dev/auth/sign-up/email \
      -H "Content-Type: application/json" \
      -d '{"email":"smoketest@enqr.dev","password":"hunter2hunter2","name":"Smoke"}'

    2. Vercel — deploy the web dashboard

    cd /Users/dom/work/development/projects/enquire-web
    vercel link            # pick or create project "enquire-web"

    Vercel reads the Next app from this repo. Set env vars in the Vercel dashboard (or via CLI):

    vercel env add AUTH_PROVIDER production           # "better-auth"
    vercel env add NEXT_PUBLIC_AUTH_PROVIDER production  # "better-auth"
    vercel env add NEXT_PUBLIC_API_URL production     # "https://api.enqr.dev"
    vercel env add NEXT_PUBLIC_MCP_URL production     # "https://bridge.enqr.dev/mcp"

    Deploy:

    vercel --prod

    Add custom domain:

    vercel domains add app.enqr.dev
    # Then add the CNAME / ALIAS shown to your DNS provider.

    Verify Vercel

    Open https://app.enqr.dev/sign-in — you should see the email/password sign-in form (since AUTH_PROVIDER=better-auth). Sign up with a test email; verify you land on /app.


    3. DNS

    Point your domain at the providers. For Cloudflare or Route 53:

    RecordTypeTarget
    api.enqr.devCNAME<railway-api-host> (shown by railway domain)
    bridge.enqr.devCNAME<railway-bridge-host>
    app.enqr.devCNAMEcname.vercel-dns.com

    Railway and Vercel both auto-provision Let's Encrypt certs once DNS resolves.


    4. Extension build for Chrome Web Store

    cd /Users/dom/work/development/projects/enquire
    
    # Build the production bundle
    VITE_CLOUD_BRIDGE_URL=wss://bridge.enqr.dev/ws \
    VITE_CLOUD_API_URL=https://api.enqr.dev \
    VITE_CLOUD_WEB_URL=https://app.enqr.dev \
    VITE_AUTH_PROVIDER=better-auth \
    npm run build:store
    
    # Output: dist/chrome-mv3-store (manifest mode "store" — no debugger permission)
    npm run zip:store
    # Output: .output/chrome-mv3.zip

    Upload .output/chrome-mv3.zip to the Chrome Web Store Developer Dashboard. Fill in:

    • Privacy policy URL (you need this hosted at enqr.dev/privacy)
    • 3-5 screenshots (1280x800)
    • Detailed description
    • Category: Developer Tools

    Review takes 5-7 business days for a first submission.


    5. End-to-end smoke test

    Once DNS resolves and all three services are live:

    # 1. Web sign-up creates user in DB
    curl -i -X POST https://api.enqr.dev/auth/sign-up/email \
      -H "Content-Type: application/json" \
      -d '{"email":"e2e@enqr.dev","password":"hunter2hunter2","name":"E2E"}'
    # Capture the set-auth-token header value as $TOKEN.
    
    # 2. Bridge accepts the token via /auth/get-session
    TOKEN="<paste here>"
    node -e "
    import WebSocket from 'ws';
    const ws = new WebSocket('wss://bridge.enqr.dev/ws?token=' + encodeURIComponent('$TOKEN'));
    ws.on('open', () => { console.log('✓ tunnel up'); ws.close(); });
    ws.on('error', console.error);
    "
    
    
    
    
    
    
    
    
    

    Env var reference

    @enqr/api

    VarRequiredExample
    DATABASE_URL✓${{Postgres.DATABASE_URL}}
    AUTH_PROVIDER✓better-auth
    AUTH_API_BASE_URL✓https://api.enqr.dev/auth
    BETTER_AUTH_SECRET✓32+ char base64
    BETTER_AUTH_URL✓https://api.enqr.dev/auth
    BETTER_AUTH_TRUSTED_ORIGINS✓https://app.enqr.dev,chrome-extension://
    STRIPE_SECRET_KEYfor billingsk_live_...
    STRIPE_WEBHOOK_SECRETfor billingwhsec_...
    STRIPE_TIER_MAPfor billingprice_pro:2000,price_team:10000
    DASHBOARD_URL✓https://app.enqr.dev
    CORS_ORIGINS✓https://app.enqr.dev,chrome-extension://
    LOG_LEVELoptionalinfo
    RUN_MIGRATIONS_ON_BOOToptionaltrue (default)

    @enqr/bridge

    VarRequiredExample
    AUTH_PROVIDER✓better-auth
    AUTH_API_BASE_URL✓https://api.enqr.dev/auth
    ENQR_API_BASE_URL✓https://api.enqr.dev
    ANTHROPIC_ORG_KEYfor managed-modesk-ant-...
    LOG_LEVELoptionalinfo
    BRIDGE_INCLUDE_COST_IN_RESPONSEoptionalfalse (default — cost stripped from MCP responses)
    BRIDGE_RATE_LIMIT_RPMoptional30

    @enqr/web

    VarRequiredExample
    AUTH_PROVIDER✓better-auth
    NEXT_PUBLIC_AUTH_PROVIDER✓better-auth
    NEXT_PUBLIC_API_URL✓https://api.enqr.dev
    NEXT_PUBLIC_MCP_URL✓https://bridge.enqr.dev/mcp

    Extension build (baked at npm run build:store)

    VarRequiredExample
    VITE_CLOUD_BRIDGE_URL✓wss://bridge.enqr.dev/ws
    VITE_CLOUD_API_URL✓https://api.enqr.dev
    VITE_CLOUD_WEB_URL✓https://app.enqr.dev
    VITE_AUTH_PROVIDER✓better-auth

    Rollback

    Railway auto-keeps the previous deploy and lets you redeploy it via the dashboard's "Deployments" tab. Vercel does the same via the project's "Deployments" panel.

    For schema rollbacks: drizzle-kit doesn't auto-generate down migrations. Keep manual rollback SQL in packages/api/migrations/rollback/ if you need them; migrations are forward-only by default.


    Troubleshooting

    API 503 on /auth/get-session: the bridge calls this endpoint to verify tokens. If it fails, the bridge fails-open on its 5s timeout. Check API logs for auth verify failed — usually a stale BETTER_AUTH_SECRET.

    Bridge 401 on every WS upgrade: extension is sending a token the bridge can't verify. Check that AUTH_API_BASE_URL on the bridge matches the API's BETTER_AUTH_URL. Inspect bridge /__diag/ws (dev-only endpoint; keep it disabled in prod by NOT setting LOG_LEVEL=debug).

    Stripe webhook 400 with WEBHOOK_SIGNATURE_INVALID: Stripe's test-mode and live-mode signatures use different secrets. In Railway, set the appropriate STRIPE_WEBHOOK_SECRET for whichever mode you're in.

    Web sign-in returns 500: if AUTH_API_BASE_URL points at a different host than BETTER_AUTH_URL, the cookie-domain mismatch breaks session validation. Both should resolve to the same API host.

    variables
    --set
    "BETTER_AUTH_URL=https://api.enqr.dev/auth"
    railway variables --set "BETTER_AUTH_TRUSTED_ORIGINS=https://app.enqr.dev,chrome-extension://"
    railway variables --set "DASHBOARD_URL=https://app.enqr.dev"
    railway variables --set "CORS_ORIGINS=https://app.enqr.dev,chrome-extension://"
    railway variables --set "STRIPE_SECRET_KEY=sk_live_..."
    railway variables --set "STRIPE_WEBHOOK_SECRET=whsec_..."
    railway variables --set "STRIPE_TIER_MAP=price_starter:500,price_pro:2000,price_team:10000"
    railway variables --set "LOG_LEVEL=info"
    railway variables --set "NODE_ENV=production"
    # Deploy
    railway up
    # Add custom domain
    railway domain add api.enqr.dev
    "NODE_ENV=production"
    railway up
    railway domain add bridge.enqr.dev
    # 3. Bridge /healthz reflects the connected tunnel
    curl -s https://bridge.enqr.dev/healthz
    # {"status":"ok","tunnels":1,"pending":0} (briefly, before the script closes)
    # 4. Web /app/setup shows a working MCP config snippet
    open https://app.enqr.dev/app/setup
    # Sign in, copy the snippet, paste into Claude Code's MCP config — verify the
    # server name "enquire" appears in tools list.