One-line summary: Launch's Inbox Assistant runs in the background on your Gmail every hour: it sorts new mail into six buckets, archives anything that isn't urgent, and writes draft replies in your voice for the messages that need answers. This SOP covers how to set it up, what each control does, where to look when something seems off, and what a tech-light Launch developer needs to know to make changes.
Inbox Assistant
Live as of 2026-05-18. Owner: xVICTORx (code) and Monica (operations). Escalation: xVICTORx.
Part 1: For team members
What the assistant actually does
Every hour, behind the scenes, the assistant reads your inbox and acts on each new thread. It does three things:
- Sorts new mail into one of six labels (described below).
- Archives anything that doesn't need a reply (labels 2 through 6 leave your inbox).
- Drafts a reply in your voice for every thread that needs one. Drafts sit in your inbox waiting for you to review, edit, and send. The assistant never sends an email on your behalf. Ever.
What about "awaiting reply" / nudges? Removed 2026-05-23. The assistant used to tag your sent mail and draft "just following up" nudges after a few days. It kept misfiring — drafting nudges on threads you'd already answered yourself. Gmail's built-in "received N days ago, reply?" reminder still surfaces stale outbound threads, and follow-ups are something you'll do on your own terms.
The six labels
| Label | Meaning | Stays in inbox? |
|---|---|---|
| 1-to-respond | A human sent you something that needs your reply | Yes |
| 2-FYI | Informational, no reply needed | Archived |
| 3-comment | Google Docs / Sheets comment notifications | Archived |
| 4-notification | System notifications (Gusto, QuickBooks, GitHub, Stripe, payroll alerts, password resets, etc.) | Archived |
| 5-meeting-update | Calendly or Google Calendar change emails | Archived |
| 6-marketing | Newsletters, promotional, sales outreach | Archived |
| AI-Draft | An idempotency marker, used so the assistant doesn't draft the same thread twice | (state only) |
Steady-state goal: only label 1 should be visible in your inbox. Everything else archives automatically.
⚠️ Do not rename, delete, or modify these seven labels in Gmail. The assistant matches them by exact name (
1-to-respond,2-FYI,3-comment,4-notification,5-meeting-update,6-marketing,AI-Draft). If you rename one, the next run will create a NEW label alongside it (e.g., a fresh1-to-respondnext to whatever you renamed it to), leaving you with duplicates and broken classification. You're welcome to change the color of a label in Gmail — that's fine. Color is a Gmail-only setting that the assistant doesn't touch. But the name and the label itself must stay exactly as provisioned.Want a different organization scheme on top? Use Gmail filters and your own labels. Filters run before the assistant sees a thread, and the assistant only touches the seven labels above — anything else you add is yours to manage and stays intact.
Old
7-awaiting-replylabel: if you had it provisioned before 2026-05-23, the label still exists in your Gmail with whatever threads were tagged at the time. The assistant no longer adds or removes it. Leave it, delete it, or repurpose it — whichever you prefer.
First-time setup
Go to inbox.launchindustries.biz and sign in with your @launchindustries.biz Google account. The dashboard auto-creates a config row for you with sensible defaults.
Then in order:
- Customize your voice and footer. Open the Instructions card on the right. Your name comes from your Launch team profile (HR-managed). Your scheduling link pulls from your team profile too. Edit the signature override and the instructions text if you want different defaults. Save settings.
- Click Connect Gmail. A new window pops up, takes you through Google's permission screen, and lands on a "Gmail connected" success page. Close that window and return to the dashboard.
- Click Provision required labels. This creates the seven workflow labels inside your Gmail. If they already exist, the button reuses them. (Note: if you've renamed any of these labels in Gmail, the button asks for confirmation before re-running, because it would create duplicates.)
- Refresh the dashboard. The Gmail Connection status should now show "Connected."
- Flip Assistant Enabled to ON and click Save.
- (Optional) Toggle "Dry Run by Default" off when you want real changes to happen. Leaving it on is useful for tuning the Instructions field without modifying your inbox.
The next scheduled run will start sorting your mail within an hour. Or click Run live now to trigger one immediately.
The dashboard fields, explained
- Assistant Enabled — Master switch. When off, scheduled runs are skipped. Manual buttons still work.
- Dry Run by Default — When on, scheduled runs only simulate. No labels written, no drafts created, no archives. Use this while you're tuning your Instructions.
- Run frequency in minutes — The minimum time between scheduled runs. Default 60.
- Instructions — Free text appended to the AI's classification and drafting prompts as "Additional operating instructions." Use it for tone, classification nuance, and operational preferences. Keep it under 2,000 characters; longer prompts confuse the model.
- Signature override — Optional. Replaces the default
"Best, {Your Name}"block on every draft. Leave blank to use the default with your full footer (title, email, phone, scheduling link). - Scheduling link — Your Calendly or other URL. Stored on your Launch team profile, so it propagates to every other Launch app at the same time.
- Run dry pass — Simulates a full re-classification of every thread in your inbox. Reports what WOULD happen but writes nothing. Use this to preview before committing.
- Run live now — Re-classifies every thread in your inbox in one pass, including threads that already have a workflow label. Old labels are cleanly replaced by the new classification (no stray duplicates). Use this when you want immediate results, want to fix historical mislabels, or after a classifier improvement. The hourly cron does NOT do this full re-classification (it stays cheap by only touching new mail). Run live now is the manual button that cleans up everything.
What it will NEVER do
These are hard-coded:
- Send an email.
- Mark a message read.
- Delete a message or move it to Trash or Spam.
- Touch labels outside the six listed above (plus AI-Draft).
- Modify a thread that already has your own unsent draft (those stay in inbox, classified as 1-to-respond).
- Run when "Assistant Enabled" is off.
What YOU should never do
- Don't rename the workflow labels in Gmail.
1-to-respond,2-FYI,3-comment,4-notification,5-meeting-update,6-marketing, andAI-Draftare matched by exact name. Renaming any of them breaks the assistant and creates duplicate labels on the next run. - Don't delete the workflow labels in Gmail. If you delete one, click Provision required labels in the dashboard and the assistant will re-create it.
- Don't manually apply or remove these labels. Let the assistant manage them. If you think a thread is misclassified, just open it and re-tag manually if you must — but the next run will re-classify and overwrite your change (unless your custom Instructions say otherwise).
Common situations
"My inbox still has lots of mail after running." Most of those threads are probably 1-to-respond, which stays in inbox by design. They're the ones that genuinely need your attention. The only way to get to zero inbox is to actually reply.
"Notifications from Gusto/Intuit/etc are sitting in 1-to-respond instead of 4-notification." The classifier was tightened on 2026-05-18 to catch these automated senders. Click Run live now once to re-classify the entire inbox. The button now processes every thread in inbox per click (up to ~500), so one pass should clean up historical mislabels.
"My drafts have the wrong sign-off." Check the Signature override field on the dashboard. If blank, the assistant uses your team profile name. If it has a value, that's what gets used.
"I clicked Run live now and nothing happened."
Check the Runtime Log at the bottom of the dashboard. If the most recent run shows dry_run = true, you actually clicked Run dry pass. If it shows dry_run = false but no labels/archives/drafts, the assistant didn't find any new threads to process this pass.
"My scheduling link in drafts is wrong." Update the Scheduling link field on the dashboard and click Save settings. It writes to your Launch team profile, so the new value will also show up everywhere else in Launch.
"I need help." Ping xVICTORx on Slack, or message Monica.
Part 2: For tech-light Launch developers
If you're maintaining this system or making changes with Claude Code, here's the map.
Where the code lives
| Piece | Location |
|---|---|
| Dashboard frontend | ~/Documents/Claude/Projects/portal-community-admin/src/pages/InboxAssistantPage.tsx |
| Layout / header | ~/Documents/Claude/Projects/portal-community-admin/src/components/AdminLayout.tsx (header reads "{First}'s inbox operations") |
| Runner (triage logic) | ~/Documents/Claude/Projects/portal-community-admin/supabase/functions/gmail-inbox-assistant/index.ts |
| Admin API (dashboard actions) | ~/Documents/Claude/Projects/portal-community-admin/supabase/functions/inbox-assistant-admin/index.ts |
| OAuth callback | ~/Documents/Claude/Projects/portal-community-admin/supabase/functions/gmail-inbox-auth/index.ts |
| GitHub repo | github.com/launchindustries/CRM (note: deployments are NOT triggered by git push, see below) |
Where it runs
- Vercel project:
crm(under thelaunchindustriesteam) - Domain alias:
inbox.launchindustries.biz(alsocrm.launchindustries.biz,client-admin.launchindustries.biz) - Supabase project:
egnrizeybeihfavzwxfe(Launch Database) - OpenAI model:
gpt-5-minivia the Responses API (OPENAI_MODELenv var override)
Supabase tables
| Table | Purpose | Key columns |
|---|---|---|
public.inbox_assistant_configs | One row per user | assistant_email (PK), enabled, run_frequency_minutes, dry_run_default, instructions, signature, label_ids (JSONB), gmail_connected_email, last_run_at, last_status, last_error |
public.inbox_assistant_runs | Run history | assistant_email, started_at, finished_at, dry_run, trigger_source, status, summary_json, error_text |
public.inbox_assistant_gmail_tokens | OAuth tokens, one row per user | email (PK), access_token, refresh_token, token_expiry |
public.launch_team_members | Canonical for name, title, phone | Joined via email (HR-managed) |
public.people | Canonical for scheduling_url (anyone, not just team) | Joined via launch_team_members.person_id → people.id |
Scheduler
A Supabase pg_cron job named inbox-assistant-hourly fires every 30 minutes (*/30 * * * *). It POSTs to the runner edge function with no assistantEmail, which causes the runner to iterate over every config row with enabled = true. The runner self-gates per user using run_frequency_minutes, so a 30-minute cron with a 60-minute interval gives you one actual run per user per hour.
To inspect or modify the schedule:
SELECT jobid, jobname, schedule, active FROM cron.job WHERE jobname = 'inbox-assistant-hourly';
SELECT * FROM cron.job_run_details WHERE jobname = 'inbox-assistant-hourly' ORDER BY start_time DESC LIMIT 10;Deploying changes
The repo has no CI/CD wired up. All deploys are manual CLI uploads. Pushing to GitHub does NOT trigger a deploy.
Frontend changes (anything in src/):
cd ~/Documents/Claude/Projects/portal-community-admin
vercel --prod
# Note: aliasing does NOT auto-promote on this project. Re-alias manually
# using the Vercel API or the dashboard.Edge function changes (anything in supabase/functions/<name>/):
cd ~/Documents/Claude/Projects/portal-community-admin
supabase functions deploy <function-name> --no-verify-jwt --project-ref egnrizeybeihfavzwxfeThe three functions to know:
gmail-inbox-assistant— the triage runner. Deploy this when changing classifier logic, voice rules, label flows, or pg_cron behavior.inbox-assistant-admin— the dashboard's API endpoint. Deploy this when adding new dashboard actions (Run now, Provision labels, Save scheduling link, etc.).gmail-inbox-auth— the OAuth callback. Deploy this when changing the connect flow or success page.
All three must be deployed with --no-verify-jwt so pg_cron and the OAuth redirect can hit them without a Supabase session.
Environment variables (set in the Supabase Function dashboard)
| Var | What it's for |
|---|---|
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET (or INBOX_ASSISTANT_GOOGLE_CLIENT_ID / INBOX_ASSISTANT_GOOGLE_CLIENT_SECRET) | The Google OAuth app used to connect each user's Gmail |
INBOX_ASSISTANT_GOOGLE_REDIRECT_URI | The callback URL Google redirects to (must match the OAuth app's registered redirect URIs in Google Cloud Console) |
INBOX_ASSISTANT_SECRET | A shared secret. If set, all calls from pg_cron and the auth callback must include it. Currently unset, which means open access from pg_cron. |
OPENAI_API_KEY | OpenAI Responses API key used for classification + drafting |
OPENAI_MODEL | Defaults to gpt-5-mini. Override if you want a different model. |
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY | Auto-provided by Supabase to all edge functions |
Auth & access control (important)
- Vercel Deployment Protection is OFF on the
crmproject on purpose. Supabase auth is the only access gate. If you turn Vercel SSO back on, non-team-member coworkers will hit a Vercel wall before they can reach the Supabase login. - The admin function enforces
@launchindustries.bizdomain. Anyone with a Supabase session for that domain can hit it; the function then scopes every operation to the signed-in email. - Gmail OAuth scope is constrained to
readonly,modify,send,compose. The send scope is required by Gmail for creating drafts in a thread, even though we never actually send.
Working with Claude Code (tech-light vibe coding)
When you open a Claude Code session in this repo, here's what works:
- The Supabase MCP is connected. Claude can run SQL queries (
mcp__claude_ai_Supabase__execute_sql) and deploy edge functions (mcp__claude_ai_Supabase__deploy_edge_function). You can ask things like "show me the last 10 inbox assistant runs for Monica" and it will write the SQL. - Local memory about this project lives at
~/.claude/projects/-Users-monicacolgan/memory/project_inbox_assistant_remote_build.md. Read that first when starting a session so Claude has full architectural context. - Phrase to bootstrap a session: "I'm working on the Inbox Assistant project. Read the project memory at
project_inbox_assistant_remote_build.mdfor context. The code lives at~/Documents/Claude/Projects/portal-community-admin/." - When making changes, ask Claude to do edits in the local files, deploy via Supabase CLI / Vercel CLI, then re-alias the Vercel deployment manually. Claude knows how.
- Don't forget the aliasing step. After
vercel --prod, a fresh deployment URL is created butinbox.launchindustries.bizdoes NOT auto-promote. Ask Claude to re-alias it.
Common gotchas
| Gotcha | What to know |
|---|---|
| "I pushed to GitHub but nothing changed in prod" | This repo has no CI. You have to manually vercel --prod and supabase functions deploy <name> for changes to go live. |
"I deployed and inbox.launchindustries.biz still shows the old version" | The Vercel alias did not auto-promote. Re-alias manually via the API or the Vercel dashboard. |
"The OAuth flow fails with state mismatch" | INBOX_ASSISTANT_SECRET is set on one function but not another, or different values. Both inbox-assistant-admin and gmail-inbox-auth need to read the same value. |
| "Drafts have a blank body" | The OpenAI Responses API sometimes returns content in data.output[].content[].text instead of data.output_text. The runner has a fallback (extractResponsesText), but if the prompt is too long (over ~2,000 chars of custom instructions) the model exhausts its reasoning budget. Keep instructions short. |
| "Duplicate drafts on one thread" | Was a real bug fixed in runner v21 on 2026-05-17. The runner now detects existing DRAFT messages and applies the AI-Draft label without re-drafting. If you still see duplicates, escalate. |
| "Notifications are being mis-routed to 1-to-respond" | Tightened on 2026-05-18. If it still happens for a specific sender domain, add the domain to the autoDomains array in classifyThread's isAutomatedSender helper. |
"Coworker can't open inbox.launchindustries.biz" | Vercel Deployment Protection may have been re-enabled. Disable it on the crm project via the Vercel API or dashboard. Supabase auth is the access gate. |
Where to look when something breaks
| Where | What it shows |
|---|---|
| The dashboard's "Runtime Log" table | Last 20 runs with status, mode, source, and a one-line summary |
public.inbox_assistant_runs table | Full run history including the JSON summary blob and error text |
| Supabase Dashboard → Functions → Logs | Real-time edge function logs (HTTP method, status, execution time, errors) |
cron.job_run_details table | pg_cron firing history |
Vercel Dashboard → crm project → Logs | Frontend errors (rare; the dashboard is mostly a static SPA) |
Memory & SOPs
- Long-form architecture and history:
~/.claude/projects/-Users-monicacolgan/memory/project_inbox_assistant_remote_build.md - Auth scope reference:
~/.claude/projects/-Users-monicacolgan/memory/reference_gmail_connector_scope.md - Local manual fallback skill:
~/.claude/skills/inbox-triage/SKILL.md - This SOP:
team-hub.launchindustries.biz/sops/sop-tech-inbox-assistant