Tickets piled up in one inbox, all mixed together. While someone read through them one by one, sorting by type and handing them off, urgent issues got buried among the routine ones. A payment error that arrived overnight would only be spotted in the afternoon — over and over.
We handed that first read-and-sort step to AI. But we split it: judgment automatic, sending manual. AI produces the category, priority, and reply draft; a human opens the draft from Gmail's Drafts folder and reviews, edits, and sends within 5 seconds. It's a safety net for when classification is wrong, and an observation window for prompt tuning in the first few weeks.
How it works
- A
Schedule Triggernode kicks off every 10 minutes (swap in a Gmail Trigger if you need real-time). - A
Gmail Get Manynode pulls only new CS mail vialabel:cs-inbox is:unread. Thecs-inboxlabel is applied automatically by a Gmail filter based on your CS address / forwarding rules. - For each email, a
Codenode cleans up the subject and body and builds the messages array for OpenAI (the Code node's Run Once for Each Item mode is required). - An
HTTP Requestnode POSTs tohttps://api.openai.com/v1/chat/completions— withresponse_format: json_objectset so the AI is forced to return valid JSON only. - A
Codenode parses the AI JSON and normalizes it into{category, priority, reason, reply}. - Three follow-up actions in parallel:
Mark as Readprevents reprocessing on the next run ·Create Draft Replysaves the response draft to Gmail's Drafts folder ·Aggregate Summary + Slack Postannounces the batch classification results to the team channel.
What you need
- n8n 2.29.9 or later (self-hosted or Cloud). Verification was done on self-hosted Docker.
- Gmail OAuth2 credentials — enable the Gmail API in a Google Cloud project and issue an OAuth 2.0 client. Required scopes:
https://www.googleapis.com/auth/gmail.modify(mark as read) +https://www.googleapis.com/auth/gmail.compose(draft creation). - Gmail filter rule — Gmail Settings → Filters and Blocked Addresses → Create new filter. Auto-apply the
cs-inboxlabel to mail matching conditions liketo:cs@yourdomain.com. Create the label ahead of time. - OpenAI API key — payment method required. Register in n8n Credentials as
OpenAI account. At 100 tickets/day,gpt-4o-miniruns under 1,000 KRW/month. - Slack Bot User OAuth Token —
chat:write,channels:read./invite @your-botin the target channel. - Slack channel — one your CS team watches. Per-batch summaries (category counts + urgent-item highlights) get posted here.
AI classification schema — this is the JSON it returns
{
"category": "billing" | "technical" | "feature" | "general",
"priority": "urgent" | "normal" | "low",
"reason": "one sentence explaining why it was classified this way",
"reply": "a polite Korean draft for the team to review, edit, and send (2–3 paragraphs, including greeting)"
}Actual classification results (batch of 4)
[billing/urgent] Payment failed but the charge came off my card
→ A payment issue occurred and the card was already charged — needs urgent handling.
[technical/urgent] API responses suddenly started returning 500 errors
→ API errors are producing 500s and there is a risk of service disruption.
[feature/normal] Do you have plans to support SSO (OIDC)?
→ The user is asking about SSO (OIDC) support plans, classified as a feature request.
[technical/normal] The homepage won't load
→ Homepage-not-loading issues fall under technical.Why save a draft instead of auto-sending
- No matter how good the AI classification and draft are, a human should review for the first few weeks. A single misfired apology creates more trust damage than 90% accuracy is worth.
- The draft lives in Gmail's Drafts folder, so the responder opens it in the original thread's reply interface, reviews and edits in 5 seconds, and clicks send once. The "AI wrote the draft" review flow is familiar to operators.
- Once accuracy is proven, switching to auto-send is a one-line change: flip the
Create Draft Replynode's operation fromdraft:createtomessage:send. You can also mix per category (e.g. auto-sendgeneral, draft everything else).
Technical note — why HTTP Request instead of the n8n OpenAI node
- The n8n OpenAI node (
typeVersion 1.7) has an issue where, under certain conditions, it constructs the request such that OpenAI returns theWelcome to the OpenAI API!intro text. No actual completion happens. - Attaching to the HTTP Request node via
Authentication → Predefined Credential Type → OpenAI APIreturns a proper completion response using the same credential. The request payload is explicit, so debugging is easy. - The HTTP Request approach is essential when you need forced JSON responses like
response_format: json_object.
Edge cases
- Zero matching emails — if Gmail Get Many returns an empty result, downstream nodes don't run. The Slack post is silently skipped too.
- AI fails to parse JSON — the
Parse AInode is wrapped intry/catchand fills in fallback values (category: general, priority: normal). The draft is left as an empty string for the responder to write from scratch. - OpenAI error — the HTTP Request node ends the workflow in error. Check the Executions tab. In production, enable the node option
Retry on Failto absorb rate limits. - Missing Gmail label — if the
cs-inboxlabel doesn't exist ahead of time, the search fails. In Gmail: sidebar → Create new label →cs-inbox. - Duplicate processing — after processing, the
Mark as Readnode flips the read state so the next run doesn't reprocess. To reprocess, mark unread manually in Gmail.
Urgent tickets don't get buried anymore. AI picks them out first.— CS team lead