Skip to content
Gmail Inbox Triage

Module 06 · business · intermediate

Lesson 06 · Gmail Inbox Triage

Goal

Your first AI workflow: let an LLM read each incoming email and decide what it is, then act on that decision. The pattern (trigger → prepare text → AI extracts structured fields → normal nodes act on them) is behind most "AI automation" gigs.

Prerequisites

  • Modules 01–05
  • An OpenAI API key (platform.openai.com → API keys). Small models like gpt-5-mini are cheap per email; check current pricing on openai.com/api/pricing.
  • Gmail credential (from Module 05) for the inbox part

Concepts

  • Cluster nodes: AI nodes have sub-nodes. The Information Extractor (root) needs a Chat Model (sub-node) plugged into its Model input. Connections between them are of type ai_languageModel, not main.
  • Information Extractor: turns free text into fields you define (name, type, description). The descriptions are your prompt; be specific about allowed values.
  • Never trust the model blindly: Triage Result forces unknown categories to other and normalizes urgent to a real boolean. Downstream nodes can then rely on clean values.
  • Two triggers, one brain: the Gmail Trigger is for production, the Webhook is for testing prompts quickly with curl (same idea as Module 02).
  • Gmail labels need IDs: the Add Labels operation takes label IDs like Label_123456, not names.

Build it

Step 1: Triggers and input

  • Gmail Trigger New Email: poll every minute, filter Read Status: Unread, keep Simplify on.
  • Webhook Triage API: POST triage, respond with a Respond to Webhook node → IF Has Content? ({{ ($json.body.subject ?? '') + ($json.body.text ?? '') }} is not empty), false → 400.
  • Edit Fields Prepare Email (fed by both): message_id = {{ $json.id ?? '' }}, from = {{ $json.From ?? $json.body?.from ?? '' }}, subject = {{ $json.Subject ?? $json.body?.subject ?? '' }}, text = {{ ($json.snippet ?? $json.body?.text ?? '').slice(0, 3000) }}.

The ?? chains let one node handle both the Gmail shape (From, Subject, snippet) and the API shape (body.from, …).

Step 2: Classify with AI

  1. Information Extractor Classify Email. Text: From: {{ $json.from }} / Subject: {{ $json.subject }} / blank line / {{ $json.text }}.
  2. Attributes:
    • category (string, required): "Exactly one of: sales (…), support (…), billing (…), newsletter (…), personal (…), other"
    • urgent (boolean, required): "true only if the sender needs a response within 24 hours or something is broken"
    • summary (string, required): "One sentence, max 20 words, saying what the sender wants"
  3. Options → System Prompt: "You triage a freelancer's inbox… Use only the allowed category values, in lowercase."
  4. Click the Model input → OpenAI Chat Model → create the OpenAI credential → model gpt-5-mini.

Step 3: Clean the result

Edit Fields Triage Result: copy message_id, from, subject from $('Prepare Email'), then:

  • category = {{ ['sales','support','billing','newsletter','personal'].includes($json.output.category) ? $json.output.category : 'other' }}
  • urgent (Boolean) = {{ $json.output.urgent === true || $json.output.urgent === 'true' }}
  • summary = {{ $json.output.summary }}

Step 4: Respond or label

IF Called via API? ({{ $('Triage API').isExecuted }} is true):

  • true → Respond to Webhook {{ $json }}
  • false → Edit Fields Label Map (edit me) (include other fields) with an Object field label_ids mapping each category to a Gmail label ID → Gmail Apply Label (Message → Add Label, Message ID {{ $json.message_id }}, Label IDs expression {{ [$json.label_ids[$json.category]] }}) → IF Urgent? → No Operation Notify Me (Exercise 2).

Step 5: Find your label IDs

Create labels in Gmail (e.g. AI/Sales, AI/Support, …). Then in n8n add a temporary Gmail node → Label → Get Many, run it, copy each id into Label Map, and delete the temporary node.

Test it

./test/test.sh                 # validation only
WITH_CREDS=1 ./test/test.sh    # + 4 classification cases (needs the OpenAI credential)

Then publish, send yourself a test email and watch it get labeled within a minute.

Common problems

  • "Node does not have any credentials set" / "Error in sub-node OpenAI Chat Model": select your OpenAI credential on the OpenAI Chat Model node.
  • Categories like "Sales Lead" or "SUPPORT": tighten the attribute description; Triage Result already maps anything unexpected to other.
  • "Invalid label": you used a label name. Use the ID from Label → Get Many.
  • Same email processed twice: the trigger only fetches unread mail. Mark as read after labeling (Exercise 1).
  • Costs: each email is one small request. Keep text capped (.slice(0, 3000)) so long threads don't cost more.