# Buddy CRM — Environment Variables

Last updated: 2026-05-06

All environment variables live in **Netlify → Site configuration → Environment variables**. They are injected at runtime into Netlify Functions only — they are never visible to the browser.

---

## Variable Inventory

| Variable | Service | Required? | Purpose | Where to get a new one |
|---|---|---|---|---|
| `SUPABASE_URL` | Supabase | **Critical** | Database endpoint URL | Supabase Dashboard → Settings → API → Project URL |
| `SUPABASE_SERVICE_KEY` | Supabase | **Critical** | Server-side admin key (bypasses RLS) | Supabase Dashboard → Settings → API → `service_role` key |
| `ALLOWED_ORIGINS` | Netlify | **Critical** | Comma-separated CORS allowlist (echoes the matching request origin). The legacy single-value `ALLOWED_ORIGIN` is still honoured as a fallback. | Set to your CRM Netlify URL plus the original Buddy URL if both apps share this Supabase project |
| `GEMINI_API_KEY` | Google Cloud | Required for AI | Sales Buddy + photo→lead OCR | Google Cloud Console → APIs & Services → Credentials → API Keys |
| `MAILCHIMP_API_KEY` | Mailchimp | Required for Audience Buddy | Campaign + audience access | Mailchimp → Account → Extras → API keys → Create A Key |
| `MAILCHIMP_SERVER_PREFIX` | Mailchimp | Required for Audience Buddy | API server prefix (e.g. `us1`, `us2`) | Found at the end of your Mailchimp API key (after the dash: `key-us1` → `us1`) |
| `MS_CLIENT_SECRET` | Microsoft Azure | Required for Outlook sync | Server-side OAuth client secret used by `auth-outlook.js` to exchange auth codes for refresh tokens | Azure App Registration → Certificates & secrets → New client secret. Coordinate with Max / Platform team |
| `EMAIL_TOKEN_KEY` | Netlify | Required for Outlook sync | 32-byte hex key for AES-256-GCM encryption of stored Microsoft refresh tokens | Generate with `openssl rand -hex 32`. **See rotation warning below** |
| `BUDDY_SERVICE_KEY` | Netlify | Required for service / MCP integrations | Bearer token that bypasses JWT verification and is treated as `mcp-service@nownz.co.nz` | Generate any secure random string |
| `GMAIL_USER` | Gmail | Required for skill-queue notifications | Sender address | Use the dedicated notifications Gmail account |
| `GMAIL_APP_PASSWORD` | Gmail | Required for skill-queue notifications | App Password for SMTP send | Gmail → Security → 2-Step Verification → App passwords → Generate |
| `AZURE_TENANT_ID` | Microsoft Azure | Optional (hardcoded fallback) | Entra ID tenant for SSO | Please see Max / Platform team |
| `AZURE_CLIENT_ID` | Microsoft Azure | Optional (hardcoded fallback) | App registration client ID for SSO | Please see Max / Platform team |
| `ALLOWED_ORIGIN` | Netlify | Optional (legacy single-value form) | Single CORS origin if you don't use the plural `ALLOWED_ORIGINS` | Set to your production URL |

---

## Criticality Levels

### App won't function without these
- `SUPABASE_URL` — every API call goes to Supabase
- `SUPABASE_SERVICE_KEY` — required to authenticate with Supabase
- `ALLOWED_ORIGINS` (or legacy `ALLOWED_ORIGIN`) — without it, CORS falls back to `'*'` which a strict browser may still reject for credentialed requests

### Specific features break without these
- `GEMINI_API_KEY` — Sales Buddy and photo→lead fail (`gemini.js` returns 500). The rest of the app works
- `MAILCHIMP_API_KEY` + `MAILCHIMP_SERVER_PREFIX` — Audience Buddy fails. The rest of the app works
- `MS_CLIENT_SECRET` — Outlook OAuth handshake fails. Existing stored tokens still refresh (uses the public-client refresh path), but no new BDM can connect
- `EMAIL_TOKEN_KEY` — every encrypted refresh token in `user_graph_tokens` becomes unreadable. **Email + calendar sync stops cluster-wide.** Don't rotate without a re-encryption migration
- `GMAIL_USER` + `GMAIL_APP_PASSWORD` — skill-queue notifications fail silently. Tasks still execute
- `BUDDY_SERVICE_KEY` — service / MCP API calls return 401. The web app is unaffected

### Have hardcoded fallbacks in code
- `AZURE_TENANT_ID` — falls back to `a792bf91-f9b4-47b5-b692-a54227b3145b` ([netlify/functions/supabase-client.js:14](../netlify/functions/supabase-client.js#L14))
- `AZURE_CLIENT_ID` — falls back to `3530576b-7189-4e35-8070-6d23c8f49fc0` ([netlify/functions/supabase-client.js:15](../netlify/functions/supabase-client.js#L15))

---

## How to Update Environment Variables

1. https://app.netlify.com → select the site
2. Site configuration → Environment variables
3. Click the variable → Edit → Save
4. **Trigger a redeploy** — env var changes only take effect on the next deploy
   - Deploys → Trigger deploy → Deploy site (or push any commit to `main`)

---

## Rotation Schedule & Cost

| Variable | When to rotate | Cost of rotation |
|---|---|---|
| `SUPABASE_SERVICE_KEY` | If compromised | Supabase doesn't allow rotation in-place — would require creating a new project. Contact Supabase support if truly compromised |
| `GEMINI_API_KEY` | If compromised or on Google's recommendation | Cheap — generate new key, update env, redeploy |
| `MAILCHIMP_API_KEY` | If compromised | Cheap — disable old, create new, update env |
| `MS_CLIENT_SECRET` | Per Azure expiry policy or if compromised | Cheap, but coordinate with Max / Platform team |
| **`EMAIL_TOKEN_KEY`** | **Effectively never** | Rotating this invalidates every encrypted refresh token in `user_graph_tokens`. Every BDM would need to re-OAuth via `auth-outlook.js`. If you must rotate, write a migration that decrypts with the old key and re-encrypts with the new one |
| `GMAIL_APP_PASSWORD` | If compromised | Cheap — Gmail Security → revoke old, generate new |
| `BUDDY_SERVICE_KEY` | If compromised | Cheap — generate new random string, update env vars *and* the consumers (`.mcp.json` in the original Buddy repo, any other service callers) |
| `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` | Never (unless re-registering the app) | Not secrets, but rotating them changes auth wiring |

---

## What Happens When a Key Goes Bad

| Variable | Symptom | Blast radius |
|---|---|---|
| `SUPABASE_SERVICE_KEY` revoked | All API calls return 500 | **Total outage** — every data endpoint fails |
| Azure App Registration deleted | SSO fails for everyone | **Total outage** — escalate to Max / Platform team |
| `MS_CLIENT_SECRET` invalid | Outlook OAuth handshake fails for new users | Existing email/calendar sync may still work if refresh tokens still validate |
| `EMAIL_TOKEN_KEY` lost or rotated incorrectly | `user_graph_tokens` rows decrypt to garbage | Email + calendar sync stops; every BDM must re-OAuth |
| `GEMINI_API_KEY` revoked | AI features error | Sales Buddy + photo OCR unavailable; rest of app works |
| `MAILCHIMP_API_KEY` revoked | Mailchimp endpoints return 401 | Audience Buddy fails; rest of app works |
| `GMAIL_APP_PASSWORD` revoked | `send-notification.js` warns in logs | Skill-queue tasks still execute, but no email arrives |
| `BUDDY_SERVICE_KEY` removed/changed | Service callers return 401 | MCP tools / external integrations stop; web app unaffected |

---

## Security Rules

1. **Never commit env vars to the repo.** Treat the `.env`-style values as secrets. Netlify's secret-scan is configured (in [netlify.toml](../netlify.toml)) to omit `MAILCHIMP_SERVER_PREFIX` and `BUDDY_SERVICE_KEY` from scanning, since those aren't strictly secrets but contain similar patterns.
2. **Never expose `SUPABASE_SERVICE_KEY` to the browser.** It grants full admin access to the database. It is only used in Netlify Functions (server-side). The browser never talks to Supabase directly.
3. **`SUPABASE_SERVICE_KEY` bypasses all RLS.** All access control is enforced in application code.
4. **All credentials are backed up** in the company password manager — please contact Marketing for the Account Access and Passwords sheet.
