How to Automate Email Triage with n8n

By The LightCube Labs team

The Problem

Not every email deserves the same attention, yet a flat inbox treats a message from your biggest client exactly like a vendor newsletter. People respond by checking constantly, which wrecks focus, or by checking rarely, which means urgent requests wait for hours. Manual rules in mail clients can sort by sender but cannot weigh several signals together. This guide builds an automatic triage system with n8n. Each new message receives a score from sender status, urgent words and attachments, lands in a priority folder, and the most important items are posted to Slack.

What You Will Need

How the n8n Workflow Fits Together

This n8n workflow reads each new email, gives it a priority score, files it into a folder, and alerts Slack when the score is high. The core idea is that no single signal decides importance. A known sender, an urgent word, and an attachment each add points, while an unknown sender subtracts points. The total then maps to four priorities: P0 urgent, P1 important, P2 normal and P3 low.

Seven n8n nodes do the work: the Email Trigger (IMAP), a Set node for lists, a Code node for scoring, a Switch node for routing, an IMAP Email node for moving mail, an IF node, and a Slack node. Because the logic is transparent JavaScript inside n8n, you can inspect and tune every rule.

Step-by-Step Workflow Setup

1 Trigger on every new email in n8n

Triage is a real-time job, so the workflow starts with the Email Trigger (IMAP) node. It watches the inbox and emits one item per new message, which the rest of the n8n workflow scores and routes within seconds.

Add an n8n Email Trigger (IMAP) node and configure:

Node: Email Trigger (IMAP)
Credential: IMAP (host imap.yourcompany.com, port 993, SSL On)
Mailbox Name: INBOX
Action: None (leave messages unread)
Download Attachments: Off
Format: Simple
Options > Force Reconnect Every Minutes: 2

The node holds an IMAP connection open and receives new mail as it arrives. Forcing a reconnect every two minutes means that even if a provider silently drops idle connections, the worst case is a two minute delay before a message is picked up. Attachments are not downloaded, but the attachment metadata is still available for scoring.

Expression: {{ $json.from.value[0].address }}
Sample incoming item:
{
  "uid": 90211,
  "from": { "value": [{ "address": "ceo@bigclient.com", "name": "Dana Lee" }] },
  "subject": "URGENT: contract signature needed today",
  "textPlain": "Hi, we need the signed contract before 5pm ...",
  "attachments": [{ "filename": "contract.pdf", "size": 184220 }]
}

Leave the action on None. Moving the message into a folder in a later step is what marks it as handled.

2 Keep the VIP list in one editable place in n8n

Scoring needs two lists: VIP senders and trusted domains. Store them in a Set node placed right after the trigger so anyone can change the values without reading code. For larger lists, replace this node with a Google Sheets or data table lookup.

Add an n8n Edit Fields (Set) node and configure:

Node: Edit Fields (Set)
Mode: Manual Mapping
Keep Only Set: Off
  vipList (Array): ["ceo@bigclient.com","cfo@bigclient.com","founder@partner.io"]
  knownDomains (Array): ["bigclient.com","partner.io","yourcompany.com","stripe.com"]
  urgentWords (Array): ["urgent","asap","immediately","deadline","action required","outage","today"]

Keeping the lists in a visible node makes the n8n workflow self-documenting. When a new client signs, add their address to vipList and every future message from them is boosted automatically.

Expression: {{ $json.vipList.includes($json.from.value[0].address.toLowerCase()) }}
Sample list values:
{
  "vipList": ["ceo@bigclient.com", "cfo@bigclient.com"],
  "knownDomains": ["bigclient.com", "partner.io"],
  "urgentWords": ["urgent", "asap", "deadline"]
}

Store addresses in lowercase and lowercase the incoming sender before comparing, or Dana@BigClient.com will miss the match.

3 Compute a multi-factor priority score in n8n

An n8n Code node adds up signals. Each factor is simple, but together they separate a CEO asking for a signature from a cold sales email using the word urgent. The weights are exactly: known VIP sender +50, urgent keywords +30, attachment +10, unknown sender -20.

Add an n8n Code node and configure:

Node: Code (Score)
Mode: Run Once for Each Item
Language: JavaScript

const j = $json;
const addr = j.from.value[0].address.toLowerCase();
const domain = addr.split('@')[1] || '';
const text = ((j.subject || '') + ' ' + (j.textPlain || '')).toLowerCase();
let score = 0;
const reasons = [];
if (j.vipList.includes(addr)) { score += 50; reasons.push('vip'); }
const hits = j.urgentWords.filter(w => text.includes(w));
if (hits.length > 0) { score += 30; reasons.push('urgent:' + hits.join('|')); }
if ((j.attachments || []).length > 0) { score += 10; reasons.push('attachment'); }
const known = j.vipList.includes(addr) || j.knownDomains.includes(domain);
if (known === false) { score -= 20; reasons.push('unknown'); }
return { json: { uid: j.uid, from: addr, name: j.from.value[0].name || addr,
  subject: j.subject, score, reasons } };

The urgent bonus is applied once, no matter how many keywords match, so a long angry email cannot outrank a VIP. The unknown sender penalty is what neutralises spam: an unknown sender who writes "urgent" nets 30 minus 20, which is 10, and lands in the lowest priority folder.

Expression: {{ $json.reasons.join(", ") }}
Sample scored item:
{
  "uid": 90211,
  "from": "ceo@bigclient.com",
  "name": "Dana Lee",
  "subject": "URGENT: contract signature needed today",
  "score": 90,
  "reasons": ["vip", "urgent:urgent|today", "attachment"]
}

Storing the reasons array makes every routing decision explainable, which helps when you tune the weights later.

4 Route by score with a Switch node in n8n

The n8n Switch node converts the number into one of four priorities. Rules are evaluated top to bottom and the first match wins, so list the highest threshold first.

Add an n8n Switch node and configure:

Node: Switch
Mode: Rules
Rule 1 (Output: P0-urgent):  {{ $json.score }}  is greater than or equal to  80
Rule 2 (Output: P1-important): {{ $json.score }}  is greater than or equal to  50
Rule 3 (Output: P2-normal):  {{ $json.score }}  is greater than or equal to  20
Fallback Output: Extra Output (P3-low)
Options > Send data to all matching outputs: Off

With the default weights, a VIP with an urgent message scores 80 or more and goes to P0. A VIP alone scores 50, which is P1. An urgent message from a known domain scores 30, which is P2. Anything lower, including unknown senders, falls through to P3.

Expression: {{ $json.score >= 80 ? "P0" : $json.score >= 50 ? "P1" : $json.score >= 20 ? "P2" : "P3" }}
Sample routing result:
{
  "score": 90,
  "routedTo": "P0-urgent",
  "output": 0
}

Turning off Send data to all matching outputs is important. If it is on, a score of 90 would fan out to P0, P1 and P2 at the same time and the message would be moved three times.

5 Move the message to a priority folder in n8n

Each Switch output connects to an IMAP Email node that files the message into a folder, so the mailbox itself becomes the triage view. Create folders named Urgent, Important, Normal and Low on your mail server first.

Add an n8n IMAP Email node and configure:

Node: IMAP Email (one per output, or one node with a folder expression)
Operation: Move to Folder
Mailbox: INBOX
UID: {{ $json.uid }}
Destination Folder: {{ { "P0-urgent": "Urgent", "P1-important": "Important", "P2-normal": "Normal", "P3-low": "Low" }[$json.priority] }}

To use a single node, add a Set node after each Switch output that writes a priority string, merge the four branches, and let the folder expression pick the target. Four separate IMAP nodes are easier to read, while one node is easier to maintain.

Expression: {{ $json.uid }}
Sample move result:
{
  "uid": 90211,
  "moved": true,
  "folder": "Urgent"
}

IMAP folder names are case sensitive and nested folders use the server delimiter, for example INBOX.Urgent on some providers. Check the exact name in your mail client.

6 Filter for urgent items in n8n

Only the top two priorities deserve an interruption. An n8n IF node checks the score so that P2 and P3 mail stays quietly filed and only P0 and P1 reach chat.

Add an n8n IF node and configure:

Node: IF
Conditions: Number
  Value 1: {{ $json.score }}
  Operation: is greater than or equal to
  Value 2: 50
Combine: AND

You could instead test the priority label, but comparing the score directly keeps the threshold in one obvious place. Connect only the true branch to the Slack node.

Expression: {{ $json.score >= 50 }}
Sample IF result:
{
  "score": 90,
  "result": true
}

A two condition version with OR is useful if you also want any message containing the word outage to alert regardless of score.

7 Post urgent mail to Slack in n8n

The final n8n Slack node posts a compact alert to #email-urgent with who wrote, the subject line and the score, so the right person can open the message straight away.

Add an n8n Slack node and configure:

Node: Slack
Resource: Message
Operation: Send
Channel: #email-urgent
Text:
{{ $json.score >= 80 ? ":rotating_light: P0" : ":warning: P1" }} *{{ $json.name }}* <{{ $json.from }}>
Subject: {{ $json.subject }}
Score: {{ $json.score }} ({{ $json.reasons.join(", ") }})

Including the reasons in the alert tells the reader why the message was escalated, which builds trust in the scoring. The channel name must exist and the Slack app must be a member of it.

Expression: {{ $json.score }} ({{ $json.reasons.join(", ") }})
Sample Slack response:
{
  "ok": true,
  "channel": "C05URGENT",
  "message": { "text": ":rotating_light: P0 *Dana Lee* ..." }
}

Keep this channel small. If it receives more than a handful of alerts a day, raise the P1 threshold from 50 to 60 rather than muting the channel.

Extending This n8n Workflow

Once the n8n workflow runs well, you can refine it in several directions. Keep a feedback loop by adding a weekly n8n workflow that counts how many messages landed in each folder, and review the Low folder for anything that deserved better. Add negative keywords such as unsubscribe or newsletter to reduce the score of bulk mail further. Add a time of day factor, so that urgent mail arriving after hours is routed to a different Slack channel.

For a team inbox, add an assignee lookup so the n8n Slack node mentions the person who owns the sender account. The scoring node is the only part that needs to change as your rules grow.

Troubleshooting

The trigger stops receiving new mail after a few hours

Many providers close idle IMAP connections. Set Force Reconnect Every Minutes to 2 in the trigger options, and confirm the n8n workflow is activated rather than only tested. Check the n8n execution list for a connection error, which usually names a timeout.

A single email is moved into several folders

The Switch option Send data to all matching outputs is on, so a high score matches every rule. Turn it off so only the first matching rule fires, and keep the highest threshold at the top of the list.

Everything scores low because VIP matching fails

Compare lowercase to lowercase. The sender address from the trigger can contain capitals, while the VIP list is usually typed in lowercase. Lowercase the sender in the Code node, and trim stray spaces from the list values in the Set node.

Free n8n Workflow Resources

Get started with the free workflow pack on the LightCube Labs templates page.

Production-Ready Workflow Packs

Get this workflow and more, with setup guides, editable configurations, and a shared failure-alert system.

View the Starter ($29) and Pro ($99) Packs

Frequently Asked Questions

How do I choose the weights and thresholds?

Start with the defaults of 50, 30, 10 and minus 20, then review the Urgent folder after a week. If it holds too much noise, raise the P0 threshold. If real priorities are missing, add keywords or raise the VIP weight. The reasons array on every item shows exactly which factor caused each placement.

Will it work with Gmail or Outlook?

It works with any provider that offers IMAP. Gmail exposes labels as folders, so moving a message to a folder named Urgent applies that label. Microsoft 365 requires IMAP to be enabled for the mailbox.

Can the VIP list come from my CRM instead of a Set node?

Yes. Replace the Set node with an HTTP Request or CRM node that returns contact emails, then build the list with a Code node. Cache the result in workflow static data so the n8n workflow does not call the CRM for every message.

Does the workflow read the full body of emails?

It reads the plain text body for keyword matching inside your own n8n instance. Nothing is sent to an outside classifier, and only the sender, subject and score are posted to Slack.