Self-hosting
OAuth setup.
Register one OAuth app per provider, add both redirect URIs, drop the credentials in env. The app picks the right ones by origin.
Redirect URI pattern: {origin}/api/oauth/callback/{provider} where provider is google, slack, or github. Register the localhost URI and your production URI on each app.
Redirect URIs to register
http://localhost:3000/api/oauth/callback/google https://your-domain.com/api/oauth/callback/google (same pattern for slack and github)
Google (Gmail + Calendar)
- 1Enable APIsconsole.cloud.google.com → APIs & Services → Library → enable Gmail API and Google Calendar API.
- 2Consent screenExternal · add yourself under Test users · add the Gmail (readonly/send/modify) and Calendar scopes. In Testing mode only listed test users can connect, and refresh tokens expire weekly. Publishing with Gmail scopes requires Google's verification process.
- 3Create a Web clientCredentials → Create OAuth client → Web application → add both origins and both redirect URIs → copy ID + secret into
GOOGLE_CLIENT_ID/SECRET.
One Google login grants both Gmail and Calendar, Xome requests the combined scopes in a single connection.
GitHub
- 1Create OAuth app(s)github.com/settings/developers. GitHub allows one callback URL per app, so create two: a dev app (localhost callback) and a prod app (your domain's callback).
- 2Set envDev app →
GITHUB_CLIENT_ID/SECRET; prod app →GITHUB_CLIENT_ID_PROD/SECRET_PROD. The app auto-selects by request origin.
Slack
- 1Create the appapi.slack.com/apps → OAuth & Permissions. Add User Token Scopes: chat:write, channels:read/history, groups:read/history, im:read/history, mpim:read/history, search:read, users:read.
- 2Redirect URLsSlack requires HTTPS, it rejects http://localhost. Add your production URL, and for local dev tunnel with
ngrok http 3000, add the ngrok URL as a redirect, and open the app through the ngrok origin when connecting. - 3Set env
SLACK_CLIENT_ID/SECRET.
Until configured
- Unconfigured providers return a clear “not configured” message from the Connect button, nothing else breaks.
- Tokens live in users' browsers; your server only relays the code exchange and refreshes, statelessly.