3.5 KiB
3.5 KiB
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 messagelabelIds), 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 systemSPAMid).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 senddoes 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
ESCALATEDfolder; 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 successfulsendGmailReply, fire-and-forgetautoAdvanceFolder(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-forgetautoAdvanceFolder(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 newFOLDER_DEFSentries;MANUAL_KEYS;getManagedFolderKey();autoAdvanceFolder(); add to exports.config.js—GMAIL_LABEL_AWAITING_REPLY(defaultAwaiting Reply),GMAIL_LABEL_NEEDS_RESPONSE(defaultNeeds 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 sendto email (separate concern; user is unsure/responseis even worth keeping). - Reopen flow (a customer reply after the Discord channel is deleted creates a new channel → Triage via the existing path; unchanged).