I replaced my morning inbox triage with a six-step n8n flow
Labels, drafts and a daily digest, all before coffee. The exact nodes, the prompts, and the two edge cases that nearly broke it.
Every morning started the same way: forty minutes of opening, skimming, archiving and flagging email before I did any real work. Most of it needed one of four things: a label, a quick reply, a note on my to-do list, or nothing at all.
That’s a sorting problem, and sorting is something a small language model does well. So I built a flow in n8n that does the first pass for me. It has been running for three months, and my morning inbox now takes about five minutes.
Here is the whole thing, node by node, including the prompt and the two bugs that almost made me switch it off.
The flow at a glance
The flow has six steps. Five of them run on every new email; the sixth runs once a day.
- Gmail Trigger picks up new mail every minute.
- Filter drops anything the model should never see.
- Basic LLM Chain reads the email and returns a label, a priority and a one-line summary.
- Switch routes the email by label.
- Gmail applies the label and, for anything that needs a reply, writes a draft.
- Schedule Trigger sends me a digest of everything the flow handled.
Nothing is sent automatically. The flow labels, drafts and summarizes; I still press send.
Catching and filtering new mail
Step 1: The Gmail Trigger
The Gmail Trigger node polls for new messages. I set it to every minute and turned on Simplify so the output is a clean object with from, subject, text and labelIds instead of the raw MIME payload.
Step 2: The filter
Not everything should go to a model. The Filter node drops three kinds of email before they reach it:
- Anything from my bank, my accountant or my doctor, matched by sender domain.
- Anything already labelled by a Gmail filter I set up years ago.
- Calendar invites, which Gmail already handles well.
This step is less about cost and more about trust. If an email is private enough that I’d hesitate to paste it into a chat window, the flow shouldn’t send it anywhere either.
Letting the model sort
The Basic LLM Chain node does the actual triage. I pair it with a Structured Output Parser so the model has to return valid JSON, which makes every step after it much simpler.
Here is the prompt, trimmed slightly:
You triage email for a solo builder who writes about automation.
Read the email and return JSON with:
- label: one of "reply", "read-later", "receipt", "newsletter", "ignore"
- priority: "high" only if a real person is waiting on an answer today
- summary: one sentence, under 20 words, no greetings
When unsure between two labels, pick the one that needs less of my time.
And the schema the parser enforces:
{
"label": "reply",
"priority": "high",
"summary": "Maya is asking whether Thursday still works for the podcast recording."
}
That last line of the prompt did more than any other change. Before it, the model sent borderline newsletters to reply and I spent the digest undoing its work.
A triage flow is only useful if you trust it enough to stop checking its work. Bias it toward doing less.
Routing, drafting and labelling
Step 4: The Switch node
The Switch node reads label and sends each email down one of five branches. Four of them just apply the matching Gmail label and archive the message. The reply branch does one more thing.
Step 5: Drafting replies
For reply emails, a second LLM call writes a short draft in my voice, and the Gmail node saves it as a draft on the thread. I gave that prompt three of my own sent emails as examples, which is enough for the tone to feel right most of the time.
The drafts are rarely perfect. They don’t need to be. Editing a draft is much faster than starting from an empty reply box.
The daily digest
At 7:30 every morning, a Schedule Trigger kicks off a separate branch. It reads everything the flow labelled in the last 24 hours, groups it by label, and emails me a digest that looks like this:
- Needs a reply (3): each with its one-line summary and a link to the draft.
- Read later (7): titles only.
- Receipts (4): amounts, pulled out of the summary.
- Ignored (19): a count, so I notice if it suddenly jumps.
I read the digest with my first coffee. Most days, that is all the inbox work I do before lunch.
The two edge cases that nearly broke it
Reply-all storms. A thread with fifteen people on it produced fifteen triggers in a row, and the flow drafted fifteen replies to the same conversation. The fix was a Code node that keeps the last seen thread ID for ten minutes and skips repeats.
Forwarded emails. When someone forwarded me a message, the model read the original sender as the person asking. It drafted replies to people who had never written to me. I now strip everything below the first ---------- Forwarded message line before the text reaches the model, and add a note that the email was forwarded.
What it costs to run
| Piece | What I use | Monthly cost |
|---|---|---|
| n8n | Self-hosted on a small VPS | About $5 |
| Model calls | A small, fast model | Under $1 |
| Gmail | Existing account | $0 |
Around sixty emails a day reach the model. Each call is a few hundred tokens, so the model bill is measured in cents.
What I’d change next
The obvious next step is letting the flow add tasks straight to my to-do app when an email contains a request. I’ve held off because a wrong task is more annoying than a missing one.
If you build your own version, start with labels only. Run it for a week, read every decision it makes, and only add drafting once the labels stop surprising you.
- n8n
- Gmail
- LLMs