| 1 | # T3 Connect Relay |
| 2 | |
| 3 | > [!NOTE] |
| 4 | > Sign in to T3 Connect from the app under Settings > Connections. |
| 5 | |
| 6 | The relay is the hosted control plane for T3 Connect. It helps clients discover and connect to |
| 7 | remote environments, manages the cloud-side records needed for those connections, and delivers |
| 8 | optional mobile notifications and Live Activities. |
| 9 | |
| 10 | The relay is intentionally not in the hot path for normal T3 Code traffic. After a client connects, |
| 11 | regular API and WebSocket traffic goes directly between that client and the selected environment. |
| 12 | See the [T3 Connect architecture note](../../docs/internals/t3-connect.md) for the larger system |
| 13 | design. |
| 14 | |
| 15 | ## Responsibilities |
| 16 | |
| 17 | The relay currently owns: |
| 18 | |
| 19 | - Linking T3 Code environments to a cloud account. |
| 20 | - Provisioning and tracking managed environment endpoints. |
| 21 | - Issuing short-lived credentials used to connect clients to linked environments. |
| 22 | - Listing linked environments and registered mobile devices for an account. |
| 23 | - Registering mobile notification preferences and APNs or FCM tokens. |
| 24 | - Receiving published agent activity and delivering notifications or Live Activity updates. |
| 25 | - Persisting relay state and exposing relay-specific traces for diagnostics. |
| 26 | |
| 27 | The environment server and relay have separate credentials and trust boundaries. Read |
| 28 | [Environment Authentication Profile](../../docs/internals/environment-auth.md) before changing token, |
| 29 | credential, or authorization behavior. |
| 30 | |
| 31 | ## Code Map |
| 32 | |
| 33 | - [`alchemy.run.ts`](./alchemy.run.ts) defines the deployed Alchemy stack. |
| 34 | - [`src/worker.ts`](./src/worker.ts) wires Cloudflare bindings, runtime layers, queues, and HTTP APIs. |
| 35 | - [`src/http/Api.ts`](./src/http/Api.ts) contains the relay HTTP handlers and authentication |
| 36 | boundaries. |
| 37 | - [`src/environments`](./src/environments) contains environment linking, credentials, endpoint |
| 38 | provisioning, and connection flows. |
| 39 | - [`src/agentActivity`](./src/agentActivity) contains mobile device registration, activity state, |
| 40 | APNs and FCM delivery, and queue processing. |
| 41 | - [`src/auth`](./src/auth) contains relay token and DPoP proof handling. |
| 42 | - [`src/persistence/schema.ts`](./src/persistence/schema.ts) defines persisted relay state. Keep |
| 43 | schema and migration changes together. |
| 44 | |
| 45 | Shared request and response schemas live in |
| 46 | [`packages/contracts/src/relay.ts`](../../packages/contracts/src/relay.ts). Shared client-side relay |
| 47 | calls live in |
| 48 | [`packages/client-runtime/src/relay/managedRelay.ts`](../../packages/client-runtime/src/relay/managedRelay.ts). |
| 49 | |
| 50 | ## Working Locally |
| 51 | |
| 52 | Install dependencies from the repository root, then run relay-focused checks from this directory: |
| 53 | |
| 54 | ```sh |
| 55 | vp install |
| 56 | cd infra/relay |
| 57 | vp test run |
| 58 | vp run typecheck |
| 59 | ``` |
| 60 | |
| 61 | To run a smaller test set while iterating: |
| 62 | |
| 63 | ```sh |
| 64 | vp test run src/environments/EnvironmentLinker.test.ts |
| 65 | ``` |
| 66 | |
| 67 | Before considering a change complete, run the repository-wide checks from the root: |
| 68 | |
| 69 | ```sh |
| 70 | vp check |
| 71 | vp run typecheck |
| 72 | ``` |
| 73 | |
| 74 | Backend changes should include tests. Prefer testing the real business logic with external |
| 75 | dependencies represented at their boundary rather than mocking internal behavior. |
| 76 | |
| 77 | ## Deployment |
| 78 | |
| 79 | The relay deploys with the Alchemy CLI (`vp run --filter t3code-relay deploy` is `alchemy deploy` |
| 80 | in this directory): |
| 81 | |
| 82 | ```sh |
| 83 | vp run --filter t3code-relay deploy |
| 84 | ``` |
| 85 | |
| 86 | The stack provisions the Cloudflare Worker and queues, managed endpoint resources, database |
| 87 | connectivity, and relay tracing resources. Copy [`infra/relay/.env.example`](./.env.example) to |
| 88 | `infra/relay/.env` and fill in the deployment-specific values before deploying. Alchemy loads that |
| 89 | file from the relay directory. Runtime secrets include Clerk, APNs, and optional FCM credentials. Set |
| 90 | `APNS_ENABLED=false` for an Android-only development deployment without Apple credentials. Production adopts |
| 91 | the configured API and tunnel DNS zones as retained Cloudflare resources. Personal stages reference |
| 92 | the production-owned zones. |
| 93 | |
| 94 | The `prod` Alchemy stage owns the retained PlanetScale database and is the shared hosted relay for |
| 95 | stable and nightly clients. Every other stage references that database and provisions an isolated |
| 96 | PlanetScale branch and runtime role for local development, so deploy `prod` before creating |
| 97 | developer stages: |
| 98 | |
| 99 | ```sh |
| 100 | vp run --filter t3code-relay deploy -- --stage prod |
| 101 | vp run --filter t3code-relay deploy -- --env-file .env.local |
| 102 | ``` |
| 103 | |
| 104 | Alchemy defaults personal deployments to the `dev_$USER` stage. Relay custom domains apply the same |
| 105 | DNS-safe sanitization as Alchemy physical resource names, so `prod` uses |
| 106 | `relay.<RELAY_API_ZONE_NAME>` and `dev_julius` uses |
| 107 | `relay-dev-julius.<RELAY_API_ZONE_NAME>`. Managed environment endpoints are provisioned below |
| 108 | `RELAY_TUNNEL_ZONE_NAME`, which may be a different Cloudflare zone. Production tunnel hostnames use |
| 109 | `prod-<digest>.<RELAY_TUNNEL_ZONE_NAME>`; personal stages use |
| 110 | `<stage>-<digest>.<RELAY_TUNNEL_ZONE_NAME>`. `RELAY_DOMAIN` remains available as an explicit API |
| 111 | domain override. |
| 112 | |
| 113 | The stack's `PublishClientConfig` action ([`src/clientConfig.ts`](./src/clientConfig.ts)) writes the |
| 114 | deployed relay URL and tracing configuration into the repository-root `.env`, so subsequent source |
| 115 | builds point at the relay that was just deployed without copying values manually. It runs only when |
| 116 | one of those outputs changed, and `T3CODE_RELAY_CLIENT_CONFIG_ENV` redirects it to another file. |
| 117 | |
| 118 | ### Deployment CI |
| 119 | |
| 120 | The relay is versioned separately from client releases. `.github/workflows/deploy-relay.yml` deploys |
| 121 | the shared Alchemy `prod` stage on every push to `main`. Stable and nightly release builds both |
| 122 | resolve their static public config from the same |
| 123 | `production` GitHub environment. Pull requests do not deploy relay stages. Developers can |
| 124 | deploy personal non-production stages locally with any stage name other than `prod`. |
| 125 | |
| 126 | The repository must define these Actions variables shared by relay deployments: |
| 127 | |
| 128 | - `CLOUDFLARE_ACCOUNT_ID` |
| 129 | - `PLANETSCALE_ORGANIZATION` |
| 130 | - `AXIOM_ORG_ID` |
| 131 | |
| 132 | The repository must define these Actions secrets shared by relay deployments: |
| 133 | |
| 134 | - `CLOUDFLARE_API_TOKEN` |
| 135 | - `PLANETSCALE_API_TOKEN_ID` |
| 136 | - `PLANETSCALE_API_TOKEN` |
| 137 | - `AXIOM_TOKEN` |
| 138 | |
| 139 | The `production` GitHub environment must define these Actions variables: |
| 140 | |
| 141 | - `RELAY_API_ZONE_NAME` |
| 142 | - `RELAY_TUNNEL_ZONE_NAME` |
| 143 | - `RELAY_DOMAIN` if overriding the derived production relay domain |
| 144 | - `CLERK_PUBLISHABLE_KEY` |
| 145 | - `CLERK_JWT_AUDIENCE` |
| 146 | - `CLERK_JWT_TEMPLATE` |
| 147 | - `APNS_ENVIRONMENT` |
| 148 | - `APNS_TEAM_ID` |
| 149 | - `APNS_KEY_ID` |
| 150 | - `APNS_BUNDLE_ID` |
| 151 | |
| 152 | The `production` GitHub environment must define these Actions secrets: |
| 153 | |
| 154 | - `CLERK_SECRET_KEY` |
| 155 | - `APNS_PRIVATE_KEY` |
| 156 | - `FCM_SERVICE_ACCOUNT` when Android push is enabled |
| 157 | |
| 158 | The account-scoped repository credentials are consumed by Alchemy while provisioning relay stages; they |
| 159 | are not bound into the relay Worker. The production deployment uses an Axiom personal access token, |
| 160 | so `AXIOM_ORG_ID` must accompany `AXIOM_TOKEN`. The release workflow reads the production relay's |
| 161 | derived public URL and Clerk publishable key from the same environment for downstream desktop, CLI, |
| 162 | and hosted web builds. |
| 163 | |
| 164 | See: |
| 165 | |
| 166 | - [T3 Connect setup](../../docs/operations/connect-setup.md) for Clerk keys, JWT templates, and sign-up restrictions. |
| 167 | - [Relay Observability](../../docs/operations/relay-observability.md) for deployment tracing and diagnostics. |
| 168 | - [T3 Connect architecture](../../docs/internals/t3-connect.md) for environment linking and trust boundaries. |