# Folder lifecycle: Triage → Awaiting Reply → Needs Response ## Goal Auto-advance a ticket's Gmail folder as the conversation moves, so the inbox reflects who owes a reply: - New email ticket → **Triage** (unchanged). - Staff reply reaches the customer → **Awaiting Reply**. - Customer responds → **Needs Response**. - Escalate → **Escalated** (unchanged; cycle resumes on the next reply/response). - Close → **Resolved** (unchanged). Triage means "new, not yet replied to"; the first staff reply moves Triage→Awaiting Reply. ## Folder taxonomy (new classification) - **Auto-cycle** (bot moves these): `TRIAGE`, `ESCALATED`, `AWAITING_REPLY`, `NEEDS_RESPONSE`, `RESOLVED`. - **Manual / exempt** (bot never auto-moves): `FOR_JAKE`, `SPAM`, `PARTNERSHIP_OFFERS`, `DASHBOARD_ERRORS`. A customer reply (or staff reply) to a thread sitting in a manual folder leaves it there; only auto-cycle (or unlabeled) threads advance. ## Mechanism New `services/gmailLabels.js` exports: - `MANUAL_KEYS` — the exempt set above. - `getManagedFolderKey(threadId, gmail)` — reads the thread's existing labels (union of message `labelIds`), maps to a managed folder key, returns it or null. **Read-only**: never creates a label just to check (uses the existing name→id cache; SPAM maps to the system `SPAM` id). - `autoAdvanceFolder(threadId, targetKey, gmail)`: ``` current = getManagedFolderKey(threadId, gmail) if current ∈ MANUAL_KEYS: return false // filed manually → don't touch await moveThreadToFolder(threadId, targetKey, gmail) return true ``` ## Decisions baked in (from brainstorming) - "A response given" = a reply that actually emails the customer. Today that's the typed-reply path (`handleDiscordReply` → `sendGmailReply`). `/response send` does NOT email today, so it is intentionally NOT wired here; if it's ever wired to email, it routes through the same send point and gets the move for free. - Escalation keeps its own distinct `ESCALATED` folder; it is auto-cycle eligible, so the next staff reply → Awaiting Reply and the next customer reply → Needs Response. - Manual filings (For Jake / Spam / Partnership / Dashboard Errors) are never overridden by the auto-flow. ## Hooks - `handlers/messages.js` — after a successful `sendGmailReply`, fire-and-forget `autoAdvanceFolder(ticket.gmailThreadId, 'AWAITING_REPLY')`. - `gmail-poll.js` (~line 308, the follow-up-to-existing-channel branch) — replace the "leave folder untouched" behavior with fire-and-forget `autoAdvanceFolder(parsed.threadId, 'NEEDS_RESPONSE', gmail)`. Both guard Discord-origin tickets (`gmailThreadId` starts `discord-`) and are fire-and-forget (`.catch(() => {})`) so a label failure (e.g. stale Gmail auth) never breaks the reply or the poll. ## Files - `services/gmailLabels.js` — 2 new `FOLDER_DEFS` entries; `MANUAL_KEYS`; `getManagedFolderKey()`; `autoAdvanceFolder()`; add to exports. - `config.js` — `GMAIL_LABEL_AWAITING_REPLY` (default `Awaiting Reply`), `GMAIL_LABEL_NEEDS_RESPONSE` (default `Needs Response`). - `.env.example` — document the two new vars. - `handlers/messages.js` — Awaiting Reply hook. - `gmail-poll.js` — Needs Response hook. - `tests/gmailLabels.test.js` — cover manual-exemption gating + happy-path advance. ## Out of scope - Wiring `/response send` to email (separate concern; user is unsure `/response` is even worth keeping). - Reopen flow (a customer reply after the Discord channel is deleted creates a new channel → Triage via the existing path; unchanged).