How Wrapper's pieces fit together across local, dev, and prod, and how each one deploys. If you only want to test locally, jump to Local development.
| Component | What it is | Hosting | Code |
|---|---|---|---|
| backend | Convex functions, auth (Better Auth), billing (Autumn) | Convex | packages/backend |
| relay | Bun + Hono WebSocket relay for remote sessions | Fly.io | apps/relay |
| web | Next.js site | Vercel | apps/web |
| cli | The wrapper CLI (host/attach) |
GitHub Releases / Homebrew | apps/cli |
| docs | Mintlify documentation source | docs.wrapper.sh |
apps/docs |
| mobile | Native iPhone/iPad viewer | TestFlight public beta | apps/mobile |
| Domain | Purpose | Current state |
|---|---|---|
wrapper.sh |
Canonical production website, auth, installer, legal, and support origin | Live on the production Vercel app |
docs.wrapper.sh |
Canonical public Mintlify documentation origin | Live |
Two long-lived environments, driven by branch:
local your machine convex dev (sleek-echidna) + ws://localhost:8080 + localhost:3000
|
dev branch -> DEV env -> Convex sleek-echidna-539 · wrapper-relay-dev · Vercel Preview
main branch -> PROD env -> Convex confident-fox-458 · wrapper-relay-prod · Vercel Production
Real Convex URLs:
| dev | prod | |
|---|---|---|
.cloud |
https://sleek-echidna-539.convex.cloud |
https://confident-fox-458.convex.cloud |
.site |
https://sleek-echidna-539.convex.site |
https://confident-fox-458.convex.site |
Released CLI binaries treat an unset NODE_ENV as production and default to
https://confident-fox-458.convex.cloud,
wss://wrapper-relay-prod.fly.dev, and https://wrapper.sh. Source development
must set NODE_ENV=development and explicit dev endpoints.
-
web (Vercel): Vercel's Git integration decides by the Production Branch setting. Set Production Branch =
main. Thenmain→ Production, every other branch (incl.dev) and PR → Preview. No GitHub workflow needed. -
backend + relay (GitHub Actions): the deploy workflows pick a GitHub Environment from the branch, so secrets/variables resolve per environment:
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'dev' }}
| Workflow | Trigger | Purpose |
|---|---|---|
.github/workflows/deploy-backend.yml |
push dev/main on packages/backend/** |
Convex (convex deploy) |
.github/workflows/deploy-relay.yml |
push dev/main on apps/relay/** |
Fly app (flyctl deploy --app) |
.github/workflows/ci.yml |
every PR/push | audit, lint, format, types, tests, web/docs builds, relay smoke (no deploy) |
.github/workflows/synthetic-health.yml |
schedule/manual | canonical web/legal/docs and dev/prod relay health |
Web deployment is not in GitHub Actions. CI still builds the web app, while Vercel deploys it directly from Git.
Operational ownership, SLO alerts, backup/restore, secret rotation, and the
recommended branch protection settings are in OPERATIONS.md.
| Key | dev (sleek-echidna) | prod (confident-fox) |
|---|---|---|
ENVIRONMENT |
development |
production |
SITE_URL |
Vercel dev/preview URL | https://www.wrapper.sh |
BETTER_AUTH_SECRET |
dev secret | prod secret |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
dev OAuth app | prod OAuth app |
APPLE_CLIENT_ID / APPLE_CLIENT_SECRET |
dev Services ID + secret | prod Services ID + secret |
APPLE_NOTIFICATION_AUDIENCES |
primary dev App ID | sh.wrapper.mobile |
AUTUMN_SECRET_KEY |
am_sk_test_… (sandbox) |
am_sk_live_… (production) |
AUTUMN_WEBHOOK_SECRET |
Autumn Svix signing secret | Autumn Svix signing secret |
WRAPPER_AUTUMN_PRO_PLAN_ID |
pro |
pro |
WRAPPER_AUTUMN_PRO_YEARLY_PLAN_ID |
pro_yearly |
pro_yearly |
RESEND_API_KEY |
Resend test/live key | Resend live key |
RESEND_WEBHOOK_SECRET |
Resend Svix signing secret | Resend Svix signing secret |
FROM_EMAIL_ADDRESS |
Wrapper <notifications@wrapper.sh> |
same |
FROM_EMAIL_ADDRESS_PERSONAL |
Can from Wrapper <can@wrapper.sh> |
same |
WRAPPER_AUTUMN_RELAY_SHARE_FEATURE_ID |
can_share_relay |
can_share_relay |
WRAPPER_RELAY_ENTITLEMENT_FAIL_OPEN |
unset (fail-closed) | unset (fail-closed); emergency 1 only |
APNS_KEY_ID / APNS_TEAM_ID / APNS_KEY_P8 |
optional; phone alerts stay off without these | same |
APNS_BUNDLE_ID |
sh.wrapper.mobile |
sh.wrapper.mobile |
WRAPPER_RELAY_HOST_TICKET_TTL_MS etc. |
optional (has defaults) | optional (has defaults) |
RELEASE_NOTES_INGEST_SECRET |
random secret, same value as the web app | random secret, same value as the web app |
RELEASE_NOTES_INGEST_SECRET gates releaseNotes:subscribe (the landing-page
release-notes signup). The web app's route handler adds a hashed client address
for per-visitor rate limits and
sign the call with this secret; without it anyone could call Convex directly and
choose their own bucket. When unset (local dev) the check is skipped and only the
global and per-email windows apply.
| Key | wrapper-relay-dev | wrapper-relay-prod |
|---|---|---|
CONVEX_URL |
https://sleek-echidna-539.convex.cloud |
https://confident-fox-458.convex.cloud |
PORT |
8080 (in fly.toml) | 8080 (in fly.toml) |
Vercel scopes: Production = main, Preview = dev + PRs, Development
= local vercel dev only. These values are baked at
build time):
| Key | Production | Preview | Development |
|---|---|---|---|
NEXT_PUBLIC_CONVEX_URL |
…confident-fox-458.convex.cloud |
…sleek-echidna-539.convex.cloud |
…sleek-echidna-539.convex.cloud |
NEXT_PUBLIC_CONVEX_SITE_URL |
…confident-fox-458.convex.site |
…sleek-echidna-539.convex.site |
…sleek-echidna-539.convex.site |
NEXT_PUBLIC_APPLE_AUTH_ENABLED |
true after Apple OAuth is configured |
true after Apple OAuth is configured |
false unless testing Apple OAuth |
NEXT_PUBLIC_IOS_APP_URL |
TestFlight public join URL until App Store | same, or empty for docs fallback | empty uses the mobile viewer guide |
NEXT_PUBLIC_IOS_APP_LABEL |
optional CTA override | optional CTA override | optional CTA override |
NEXT_PUBLIC_POSTHOG_KEY |
unused for now; empty keeps PostHog web off | empty | empty |
NEXT_PUBLIC_POSTHOG_HOST |
unused while the key is empty | https://telemetry.wrapper.sh |
https://telemetry.wrapper.sh |
RELEASE_NOTES_INGEST_SECRET |
same value as prod Convex | same value as dev Convex | optional |
NEXT_PUBLIC_* is baked at build time. After creating a TestFlight public
link, set Production NEXT_PUBLIC_IOS_APP_URL to
https://testflight.apple.com/join/<id> and redeploy the web app. Until that
value is set, landing CTAs stay Get iOS viewer with a Beta tag and open the
mobile viewer guide.
| Name | Kind | dev | production |
|---|---|---|---|
CONVEX_DEPLOY_KEY |
secret | sleek-echidna deploy key | confident-fox deploy key |
FLY_API_TOKEN |
secret | ✓ | ✓ |
FLY_APP_NAME |
var | wrapper-relay-dev |
wrapper-relay-prod |
-
Vercel (web): import the repo, set Root Directory =
apps/weband Production Branch =main. Add the env vars from the web table above (Production / Preview / Development). If the build fails resolving the buncatalog:versions, set the Install Command to run at the repo root (cd ../.. && bun install). -
Convex env: set the backend vars on both deployments and create a deploy key each (Dashboard → Settings → Deploy Keys):
cd packages/backend # dev (against sleek-echidna, e.g. `bunx convex env set --preview-name` or via dashboard) bunx convex env set SITE_URL https://<vercel-dev-url> # prod (against confident-fox) bunx convex env set SITE_URL https://www.wrapper.sh
-
Fly (relay): two apps plus secrets:
flyctl apps create wrapper-relay-dev flyctl apps create wrapper-relay-prod flyctl secrets set CONVEX_URL=https://sleek-echidna-539.convex.cloud --app wrapper-relay-dev flyctl secrets set CONVEX_URL=https://confident-fox-458.convex.cloud --app wrapper-relay-prod # FLY_API_TOKEN: `flyctl tokens create deploy` -> GitHub env secret (both envs)
Retire any previous Fly app only after both replacement environments pass their smoke checks and no configuration or traffic references the old app.
-
GitHub Environments: create
devandproduction, then fill the table above. Restrictdevdeployments to thedevbranch and production deployments tomain; apply the repository ruleset specified inOPERATIONS.md. -
OAuth apps: configure each provider callback against the matching web
SITE_URL. Production callbacks are:https://www.wrapper.sh/api/auth/callback/githubhttps://www.wrapper.sh/api/auth/callback/googlehttps://www.wrapper.sh/api/auth/callback/apple
Put each provider client id/secret in the matching Convex deployment env. For Apple, create a Sign in with Apple-enabled primary App ID (
sh.wrapper.mobile), associate a Services ID withwww.wrapper.sh, register the exact Apple return URL above, and generateAPPLE_CLIENT_SECRETas Apple's signed client-secret JWT. Rotate that JWT before its configured expiration. Never commit the.p8key or paste it into chat.Register this production server-to-server notification URL on the primary App ID:
https://confident-fox-458.convex.site/api/auth/apple/notificationsSet
APPLE_NOTIFICATION_AUDIENCES=sh.wrapper.mobileon the matching Convex deployment. If Apple's tested notification token uses both the primary App ID and Services ID as audiences, provide both as a comma-separated allowlist. The endpoint verifies Apple's RS256 signature, issuer, audience, expiry, and replay id before invalidating sessions or deleting Apple-only accounts.Register Autumn and Resend webhooks against the matching Convex
.siteorigin. Production URLs:https://confident-fox-458.convex.site/webhook/autumn https://confident-fox-458.convex.site/webhook/resendVerify
wrapper.shin Resend and setRESEND_API_KEY,RESEND_WEBHOOK_SECRET,FROM_EMAIL_ADDRESS, andFROM_EMAIL_ADDRESS_PERSONALon both Convex deployments. WithoutRESEND_API_KEY, transactional sends are skipped.Generate and pipe the Apple client secret without placing it in shell history:
export APPLE_TEAM_ID="<10-character-team-id>" export APPLE_CLIENT_ID="<services-id>" export APPLE_KEY_ID="<sign-in-with-apple-key-id>" export APPLE_PRIVATE_KEY_PATH="$HOME/secure/AuthKey_<key-id>.p8" bun run apple:client-secret | (cd packages/backend && bunx convex env set --prod APPLE_CLIENT_SECRET) cd packages/backend bunx convex env set --prod APPLE_CLIENT_ID "$APPLE_CLIENT_ID" bunx convex env set --prod APPLE_NOTIFICATION_AUDIENCES sh.wrapper.mobile
Repeat against the intended development deployment before enabling the Vercel Preview Apple flag. The generator caps client-secret lifetime at 180 days; schedule rotation before expiry.
-
Autumn: push config to both (not automatic, the key decides the env):
cd packages/backend bunx atmn push # sandbox (dev) -> am_sk_test_ key bunx atmn push --prod # production -> am_sk_live_ key
After a price change, move remaining
$20monthly subscribers to$15or yearly in Autumn so they match the public list price. -
Docs:
apps/docsis connected to Mintlify athttps://docs.wrapper.sh. Keep DNS, TLS, and representative page requests healthy after navigation or domain changes. -
iOS TestFlight public beta: upload a Release build (prod Convex and relay), add it to an External testing group, complete Beta App Review, then Enable Public Link. Set Vercel Production
NEXT_PUBLIC_IOS_APP_URLto thattestflight.apple.com/join/…URL and redeploy. Do not put an Internal-only invite on the website.
Once done, a push to dev updates matching dev backend/relay paths and creates a
Vercel Preview. A merge to main updates matching production backend/relay
paths and Vercel Production. CLI, docs, and mobile releases use their separate
release or hosting processes.
Four terminals:
# 1) backend (Convex dev deployment + live codegen)
cd packages/backend && bunx convex dev
# 2) relay
cd apps/relay && CONVEX_URL="https://sleek-echidna-539.convex.cloud" bun run dev
# 3) web (optional, for auth/onboarding UI)
cd apps/web && bun run dev # http://localhost:3000
# 4) cli
cd apps/cli && bun run index.ts auth login
bun run index.ts shell-hostpackages/backend/.env.local holds your local Convex + AUTUMN_SECRET_KEY. Point
the CLI at local with apps/cli/.env.local (copy from .env.example).
-
In the host shell, press
Ctrl+\thensto share. -
As a free user you should see "Relay sharing requires Pro" + a checkout URL and "Press Enter to open the browser" (URL only if Stripe is connected in the Autumn sandbox; otherwise the generic message, still correctly denied). Press Enter to open Stripe checkout, the same way
wrapper auth loginopens the device-approval URL. -
Grant Pro (Stripe test card
4242 4242 4242 4242, or attachproin the Autumn dashboard), retry → sharing succeeds and a viewer can attach:bun run index.ts attach --id <session-id>