Build an email triage agent in n8n
A step-by-step build: an agent that reads incoming customer email, matches it against your own answers, and leaves a draft reply for a human to send.
Inbox- Classify
Answers- Draft
- Human
This is a build, not an overview. By the end you’ll have an agent that watches an inbox, works out what each message is actually asking, looks up your own answer to that question, and leaves a draft reply sitting in Gmail for a human to check and send.
It takes an afternoon. It runs for about two euros a month at small volume. And it does not send anything on its own, which is the single most important design decision in the whole thing.
What you’re building
The agent sits between the inbox and the person who answers it. Mail arrives, the agent classifies it, and one of three things happens.
If it’s a question you’ve answered before, the agent pulls your approved answer, writes a reply in your tone, and saves it as a Gmail draft. If it’s something new or sensitive, the agent doesn’t guess. It posts to Slack with a short summary and a link, and gets out of the way. Anything that’s clearly not customer mail gets labelled and left alone.
What it is not: an autoresponder. Nothing leaves your domain without a person clicking send. That constraint is what makes this safe to run against real customers in week one instead of month six.
What it costs to run, before you start
Three cost lines, and only one of them is the model.
The orchestration layer is the cheap part if you self-host: n8n bills per execution (one whole workflow run, however many steps it contains) while Zapier bills per task, meaning per step.
A seven-step chain firing a thousand times a month is seven thousand Zapier tasks and one thousand n8n executions.
Self-hosted n8n on a small VPS runs at roughly $5 to $10 a month with no execution meter at all, see the full cost curve
The second line is the model, billed per token, and the reason step 3 below classifies before it generates: classification is a short prompt with a short answer, generation is neither. Getting the order wrong multiplies the bill by the share of mail that needed no answer.
The third line is the one nobody budgets: someone has to own it. Expect ongoing costs of roughly a quarter to 40 % of the build effort per year, because models get deprecated, fields get renamed and inboxes change.
Before you start
You need four things.
An n8n instance, either cloud or self-hosted. A Gmail or IMAP account with OAuth set up in n8n’s credentials. An API key for a model. And, the part people skip, thirty to forty real answers you already give customers, pulled out of your sent folder.
That last one is the actual work. The agent is maybe an hour of node wiring. The quality of the thing lives entirely in how good your answer bank is, and there’s no shortcut around writing it down.
Budget roughly: 45 minutes on the answer bank, 60 minutes on the build, 30 minutes testing against real mail.
Step 1: Put your answers in a data table
In n8n, create a Data Table called faq_answers with four columns:
| Column | Type | What goes in it |
|---|---|---|
topic | string | A short slug like shipping_delay or invoice_copy |
question | string | How customers actually phrase it, in their words |
answer | string | The approved reply, written the way you’d send it |
escalate | boolean | True if a human must always look at this one |
Fill it from your sent folder, not from imagination. Copy the phrasing customers actually use, including the sloppy version. “wo ist mein paket” and “Sendungsverfolgung funktioniert nicht” are the same topic and both belong in the table.
Mark anything involving money, cancellations, complaints, or legal language as escalate: true. You can loosen that later once you trust the classifier.
You cannot un-send a bad reply. Every default in this build points the same way: when in doubt, a person sees it first.
Step 2: Trigger on new mail
Add a Gmail Trigger node. Set the poll interval to every minute, and filter on the label or address you want covered. Start narrow. Point it at one support alias, not the founder’s personal inbox.
Add an Edit Fields (Set) node right after it and pull out just what you need: from, subject, body, threadId, messageId. Everything downstream reads from these five fields, so if you later swap Gmail for IMAP you only rewire this one node.
Step 3: Classify before you generate
This is the step most tutorials skip, and it’s why their agents hallucinate.
Add a Basic LLM Chain node. Give it the message and your list of topic slugs from step 1, and ask for exactly one of them back, or the literal string unknown. Nothing else. No prose, no explanation.
You classify inbound customer email for a support desk.
Valid topics:
{{ $json.topicList }}
Return exactly one topic slug from that list, or the single word: unknown
Return "unknown" whenever you are not confident. "unknown" is a
correct and useful answer. Guessing is not.
Subject: {{ $json.subject }}
Body: {{ $json.body }}
Two things matter here. The model picks from a closed list instead of inventing a category, and unknown is framed as a success rather than a failure. Models will avoid saying “I don’t know” unless you explicitly make it a valid, respectable answer.
Use a small model. Classification into twenty buckets does not need your most expensive one.
Step 4: Look up the answer
Add an If node. If the topic came back unknown, branch to escalation (step 6). Otherwise continue.
On the continue branch, add a Data Table node with the “Get row(s)” operation, filtering topic equals the classified slug. You now have the approved answer text in hand.
Check the escalate flag with a second If. True sends it to escalation as well, even though the classification succeeded. Some topics you just always want a human on.
Step 5: Draft, don’t send
Add a second Basic LLM Chain. This one gets the customer’s original mail and your approved answer, and its only job is to fit the approved answer to this specific message.
Write a reply to the customer email below.
Use ONLY the facts in the approved answer. Do not add information,
promises, dates, or numbers that are not in it. If the approved
answer does not fully cover what they asked, answer the part it
covers and say a colleague will follow up on the rest.
Match the tone of the approved answer. Same language as the
customer wrote in. No greeting fluff, no "I hope this finds you well".
Approved answer:
{{ $json.answer }}
Customer email:
{{ $json.body }}
Then a Gmail node, operation Create Draft, with threadId set so the draft lands in the right conversation rather than as a new mail.
Say it once more because it’s the whole point: Create Draft, not Send. A human opens Gmail, sees a reply already written, reads it in four seconds, and hits send. That’s the workflow. The time saved is real, and the failure mode is a slightly awkward draft rather than a wrong promise sent to a customer.
Step 6: Escalate loudly
For unknown and for anything flagged escalate, add a Slack node posting to the channel the support person actually reads.
Keep the message short: who wrote in, the subject line, the first two lines of the body, and a direct link to the thread. Do not attach the model’s guess. If the agent wasn’t confident enough to draft, its opinion isn’t worth putting in front of a human who now has to think about it independently.
Step 7: Test it against yesterday
Do not test with invented emails. You’ll write clean, well-formed examples, and real customers don’t write like that.
Take the last twenty real messages from that inbox and run them through. For each one, write down the topic you’d have picked and compare it to what the agent picked. You’re looking for two numbers: how often it classified correctly, and how often it said unknown when it should have known.
The second number is the good kind of wrong. The dangerous number is confident and incorrect: it picked shipping_delay for a refund request and drafted a reply about tracking numbers. Every one of those is a missing or overlapping row in your data table, not a prompt problem. Go fix the table.
Rerun until confident-and-wrong is at zero across your twenty. unknown can sit at 20 or 30 percent on day one and that’s fine, because escalation is a working path, not a failure.
What breaks in week two
Three things, reliably.
Threads. A customer replies to the agent’s draft and the whole conversation re-enters the workflow. Filter on threadId against a table of threads you’ve already touched, or you’ll draft replies to your own replies.
Language drift. The classifier trained on your German answer bank starts seeing English mail. Add the customer’s language to the classifier output and keep separate answer rows per language rather than asking the model to translate your approved wording.
Answer rot. Your shipping policy changes and the data table doesn’t. The agent then confidently drafts last quarter’s policy, in your voice, on your letterhead. Put a calendar reminder on the table review. Monthly is enough.
What it costs to run
At 200 customer emails a month with a small model doing classification and a mid-size one doing the draft, model spend lands around one to three euros. Self-hosted n8n runs on a five to ten euro VPS. Cloud n8n starts around twenty.
So: under thirty euros a month, against maybe four hours of someone’s week. The economics were never the hard part.
Where this goes next
Once the classifier is reliable, the same skeleton extends without redesign. Route by topic to different people instead of one shared channel. Pull order status from your actual system and hand it to the draft step as a fact. Track which drafts get sent unedited, which is the only honest measure of whether the thing is good.
The pattern underneath is the part worth keeping: classify against a closed list, look up an approved answer, generate inside tight constraints, and stop before the irreversible step. That shape works for quotes, for inbound leads, for internal ticket triage. The nodes change, the skeleton doesn’t.
If you want this running against your real inbox with your real answers, book a strategy call and we’ll build it with you.
Frequently asked questions about building an email triage agent
Can I let the agent send replies automatically?
Technically yes, and we’d argue against it for the first few months. Draft-and-approve costs the reviewer about four seconds per mail and removes the entire category of failure where a wrong answer reaches a customer with your name on it. Revisit once you have months of data on how often drafts get sent unedited.
Does this work with Outlook or plain IMAP?
Yes. Swap the Gmail Trigger for the Microsoft Outlook or IMAP node and the draft step for the matching create-draft operation. Because step 2 normalises everything into five fields, nothing downstream changes.
How many FAQ entries do I need before it’s useful?
Thirty covers most of the volume for a small support desk, because inbound mail follows a steep distribution. Ten is enough to see whether the shape works at all. The agent gets better by adding rows, not by rewriting prompts.
Is this GDPR compliant?
The workflow itself doesn’t decide that. What decides it is where n8n runs, which model provider you send customer text to, whether you have a data processing agreement with them, and whether they can train on your data. Self-hosted n8n in the EU with a provider that offers an Article 28 DPA and a no-training commitment is a defensible setup. A US chat product with a consumer account is not.
What if the model picks the wrong topic?
Then you have two rows in your data table that are too close together, or one that’s too broad. Fix the table. Reaching for a bigger model to paper over an ambiguous answer bank makes the agent more expensive and more confidently wrong at the same time.
Sources: n8n pricing and Zapier pricing, checked July 2026. Under the AI Act, an automated reply that goes to a person outside the company has to disclose that it is automated, see Article 50, in force since 2 August 2026.


