How to Automate Email Categorization with n8n and LLM
I got tired of waiting for Gmail to figure out which emails actually matter to me. After three months of letting AI handle my personal email categorization, I can't imagine going back to manual sorting.
My system is embarrassingly simple: every email gets sorted into one of three buckets by GPT-5-nano. Archive it, read it, or answer it. That's it.
The whole thing runs on n8n and costs me almost nothing since I'm using the cheapest OpenAI model with structured output. Sure, OpenAI now sees all my personal emails, but let's be honest—they probably know more about me from my ChatGPT conversations anyway.
This isn't some theoretical concept I'm pitching. I've been running this in production for three months, and it saves me hours every week.
Why I Built This Email Automation System
I receive way too many emails. Newsletter subscriptions, service notifications, actual important messages from real humans—it all lands in the same inbox. Gmail's built-in categorization kept missing obvious patterns, and I was spending 20 minutes every morning just deciding what to read.
The breaking point came when I realized I was archiving 80% of my emails without reading them. If most emails don't need my attention, why am I the one deciding which ones do?
Prefer Video Tutorial? I've created a step-by-step video demonstration of this entire email automation workflow. Watch Email Automation with n8n and LLM Tutorial to see the complete setup process in action.
The Three-Category System That Actually Works
My AI categorizes every email into exactly three options:
- Archive - Newsletters I subscribed to but never read, automated notifications, promotional emails
- Read - Content I want to consume but don't need to respond to
- Answer - Emails that require a human response from me
No complex folder structures. No priority levels. Just three simple actions that cover 100% of my email.
Setting Up the n8n Workflow
Here's the complete n8n workflow that handles everything automatically:

The workflow triggers every time a new email arrives in my Gmail inbox. It grabs the email content, sends it to OpenAI for categorization, then applies the appropriate Gmail label and archives emails that don't need my attention.
Required n8n Nodes
You'll need these nodes in your workflow:
- Gmail Trigger - Monitors for new emails
- OpenAI Chat Model - Categorizes the email content
- Gmail - Applies labels and archives emails
- IF conditions - Routes emails based on AI decision
The magic happens in the OpenAI node configuration. Here's how I set it up:

The LLM Prompt That Makes It Work
The prompt is the heart of this system. After testing dozens of variations, this one gives me the most consistent results:
Note: This is just the LLM prompt for email categorization. If you want the complete n8n workflow automation (including all nodes and connections), scroll down to the "Complete n8n Workflow JSON" section below.
Click to expand the full LLM prompt
Act as an Email classifier. You will get email data and need to return the correct label from the list of available labels.
<email-data>
<from>{{ $json.From }}</from>
<to>{{ $json.To }}</to>
<email-subject>
{{ $json.Subject }}
</email-subject>
<email-snippet>
{{ $json.snippet }}
</email-snippet>
</email-data>
<possible-labels>
<to_read>
- `to_read` — for emails that need to be read by a human.
<to_read-examples>
- a secret code from the app/service
- notification about a message in another system, Kirill needs to log in and answer
- notification about something important Kirill needs to do
- notification from GitHub about a new message or issue in my project
</to_read-examples>
</to_read>
<to_hide>
- `to_hide` — for emails that are OK to be in the archive, but not needed to be read by a human right now.
<to_hide-examples>
- Invoices
- Some subscription for charity or news
- Product updates
- Advertising
- Proposals from companies without a history of our communications, but do not hide proposals from individuals
- Mentioning me in Discord or Slack (most likely spam and group mentions, or I will see it again in Slack, and no need to see here)
- Slack notification about new messages because Kirill will see them in Slack, no need to see them in email.
- Community summaries
- Some service incidents are mentioned, and an overview
- Notification about one more subscriber, because it does not need action from Kirill, and we can hide it.
- meeting notes from some AI software, because it is OK to have them in the archive, but Kirill doesn't need to reread them now.
</to_hide-examples>
</to_hide>
<to_answer>
- `to_answer` — for emails that need an answer.
<to_answer-examples>
- some emails with questions from a person or a company
</to_answer-examples>
<to_answer-bad-examples>
- message in external system, not in Gmail — bad example because Kirill can not answer via email. We need to mark these emails as to_read.
</to_answer-bad-examples>
</to_answer>
</possible-labels>
{{$node["exceptions"].json["additional-data-for-classify"]}}
Answer in JSON with two fields:
- `reasoning`
- `label`
I use structured output to ensure the AI always returns a valid category. No parsing errors, no edge cases where the AI gets creative with its response format.
Three Months of Real-World Results
Since implementing this system:
- Time saved: About 15-20 minutes per day on email triage
- Accuracy: The AI correctly categorizes roughly 95% of emails
- Cost: Under $3 per month using GPT-5-nano
- False positives: Maybe 2-3 emails per week get miscategorized
The 5% error rate is totally manageable. When the AI gets it wrong, I just move the email to the right category and move on. Still faster than manually sorting everything.
Security Considerations (And Why I'm Okay With Them)
Yes, OpenAI now processes all my personal emails. This isn't ideal from a privacy standpoint, but I made peace with it for a few reasons:
First, I already use a password manager with 2FA codes (not email-based), so email compromise isn't catastrophic. Second, OpenAI already knows plenty about me from regular ChatGPT usage. Third, the time savings are worth the privacy trade-off for my personal workflow.
If you're handling sensitive business emails, you might want to use a local LLM instead of OpenAI's API. The n8n setup works the same way.
Getting Started With Your Own Email Automation
Here's how to build this system yourself:
- Set up n8n - Either self-hosted or use n8n Cloud
- Connect Gmail - You'll need to authenticate your Gmail account
- Get OpenAI API access - Create an account and grab your API key
- Import the workflow - I'll share the JSON export if people want it
- Customize the prompt - Adjust the categories for your email patterns
- Test with a few emails - Start small before automating everything
The whole setup takes about 30 minutes if you're familiar with n8n. Maybe an hour if you're starting from scratch.
Why This Beats Gmail's Built-in Features
Gmail's automatic categorization is designed for everyone, which means it's optimized for no one. My system learns my specific email patterns and preferences.
Plus, I can modify the logic anytime. Want to add a fourth category? Change the prompt. Need different handling for emails from specific senders? Add a condition node. Gmail's rules are rigid; this system adapts to whatever I need.
The Bottom Line
Three months in, this email automation system has become essential to my daily workflow. It's not perfect, but it's way better than manually sorting hundreds of emails every week.
The setup is straightforward, the ongoing costs are minimal, and the time savings are real. If you're drowning in email like I was, this approach might be worth trying.
Just remember: start simple, test thoroughly, and don't automate anything you can't easily undo.
Complete n8n Workflow JSON
For those ready to implement this system, here's the complete n8n workflow you can import directly:
Click to expand the full JSON workflow
{
"name": "email-ai-automation-personal",
"nodes": [
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.message.content.label }}",
"rightValue": "to_read",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "585b8b65-2369-4f4e-ba0d-4a7dfc7cdef9"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "to_read"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "99e46836-ea2f-41d7-8ebc-24bd5cfadd41",
"leftValue": "={{ $json.message.content.label }}",
"rightValue": "to_hide",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "to_hide"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "2a10fb26-99e6-43ab-854c-2d208f701ac3",
"leftValue": "={{ $json.message.content.label }}",
"rightValue": "to_answer",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "to_answer"
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-1552,
432
],
"id": "ff266540-edd6-42ef-8e07-f1754c1aa394",
"name": "Switch"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-2256,
432
],
"id": "66a72457-b0e5-4ab0-bf49-ef00a06213aa",
"name": "Loop Over Items"
},
{
"parameters": {
"resource": "thread",
"operation": "addLabels",
"threadId": "={{ $json.threadId }}",
"labelIds": []
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1296,
112
],
"id": "47db305c-2f55-4797-b646-331903a798ad",
"name": "Add label to_hide",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"resource": "thread",
"operation": "addLabels",
"threadId": "={{ $json.threadId }}",
"labelIds": []
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1296,
480
],
"id": "bb1c5387-99a2-42ba-b615-ae65f39e9337",
"name": "Add label to_answer",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"resource": "thread",
"operation": "addLabels",
"threadId": "={{ $json.threadId }}",
"labelIds": []
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1296,
288
],
"id": "82f68387-c342-4222-87c3-f6bbb1a23f77",
"name": "Add label to_read",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"resource": "thread",
"operation": "addLabels",
"threadId": "={{ $('Merge2').item.json.threadId }}",
"labelIds": []
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-880,
656
],
"id": "7de8f655-3c3d-49f9-9074-b39eaf4abaf2",
"name": "Add label processed_by_ai",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"resource": "thread",
"operation": "removeLabels",
"threadId": "={{ $('Merge2').item.json.threadId }}",
"labelIds": [
"INBOX"
]
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1040,
208
],
"id": "438c1618-edf3-41a1-b991-fef18b003edf",
"name": "Remove label from thread",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyX"
}
]
},
"filters": {}
},
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1.2,
"position": [
-2560,
160
],
"id": "676e065d-fc0b-4ae4-8c1b-4b663267023f",
"name": "Gmail Trigger",
"credentials": {}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "be8e84b9-48af-4a49-b4f7-797382c13afe",
"name": "additional-data-for-classify",
"value": "<additional-data-for-classify>\n- All messages from Deel with the main idea, like `New contractor submission`, please mark as `to_hide`.\n</additional-data-for-classify>",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-2240,
-32
],
"id": "9cb4e14e-cd43-4a7b-96c9-7acd93b8b947",
"name": "exceptions"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-5-mini",
"mode": "list",
"cachedResultName": "GPT-5-MINI"
},
"messages": {
"values": [
{
"content": "=Act as an Email classifier. You will get email data and need to return the correct label from the list of available labels.\n\n<email-data>\n<from>{{ $json.From }}</from>\n<to>{{ $json.To }}</to>\n<email-subject>\n{{ $json.Subject }}\n</email-subject>\n<email-snippet>\n{{ $json.snippet }}\n</email-snippet>\n</email-data>\n\n<possible-lables>\n\n<to_read>\n- `to_read` — for emails that need to be read by a human. \n<to_read-examples>\n- a secret code from the app/service\n- notification about a message in another system, Kirill needs to log in and answer\n- notification about something important Kirill needs to do\n- notification from GitHub about a new message or issue in my project\n</to_read-examples>\n</to_read>\n\n<to_hide>\n- `to_hide` — for emails that are OK to be in the archive, but not needed to be read by a human right now. \n<to_hide-examples> \n- Invoices \n- Some subscription for charity or news\n- Product uddates\n- Advertising\n- Proposals from companies without a history of our communications, but do not hide proposals from individuals\n- Mentioning me in Discord or Slack (most likely spam and group mentions, or I will see it again in Slack, and no need to see here)\n- Slack notification about new messages because Kirill will see them in Slack, no need to see them in email.\n- Community summaries\n- Some service incidents are mentioned, and an overview\n- Notification about one more subscriber, because it does not need action from Kirill, and we can hide it.\n- meeting notes from some AI software, because it is OK to have them in the archive, but Kirill doesn't need to reread them now.\n</to_hide-examples>\n</to_hide>\n\n<to_answer>\n- `to_answer` — for emails that need an answer.\n<to_answer-examples>\n- some emails with questions from a person or a company\n</to_answer-examples>\n<to_answer-bad-examples>\n- message in external system, not in Gmail — bad example because Kirill can not answer via email. We need to mark these emails as to_read.\n</to_answer-bad-examples>\n</to_answer>\n\n</possible-lables>\n\n{{$node[\"exceptions\"].json[\"additional-data-for-classify\"]}}\n\nAnswer in JSON with two fields:\n- `reasoning`\n- `label`"
}
]
},
"jsonOutput": true,
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.8,
"position": [
-2032,
560
],
"id": "4f65ce04-67eb-4c28-9d56-6d2f03dedd41",
"name": "llm categorization",
"alwaysOutputData": false,
"credentials": {}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "95f8838a-df88-496c-a0ea-d19c612eb81b",
"leftValue": "={{ $json.labels.filter(item => item.id == \"__REPLACE_WITH_YOUR_PROCESSED_LABEL_ID__\") }}",
"rightValue": "={{ \"\" }}",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
-1936,
144
],
"id": "cc8dda5a-414c-4fb8-9774-6bdfe1224976",
"name": "Filter: not processed"
},
{
"parameters": {
"sendTo": "",
"subject": "There is a problem with AI email workflow — AI choose the wrong option",
"emailType": "text",
"message": "!",
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1296,
656
],
"id": "90cfc5e9-cddd-418e-bf95-5e9eb77b6778",
"name": "Send error message",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"operation": "getAll",
"returnAll": true,
"filters": {
"q": "=label:INBOX -label:__REPLACE_WITH_YOUR_PROCESSED_LABEL_ID__ after:2025/06/15"
}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-2400,
160
],
"id": "5d534d0c-0210-4d32-853d-d26a5b13c646",
"name": "Get emails with filter",
"webhookId": "",
"credentials": {}
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-2080,
144
],
"id": "c6e6b5bd-0196-4a4f-a36d-1f119df3ee5d",
"name": "Merge1"
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-1696,
464
],
"id": "d109e9f9-1089-4060-89e4-e70762d0aea5",
"name": "Merge2"
}
],
"pinData": {},
"connections": {
"Switch": {
"main": [
[
{
"node": "Add label to_read",
"type": "main",
"index": 0
}
],
[
{
"node": "Add label to_hide",
"type": "main",
"index": 0
}
],
[
{
"node": "Add label to_answer",
"type": "main",
"index": 0
}
],
[
{
"node": "Send error message",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "llm categorization",
"type": "main",
"index": 0
},
{
"node": "Merge2",
"type": "main",
"index": 0
}
]
]
},
"Add label to_hide": {
"main": [
[
{
"node": "Remove label from thread",
"type": "main",
"index": 0
}
]
]
},
"Add label to_answer": {
"main": [
[
{
"node": "Add label processed_by_ai",
"type": "main",
"index": 0
}
]
]
},
"Add label to_read": {
"main": [
[
{
"node": "Add label processed_by_ai",
"type": "main",
"index": 0
}
]
]
},
"Add label processed_by_ai": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Remove label from thread": {
"main": [
[
{
"node": "Add label processed_by_ai",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Get emails with filter",
"type": "main",
"index": 0
}
]
]
},
"exceptions": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"llm categorization": {
"main": [
[
{
"node": "Merge2",
"type": "main",
"index": 1
}
]
]
},
"Filter: not processed": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Send error message": {
"main": [
[
{
"node": "Add label processed_by_ai",
"type": "main",
"index": 0
}
]
]
},
"Get emails with filter": {
"main": [
[
{
"node": "exceptions",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"Merge1": {
"main": [
[
{
"node": "Filter: not processed",
"type": "main",
"index": 0
}
]
]
},
"Merge2": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner"
},
"versionId": "",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": ""
},
"id": "",
"tags": []
}
Import Instructions
- Copy the JSON above
- Save it to a file with the name
email-ai-automation-personal.json - In n8n, go to Workflows → Import from JSON
- Select the file you saved and click Import
- Configure your Gmail and OpenAI credentials
- Update the
__REPLACE_WITH_YOUR_PROCESSED_LABEL_ID__with the ID of the label you want to use for the processed emails - Setup email to send in case of error (fallback)
- Test with a few emails before enabling the trigger
Remember to update the OpenAI API key and Gmail authentication after importing the workflow.
Video Tutorial: Watch the Complete Email Automation Setup
If you prefer learning visually, I've created a comprehensive video tutorial that walks through the entire process of setting up this email automation system:
The video demonstrates each step of creating the n8n workflow, configuring the OpenAI integration, setting up Gmail connections, and testing the complete automation. You'll see the exact node configurations, the LLM prompt in action, and how the system categorizes real emails in real-time.





