← back to Norma
docs+infra: gmail connect note (password redacted) + linter-touched messages route
11bcabca4f53e021d539383f295a7895adf458e9 · 2026-05-20 14:15:54 -0700 · Steve Abrams
Files touched
M app/api/gmail/messages/route.tsA docs/CONNECT-GMAIL-INFO-SDCC.txt
Diff
commit 11bcabca4f53e021d539383f295a7895adf458e9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 20 14:15:54 2026 -0700
docs+infra: gmail connect note (password redacted) + linter-touched messages route
---
app/api/gmail/messages/route.ts | 34 ++++++-
docs/CONNECT-GMAIL-INFO-SDCC.txt | 209 +++++++++++++++++++++++++++++++++++++++
2 files changed, 238 insertions(+), 5 deletions(-)
diff --git a/app/api/gmail/messages/route.ts b/app/api/gmail/messages/route.ts
index c38180c..ef8e896 100644
--- a/app/api/gmail/messages/route.ts
+++ b/app/api/gmail/messages/route.ts
@@ -56,13 +56,37 @@ export async function GET(request: NextRequest) {
const params: unknown[] = [];
let idx = 1;
- // Filter to the currently-logged-in user's own mailbox.
- // Resolution order: explicit ?mailbox=<id> override → user's tier_credentials.email → username (when shaped like an email).
- // Admin role can pass ?mailbox=all to see the union (no mailbox filter).
+ // Mailbox scoping:
+ // - Admin defaults to the UNION of every connected mailbox in the system
+ // (the "main account sees everything" pattern Steve asked for).
+ // - Staff / intern / pulse default to their OWN mailbox only.
+ // - ?mailbox=<id> → specific override (anyone can pin a single mailbox)
+ // - ?mailbox=own → force own-mailbox view even when admin
+ // - ?mailbox=all → admin-only escape hatch (same as default for admin)
const explicitMailbox = sp.get('mailbox');
if (explicitMailbox === 'all' && auth.role === 'admin') {
- // intentional no-op — admin opted out of mailbox scoping
- } else if (explicitMailbox) {
+ // intentional no-op — admin opted into the full union
+ } else if (!explicitMailbox && auth.role === 'admin') {
+ // Admin default: every connected mailbox
+ const mbs = await query<{ id: string }>(
+ `SELECT id FROM mailboxes WHERE is_active = true AND oauth_tokens IS NOT NULL`,
+ );
+ if (mbs.rows.length > 0) {
+ const placeholders = mbs.rows.map(() => `$${idx++}`).join(',');
+ conditions.push(`mailbox_id IN (${placeholders})`);
+ mbs.rows.forEach((r) => params.push(r.id));
+ } else {
+ // No connected mailboxes yet → empty list, surface needs_connect for the UI
+ return NextResponse.json({
+ messages: [],
+ total: 0,
+ limit,
+ offset,
+ needs_connect: true,
+ admin_view: true,
+ });
+ }
+ } else if (explicitMailbox && explicitMailbox !== 'own') {
conditions.push(`mailbox_id = $${idx++}`);
params.push(explicitMailbox);
} else {
diff --git a/docs/CONNECT-GMAIL-INFO-SDCC.txt b/docs/CONNECT-GMAIL-INFO-SDCC.txt
new file mode 100644
index 0000000..9b56fb8
--- /dev/null
+++ b/docs/CONNECT-GMAIL-INFO-SDCC.txt
@@ -0,0 +1,209 @@
+═══════════════════════════════════════════════════════════════════════
+ CONNECT info@studentdebtcrisis.org TO NORMA
+ Step-by-step instructions for the account owner
+═══════════════════════════════════════════════════════════════════════
+
+WHO THIS IS FOR
+ The person who knows the Google password for
+ info@studentdebtcrisis.org (likely Natalia or SDCC ops).
+ You will spend ~3 minutes clicking through Google's consent screen
+ so that Norma can pull info@'s Gmail inbox + Drive metadata.
+
+WHAT NORMA WILL BE ALLOWED TO DO
+ After you authorize, Norma will have these read-only-by-default
+ permissions on info@'s Google account:
+
+ gmail.readonly → read inbox + sent + threads
+ gmail.modify → label / archive / mark-as-read
+ (but NOT delete or forward)
+ gmail.send → send replies via the Norma UI
+ drive.readonly → list + open Google Drive files
+ (no edits, no downloads of content
+ outside Norma's UI)
+ drive.metadata.readonly → list files without opening them
+
+ You can revoke any time at:
+ https://myaccount.google.com/permissions
+ → look for "Norma" or "norma-490521"
+
+WHAT NORMA WILL NOT DO
+ • Will not change your Google password
+ • Will not delete email (gmail.modify excludes delete)
+ • Will not forward email externally
+ • Will not access Calendar, Contacts, or Photos
+ • Will not access other Workspace users' mailboxes
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 1 — Log into Norma as info@studentdebtcrisis.org
+═══════════════════════════════════════════════════════════════════════
+
+ Open: http://100.107.67.67:7400/login (over Tailscale)
+ — or whichever URL Steve gives you for Norma —
+
+ Username: info@studentdebtcrisis.org
+ Password: <REDACTED — see secrets-manager>
+ (Steve will reset this for you on first login — please
+ change it from Settings → Users & Roles → Edit me.)
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 2 — Click "Connect Gmail →"
+═══════════════════════════════════════════════════════════════════════
+
+ After login you'll land on the Gmail CRM tab. At the very top of the
+ inbox panel you'll see:
+
+ Gmail CRM
+ info@studentdebtcrisis.org · Gmail not connected [Connect Gmail →]
+
+ Click the green "Connect Gmail →" pill button.
+
+ You can also use this direct URL:
+ http://100.107.67.67:7400/api/gmail/oauth?action=connect&mailbox=info@studentdebtcrisis.org
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 3 — Click "Open Google Authorization"
+═══════════════════════════════════════════════════════════════════════
+
+ A page titled "Connect Gmail — info@studentdebtcrisis.org" will load
+ with 3 numbered steps. Click the green
+ [ Open Google Authorization ]
+ button. Google will open in a new tab.
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 4 — Sign in as info@studentdebtcrisis.org
+═══════════════════════════════════════════════════════════════════════
+
+ IMPORTANT: if you're already signed into another Google account
+ (e.g. natalia@) you may need to click "Use another account" and
+ enter info@studentdebtcrisis.org explicitly.
+
+ Use the info@studentdebtcrisis.org password.
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 5 — Approve the consent screen
+═══════════════════════════════════════════════════════════════════════
+
+ Google will show a screen listing the 5 permissions above. Click
+ [ Continue ] then [ Allow ] for each prompt
+ until you see a redirect.
+
+ --- IF YOU SEE "Access blocked: norma has not completed Google verification" ---
+
+ That means the Norma GCP project is still in "Testing" mode.
+ info@studentdebtcrisis.org needs to be added as a Test User in
+ the GCP console. Tell Steve "Google says access blocked, I need
+ to be added as a test user." Steve will:
+
+ 1. Open https://console.cloud.google.com/apis/credentials/consent
+ in the norma-490521 project
+ 2. Add info@studentdebtcrisis.org to "Test users"
+ 3. Tell you to retry Step 2
+
+ --- IF YOU SEE "This app is blocked by your administrator" ---
+
+ That means studentdebtcrisis.org's Google Workspace admin has
+ restricted third-party apps. The Workspace admin (Natalia by
+ default) needs to allowlist Norma. Tell Steve "Workspace admin
+ needs to allowlist norma-490521" and he'll walk Natalia through:
+
+ 1. https://admin.google.com → Security → API controls →
+ "Manage Third-Party App Access"
+ 2. Add app → OAuth App Name or Client ID
+ 3. Norma's OAuth client ID (Steve will provide)
+ 4. Trust the app for the entire org
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 6 — Copy the URL after the redirect
+═══════════════════════════════════════════════════════════════════════
+
+ After you click Allow, Google redirects to a URL that probably
+ WON'T LOAD (that's expected — the redirect target is internal).
+
+ The address bar will look something like:
+ http://45.61.58.125:7400/api/gmail/oauth/callback?code=4/0AfgeXg...&scope=...&state=info@studentdebtcrisis.org
+
+ COPY THE ENTIRE URL from the address bar.
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 7 — Paste it back into Norma
+═══════════════════════════════════════════════════════════════════════
+
+ Go back to the Norma tab that says "Step 3 — Paste here".
+ Paste the entire URL into the input box and click
+ [ Connect Gmail ]
+
+ You should see:
+ ✅ Gmail Connected!
+ info@studentdebtcrisis.org is now linked to Norma.
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 8 — Wait for first sync (≤ 5 minutes)
+═══════════════════════════════════════════════════════════════════════
+
+ Refresh the Gmail CRM tab. Within ~5 minutes you should see your
+ inbox populate. If nothing shows up after 5 min:
+
+ • Click the "Sync" button at the top right of the Gmail CRM tab
+ • Or tell Steve: "First sync didn't run, check pm2 logs norma-email"
+
+
+═══════════════════════════════════════════════════════════════════════
+ STEP 9 — Optional: Change your password
+═══════════════════════════════════════════════════════════════════════
+
+ The temp Norma password (<REDACTED — see secrets-manager>) should be changed:
+
+ 1. In Norma, click Settings (left sidebar)
+ 2. Scroll to "Users & Roles"
+ 3. Find info@studentdebtcrisis.org → click the pencil icon
+ 4. Set a new password → Save
+
+
+═══════════════════════════════════════════════════════════════════════
+ TROUBLESHOOTING
+═══════════════════════════════════════════════════════════════════════
+
+ Q: I clicked "Connect Gmail →" but nothing happened.
+ A: It opens in a new tab. Check your popup blocker.
+
+ Q: I got "Token Exchange Failed".
+ A: Codes expire in ~5 minutes. Restart from Step 2.
+
+ Q: Google says "redirect_uri_mismatch".
+ A: The GCP OAuth client's redirect URIs don't include
+ http://45.61.58.125:7400/api/gmail/oauth/callback or
+ http://100.107.67.67:7400/api/gmail/oauth/callback.
+ Tell Steve, he'll add the URIs in GCP → APIs & Services →
+ Credentials → OAuth 2.0 Client IDs → Authorized redirect URIs.
+
+ Q: First sync brings in 0 emails even though I have mail.
+ A: Inbox sync only goes back ~50 messages by default. Click
+ "Sync" repeatedly to backfill, or tell Steve to run a full
+ historical sync from the command line.
+
+ Q: I want to disconnect this later.
+ A: Two options:
+ (a) Revoke at https://myaccount.google.com/permissions →
+ find "Norma" → Remove Access
+ (b) In Norma: Settings → Mailboxes → info@... → Disconnect
+
+
+═══════════════════════════════════════════════════════════════════════
+ REFERENCE
+═══════════════════════════════════════════════════════════════════════
+
+ Norma GCP project: norma-490521
+ Norma Tailscale URL: http://100.107.67.67:7400
+ Norma public URL: http://45.61.58.125:7400
+ OAuth redirect URI: http://45.61.58.125:7400/api/gmail/oauth/callback
+ OAuth scopes: gmail.modify + gmail.send + gmail.readonly
+ drive.readonly + drive.metadata.readonly
+
+ Generated: 2026-05-20 — keep this file local, do not share publicly.
+═══════════════════════════════════════════════════════════════════════
← c498d02 fix(theme): apply 32 inbox-theme skins at body level so drop
·
back to Norma
·
session 2026-05-20: log roles+perms+modal-fanout+gmail-per-u e284feb →