Human-in-the-loop setup. This guide configures Google Cloud and Tendnote environment variables so Tendnote can authorize Google provider capabilities. The Google Cloud Console steps cannot be completed by an agent or by code; an operator with access to the Google Cloud project must run them.
Tendnote authorizes Google capabilities through Better Auth's Google social
provider and linkSocial flow (ADR-0071, ADR-0090, ADR-0107). Better Auth owns OAuth
token custody, refresh, and encryption. Tendnote Provider Connections mirror only
non-secret product status, display identity, and authorized-scope metadata for
each capability, such as google/calendar, google/gmail, and google/contacts.
Do not build a separate OAuth subsystem or store provider tokens in a Tendnote table.
Open the Google Cloud Console and select the project that owns Tendnote's OAuth client.
Enable only the APIs that match the Tendnote capabilities you are testing:
| Capability | Google API | Needed for |
|---|---|---|
| Google Calendar | Google Calendar API | Read-only upcoming/recent event summaries and Calendar-derived follow-up suggestions. |
| Gmail | Gmail API | Creating and updating user-approved Gmail drafts. |
| Google Contacts | People API | Explicit preview of personal contacts before confirmed Tendnote profile/contact enrichment. |
Enable the People API only when testing Phase 2E Contacts import preview. Do not enable Directory API, Admin SDK, or organization-wide contact APIs.
- Go to APIs & Services -> OAuth consent screen.
- Set User type to External, unless every tester is inside your Google Workspace org and Internal is available.
- Set App name to Tendnote. Add a support email and developer contact email.
- Add your production domain under App domain / authorized domains once known. Localhost does not need an authorized domain.
- During private beta, keep the app in Testing and add each beta tester's Google account under Test users. Move to In production only when you are ready for broader access and any required Google verification.
Add exactly the scopes Tendnote uses, no more.
| Purpose | Scope | Notes |
|---|---|---|
| Sign-in identity for the connected Google account | openid, email, profile |
Better Auth requests identity scopes for the linked Google account and display identity. |
| Read event details for the primary calendar | https://www.googleapis.com/auth/calendar.events.readonly |
Used by Phase 2C. Narrower than full calendar read/write and enough for event title, time, attendees, status, selected location, and description excerpts. |
| Create and update Gmail drafts | https://www.googleapis.com/auth/gmail.compose |
Used by Phase 2D. Allows draft create/update; Tendnote still has no send path and does not read Gmail history. |
| Preview personal contacts | https://www.googleapis.com/auth/contacts.readonly |
Used by Phase 2E. Read-only personal contacts for explicit preview; no Directory, Admin, inferred contacts, or writes. |
Do not add these broader scopes for the current product:
https://www.googleapis.com/auth/calendarhttps://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/gmail.sendhttps://mail.google.com/https://www.googleapis.com/auth/contactshttps://www.googleapis.com/auth/contacts.other.readonlyhttps://www.googleapis.com/auth/directory.readonly- Directory API, Admin SDK, organization, or inferred-contact scopes
Calendar, Gmail, and Contacts are separate Tendnote Provider Connection capabilities. A user may connect one without the others, even though all use the same Google OAuth client and Better Auth account-link custody. Contacts must use the same linked Google account identity as the owner's other connected Google capabilities; Tendnote blocks mismatches until explicit multi-account semantics exist.
-
Go to APIs & Services -> Credentials -> Create credentials -> OAuth client ID.
-
Set Application type to Web application.
-
Add one authorized redirect URI per environment. Better Auth's Google callback path is
/api/auth/callback/googleunderBETTER_AUTH_URL:Environment Redirect URI Local http://localhost:3000/api/auth/callback/googleProduction https://<your-production-domain>/api/auth/callback/googlePreview, optional https://<your-preview-domain>/api/auth/callback/googleVercel preview deployments use generated domains. Add a stable preview alias or skip live Google smoke tests on previews rather than chasing per-deploy URLs.
-
Save and copy the Client ID and Client secret. The secret is shown once. Store it in your secret manager, never in the repo.
Google integrations reuse the same web-app env file as the other Better Auth
providers: apps/web/.env.local locally, and project env vars in hosting.
Set both Google values to enable Google capability connect buttons. Leave either
unset and Google connect flows stay disabled. Hosted Eve and local scheduled or
non-request Calendar reads also need the same pair in apps/agent/.env.local so
its lifecycle-only Better Auth instance can refresh encrypted account tokens;
Eve exposes no Google OAuth UI, callback, sign-in, or account-linking route
(ADR 0224).
# apps/web/.env.local (local)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Required in production/preview so the OAuth redirect matches the registered
# callback URL exactly. Locally it defaults to http://localhost:3000.
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET= # required in production; openssl rand -base64 32
# apps/agent/.env.local (same values for non-request Calendar lifecycle reads)
# Hosted Eve uses the production web origin instead of localhost below.
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
BETTER_AUTH_URL=http://localhost:3000 # hosted Eve: https://<production-domain>
BETTER_AUTH_SECRET= # exactly the same value as the web app
DATABASE_URL= # exactly the same database as the web app
REDIS_URL= # exactly the same Redis as the web appGOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETare server-only. Never prefix them withNEXT_PUBLIC_.- The redirect URI registered in Google must match
BETTER_AUTH_URL+/api/auth/callback/googlebyte-for-byte. - Never commit
.env*files or paste credentials into issues, logs, or PRs.
Run after the relevant code slices land, with GOOGLE_CLIENT_ID and
GOOGLE_CLIENT_SECRET set in both apps/web/.env.local and
apps/agent/.env.local, and pnpm dev running on :3000.
- Connect: From the account provider area, start the Google Calendar connect flow. Google's consent screen lists Calendar event-read plus identity scopes, not broad Calendar, Gmail, Contacts, or send scopes.
- Status: After returning to Tendnote, the Calendar Provider Connection
shows
connectedwith the connected Google account display identity and the authorized event-read scope. No tokens are visible in the UI. - Read: Ask Eve about an upcoming or recent meeting, or open the account preview. Minimized event summaries appear for a bounded window; no raw Google payload is shown or stored.
- Disconnect: Disconnect Calendar. The connection moves to
revoked, cached Calendar summaries are cleared, and Calendar reads are blocked until reconnect. If provider-side revocation could not complete, the UI explains Google Account permission cleanup. - Failure cleanup: Revoke Tendnote from Google Account permissions, then trigger a Calendar read. Eve and previews degrade gracefully; auth failure, not transient noise, is what surfaces as an error state.
- Connect: From the account provider area, start the Gmail connect flow.
Google's consent screen lists
gmail.composeplus identity scopes, not Gmail read, modify, send, history, mailbox, Contacts, or Calendar write scopes. - Status: After returning to Tendnote, the Gmail Provider Connection
shows
connectedwith the connected Google account display identity and the authorized compose scope. Calendar connection state remains independent. - Create draft: From an approved Tendnote message draft, confirm a
torecipient and subject, then create the Gmail draft. Gmail contains a draft; no email is sent. - Update draft: Revise the linked Tendnote draft and explicitly update Gmail. The existing Gmail draft is updated rather than a duplicate being created.
- Failure cleanup: Revoke Tendnote from Google Account permissions, then attempt a Gmail draft write. Tendnote reports a visible failure/retry state and does not background retry.
- Connect: From the account provider area, start the Google Contacts
connect flow. Google's consent screen lists
contacts.readonlyplus identity scopes, not Directory, Admin, inferred-contact, Contacts write, Gmail, or Calendar write scopes. - Same identity: If Calendar or Gmail is already connected, the Contacts row connects only for the same linked Google account identity. A different Google account is blocked with visible recovery copy.
- Preview entry: After returning to Tendnote, the Contacts Provider
Connection shows
connectedand the account row offers Preview latest contacts. Opening preview is explicit; no contacts are saved merely by connecting. - Live preview: Open
/account/contacts/import. The preview fetches only personal People API contacts usingnames,emailAddresses,phoneNumbers,birthdayswithREAD_SOURCE_TYPE_CONTACT; no Directory, Admin, inferred contacts, or raw People payload appears in the UI or logs. - Confirm safe: Confirm a safe recommendation. Tendnote updates only confirmed Tendnote-owned people/contact fields, stores a minimized provider contact reference, and records audit/provenance. No Gmail draft, send, Calendar write, or outbound behavior occurs.
- Resolve review: Resolve one conflicting/advisory candidate by choosing the target person and winning values. Conflicting Tendnote fields are not overwritten unless explicitly chosen, and skipped candidates create no durable relationship writes.
- Failure cleanup: Revoke Tendnote from Google Account permissions, then open Contacts import preview. Tendnote shows a visible preview failure and confirmation actions do not apply partial durable changes.
- Disconnect: Disconnect Contacts. Future Contacts preview reads are blocked, while confirmed Tendnote-owned people, emails, phones, and birthdays remain editable in Tendnote.
- Tail dev server logs during all flows. No OAuth tokens, raw Calendar payloads, raw Gmail API payloads, raw People API payloads, message history, labels, threads, mailbox data, or directory data appear in logs.
- Project env vars set the Google client id/secret and a
BETTER_AUTH_URLmatching the deployed origin. - The deployed origin's
/api/auth/callback/googleis registered as an authorized redirect URI in Google Cloud. - The beta tester's Google account is listed under Google OAuth Test users while the consent screen is in Testing mode.
- Calendar connect -> status -> read -> disconnect works end-to-end.
- Gmail connect -> status -> create draft -> update draft works end-to-end.
- Contacts connect -> status -> preview entry -> live preview -> safe confirmation -> conflict/advisory resolution -> disconnect works end-to-end, following the Phase 2E checklist above.
- Hosted logs and audit entries contain no OAuth tokens, raw provider payloads, Gmail history, mailbox labels, directory data, or sent-message activity.
- Normal
pnpm verifymust stay deterministic and must not depend on live Google credentials, OAuth redirects, Vercel, or provider network availability. - Live Google checks are explicit, opt-in smoke tests only.
- Treat this checklist as the human acceptance gate for operator-facing Google setup: APIs enabled, consent screen scopes correct, callback URLs correct, env vars present, local flow works, hosted flow works, and logs remain secret-safe.