Triage support email with n8n's Text Classifier and Gmail labels
Label every support email in Gmail as billing, bug, how-to or feature request with an LLM in n8n, and ping Slack for bugs. Setup, prompts, and costs.
· ranx.cloud

This is for whoever owns a shared support inbox in Google Workspace or Gmail and spends the first hour of the day sorting it. You end up with an n8n workflow that reads each new email to support@, asks a small LLM which of five buckets it belongs in, applies the matching Gmail label, and posts bug reports to a Slack channel. Humans still write the replies; the inbox just arrives sorted.
Gmail Trigger → Text Classifier (+ Anthropic Chat Model) → Gmail: Add Label (one per category)
↘ Slack (bug branch only)
At current Claude Haiku prices, 3,000 emails a month costs about a dollar in LLM spend. The arithmetic is below.
1. Google OAuth credential
n8n self-hosted can't use the "Sign in with Google" managed app that n8n Cloud has, so you create your own OAuth client once. It takes about ten minutes and you reuse it for Sheets, Drive and Calendar later. The full reference is n8n's Google OAuth2 single service page; this is the short path.
- In n8n, go to Credentials, create a Gmail OAuth2 API credential and copy the OAuth Redirect URL it shows. It's your instance URL plus
/rest/oauth2-credential/callback; on ranx.cloud that'shttps://<name>.ranx.cloud/rest/oauth2-credential/callback. - In Google Cloud Console, create a project (or pick one), then go to APIs & Services → Library and enable the Gmail API.
- APIs & Services → OAuth consent screen → Get started. Give it a name and support email. For Audience, choose Internal if the mailbox is on Google Workspace and you're an admin there; otherwise External.
- APIs & Services → Credentials → Create credentials → OAuth client ID. Type Web application. Paste the redirect URL from step 1 into Authorized redirect URIs, exactly, including
https://. - Copy the Client ID and Client Secret into the n8n credential, click Sign in with Google, approve, and save.
If you chose External, the app starts in Testing mode: only accounts you add under Test users can sign in, and Google expires refresh tokens for Testing apps after seven days. That shows up as a workflow that works for a week and then fails with invalid_grant. Either use Internal (Workspace), or move the app to production under Audience → Publish app. An unverified app in production shows a warning screen when you sign in; for an app only you use, click through it. The n8n community thread on credentials expiring weekly has the details and one report of it still expiring after publishing, so keep an eye on the first two weeks.
2. Create the labels in Gmail
Create these labels in Gmail first: triage/billing, triage/bug, triage/how-to, triage/feature, triage/other. The n8n Gmail node picks labels from a list it loads from your account, so they need to exist.
3. Gmail Trigger
Add a Gmail Trigger with the credential from step 1.
Poll Times: every minute (every 5 minutes is fine too)
Event: Message Received
Simplify: off
Filters → Search: to:support@yourcompany.com -label:triage/billing -label:triage/bug -label:triage/how-to -label:triage/feature -label:triage/other
Filters → Read Status: Unread and read emails
Turn Simplify off. With it on, the trigger asks Gmail for metadata only, and you get a ~200-character snippet instead of the body. With it off you get $json.text (plain-text body), $json.subject, $json.from, $json.id and $json.threadId.
Always set Search. Without it, every email in the mailbox triggers the workflow, including newsletters and calendar invites. Excluding the triage labels means a message that's already sorted won't be classified again if you re-run things.
The trigger remembers when it last polled and asks Gmail for messages after that time, so emails that arrive while n8n is restarting are picked up on the next poll instead of being skipped.
4. Text Classifier
Add a Text Classifier node (search for it by name in the nodes panel). Attach a chat model to its Model input: Anthropic Chat Model, with an Anthropic credential.
Get the key at console.anthropic.com → API Keys → Create Key, and paste it into the Anthropic credential in n8n. You pay Anthropic directly.
In the model node, pick a Haiku model from the list (the list comes from Anthropic's API, so it shows what your key can use). Classification doesn't need a bigger model.
Text Classifier settings:
Text to Classify:
Subject: {{ $json.subject }}
From: {{ $json.from.text }}
{{ $json.text.slice(0, 6000) }}
The slice caps very long threads with quoted history at about 1,500 tokens. Categories, with descriptions the model actually reads:
| Category | Description |
|---|---|
| billing | Invoices, charges, refunds, payment methods, plan changes, VAT and receipts. |
| bug | Something in the product is broken or behaves differently from what the customer expected: errors, crashes, data missing, a feature that stopped working. |
| how-to | The customer is asking how to do something the product already supports. |
| feature | A request for something the product doesn't do yet. |
Options:
When No Clear Match: Output on Extra, 'Other' Branch
Allow Multiple Classes To Be True: off
Turning on the extra branch gives you a fifth output for anything that doesn't fit, which is better than the default (Discard Item), where unclassifiable emails silently disappear from the workflow.
The descriptions do most of the work. When you see a misclassification in the first week, fix the description rather than adding a sixth category. "bug vs how-to" is the boundary that usually needs sharpening: the line "behaves differently from what the customer expected" catches people reporting a misunderstanding as a bug, and that's often what you want routed to whoever handles bugs anyway.
5. Apply the labels
On each of the five outputs, add a Gmail node:
Resource: Message
Operation: Add Label
Message ID: {{ $('Gmail Trigger').item.json.id }}
Label Names or IDs: triage/billing (triage/bug on the bug branch, and so on)
Reference the trigger by name, as above. The Text Classifier passes the item through, but naming the source node keeps the expression working if you insert nodes in between later.
6. Ping Slack for bugs
On the bug branch, after the label node, add a Slack node (Message → Send, to #support-bugs). Slack app setup: api.slack.com/apps → Create New App → From scratch → OAuth & Permissions, add the chat:write bot scope, install, copy the xoxb- token into a Slack API credential, and /invite the bot to the channel.
:beetle: Bug report from {{ $('Gmail Trigger').item.json.from.text }}
*{{ $('Gmail Trigger').item.json.subject }}*
{{ $('Gmail Trigger').item.json.text.slice(0, 400) }}
https://mail.google.com/mail/u/0/#all/{{ $('Gmail Trigger').item.json.threadId }}
7. Test and publish
Send yourself four test emails that are clearly one category each, plus one ambiguous one. Click Execute workflow; in a manual run the trigger fetches recent matching mail so you can check each branch. Then Publish.
Costs
Prices are Anthropic's list prices as of November 2026. Claude Haiku 5.5 is $0.10 per million input tokens and $0.50 per million output tokens (for prompts up to 100,000 tokens).
Per email, roughly:
- Input: classifier system prompt and category descriptions (~400 tokens) plus subject and body capped at 6,000 characters (up to ~1,500 tokens). Call it 1,900 tokens.
- Output: the JSON answer is tiny. If the model uses extended thinking, those tokens bill as output too, so budget 300 output tokens to be safe.
input: 1,900 × $0.10 / 1,000,000 = $0.00019
output: 300 × $0.50 / 1,000,000 = $0.00015
total per email ≈ $0.00034
3,000 emails / month ≈ $1.02
Check the real numbers after a day: open an execution, click the chat model node, and look at the token usage it reports. If you swap in a Sonnet-class model ($2 / $10 per million at the same date), the same 3,000 emails cost about 3,000 × (1,900 × 2 + 300 × 10) / 1,000,000 = $20.40.
On the Gmail side, the trigger and the label calls are far inside Gmail's per-user API quota (Gmail API usage limits) at support-inbox volumes.
What goes wrong
redirect_uri_mismatch on Sign in with Google. The URI in Google Cloud doesn't match what n8n sends. Copy it again from the n8n credential panel and paste it exactly. A trailing slash or http vs https is enough to break it.
Works for a week, then invalid_grant. The Testing-mode seven-day refresh token expiry described in step 1. Publish the app or use Internal, then reconnect the credential.
Every email in the mailbox runs through the LLM. The Search filter is empty or wrong. The node itself warns about this: without a filter, all incoming email triggers the workflow. Test the query in Gmail's own search box first.
Emails get classified twice. You have both the published workflow and a manual test run picking up the same mail, or a second copy of the workflow is published. The -label: exclusions in the search query are a second line of defence.
"Add Label" fails with a label error. The label was renamed or deleted in Gmail after you set up the node. Reselect it from the list.
Bursts of mail arrive at once. Max Emails per Poll defaults to 10. Anything beyond that is picked up on later polls, so a 200-email morning drains over 20 polls. Raise it (maximum 50) if that's too slow.
The model returns nothing useful for non-English mail. Haiku handles common languages fine, but category descriptions in English plus an email in another language occasionally lands in "other". Add "The email may be in any language" to the classifier's System Prompt Template option.
Variations
- Draft replies for how-to questions. On the how-to branch, add a Basic LLM Chain that drafts an answer from a pasted FAQ, then a Gmail node with Resource Draft, Operation Create, and the trigger's
threadIdin the Thread ID option so the draft lands in the conversation. Someone reviews and sends it. Budget this with output length in mind: a 300-word draft is about 400 output tokens before thinking. - Urgency flag. Turn on Allow Multiple Classes To Be True and add an
urgentcategory ("the customer cannot use the product at all, or mentions losing data or money"). Route urgent mail to a Slack DM.


