1# T3 Connect Relay
2
3> [!NOTE]
4> Sign in to T3 Connect from the app under Settings > Connections.
5
6The relay is the hosted control plane for T3 Connect. It helps clients discover and connect to
7remote environments, manages the cloud-side records needed for those connections, and delivers
8optional mobile notifications and Live Activities.
9
10The relay is intentionally not in the hot path for normal T3 Code traffic. After a client connects,
11regular API and WebSocket traffic goes directly between that client and the selected environment.
12See the [T3 Connect architecture note](../../docs/internals/t3-connect.md) for the larger system
13design.
14
15## Responsibilities
16
17The 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
27The environment server and relay have separate credentials and trust boundaries. Read
28[Environment Authentication Profile](../../docs/internals/environment-auth.md) before changing token,
29credential, 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
45Shared request and response schemas live in
46[`packages/contracts/src/relay.ts`](../../packages/contracts/src/relay.ts). Shared client-side relay
47calls live in
48[`packages/client-runtime/src/relay/managedRelay.ts`](../../packages/client-runtime/src/relay/managedRelay.ts).
49
50## Working Locally
51
52Install dependencies from the repository root, then run relay-focused checks from this directory:
53
54```sh
55vp install
56cd infra/relay
57vp test run
58vp run typecheck
59```
60
61To run a smaller test set while iterating:
62
63```sh
64vp test run src/environments/EnvironmentLinker.test.ts
65```
66
67Before considering a change complete, run the repository-wide checks from the root:
68
69```sh
70vp check
71vp run typecheck
72```
73
74Backend changes should include tests. Prefer testing the real business logic with external
75dependencies represented at their boundary rather than mocking internal behavior.
76
77## Deployment
78
79The relay deploys with the Alchemy CLI (`vp run --filter t3code-relay deploy` is `alchemy deploy`
80in this directory):
81
82```sh
83vp run --filter t3code-relay deploy
84```
85
86The stack provisions the Cloudflare Worker and queues, managed endpoint resources, database
87connectivity, 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
89file 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
91the configured API and tunnel DNS zones as retained Cloudflare resources. Personal stages reference
92the production-owned zones.
93
94The `prod` Alchemy stage owns the retained PlanetScale database and is the shared hosted relay for
95stable and nightly clients. Every other stage references that database and provisions an isolated
96PlanetScale branch and runtime role for local development, so deploy `prod` before creating
97developer stages:
98
99```sh
100vp run --filter t3code-relay deploy -- --stage prod
101vp run --filter t3code-relay deploy -- --env-file .env.local
102```
103
104Alchemy defaults personal deployments to the `dev_$USER` stage. Relay custom domains apply the same
105DNS-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
111domain override.
112
113The stack's `PublishClientConfig` action ([`src/clientConfig.ts`](./src/clientConfig.ts)) writes the
114deployed relay URL and tracing configuration into the repository-root `.env`, so subsequent source
115builds point at the relay that was just deployed without copying values manually. It runs only when
116one of those outputs changed, and `T3CODE_RELAY_CLIENT_CONFIG_ENV` redirects it to another file.
117
118### Deployment CI
119
120The relay is versioned separately from client releases. `.github/workflows/deploy-relay.yml` deploys
121the shared Alchemy `prod` stage on every push to `main`. Stable and nightly release builds both
122resolve their static public config from the same
123`production` GitHub environment. Pull requests do not deploy relay stages. Developers can
124deploy personal non-production stages locally with any stage name other than `prod`.
125
126The repository must define these Actions variables shared by relay deployments:
127
128- `CLOUDFLARE_ACCOUNT_ID`
129- `PLANETSCALE_ORGANIZATION`
130- `AXIOM_ORG_ID`
131
132The 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
139The `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
152The `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
158The account-scoped repository credentials are consumed by Alchemy while provisioning relay stages; they
159are not bound into the relay Worker. The production deployment uses an Axiom personal access token,
160so `AXIOM_ORG_ID` must accompany `AXIOM_TOKEN`. The release workflow reads the production relay's
161derived public URL and Clerk publishable key from the same environment for downstream desktop, CLI,
162and hosted web builds.
163
164See:
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.