This workflow corresponds to n8n.io template #15913 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Triage emails and send a morning digest with local AI",
"tags": [],
"nodes": [
{
"id": "657afdd0-ae4f-4428-966d-dbfa5c99e7b4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
6048,
8384
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Triage emails and send a morning digest with local AI\n\n### How it works\n\nThis workflow manually runs a sample email triage process, classifies each email with a local Ollama-powered LLM, and parses the classification results. It branches urgent items into an alert-formatting path, passes non-urgent items through without action, and also builds a morning digest from the parsed classifications. A separate error-trigger path logs workflow failures for troubleshooting.\n\n### Setup steps\n\n- Ensure Ollama is installed, running locally, and has the required chat model pulled and available.\n- Configure the Ollama Chat Model node credentials/host and model name to match your local Ollama setup.\n- Review or replace the Sample Emails code node with a real email source if moving beyond demo data.\n- Update the formatting code for urgent alerts and the morning digest to match the desired output format and delivery channel.\n\n### Customization\n\nReplace the manual trigger with a scheduled trigger for daily morning runs, connect a real email inbox node instead of sample data, and add email/Slack/Teams output nodes after the alert and digest formatting nodes to actually send notifications."
},
"typeVersion": 1
},
{
"id": "77ef55d0-2ea1-47c2-b802-9e347446e85c",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
6608,
8640
],
"parameters": {
"color": 7,
"width": 432,
"height": 304,
"content": "## Manual sample input\n\nStarts the workflow manually and generates sample email items that feed the triage process."
},
"typeVersion": 1
},
{
"id": "4b567f61-804b-403b-b25f-19c45e6491c7",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
7344,
8608
],
"parameters": {
"color": 7,
"width": 448,
"height": 496,
"content": "## Local AI classification\n\nUses the local Ollama chat model through an LLM chain to classify emails, then parses the model output into structured classification data."
},
"typeVersion": 1
},
{
"id": "f7659901-1f87-46d0-a92e-8060929fe856",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
7856,
8384
],
"parameters": {
"color": 7,
"width": 432,
"height": 736,
"content": "## Urgency and digest outputs\n\nHandles the parsed results by checking for urgency, formatting urgent alerts, passing non-urgent items through, and building the morning digest in the same output cluster."
},
"typeVersion": 1
},
{
"id": "1ba689d0-2d5d-46bd-9b06-331d995b95a2",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
6608,
9008
],
"parameters": {
"color": 7,
"width": 432,
"height": 320,
"content": "## Workflow error logging\n\nProvides a separate error-handling path that triggers on workflow failures and logs the error details for debugging."
},
"typeVersion": 1
},
{
"id": "873a595e-7cd2-460b-9415-de7c1ad98247",
"name": "Manual Execution Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
6656,
8768
],
"parameters": {},
"typeVersion": 1
},
{
"id": "cf2a4069-60d0-4fbe-8eff-ae472c59d6e4",
"name": "Simulate Email Input",
"type": "n8n-nodes-base.code",
"position": [
6896,
8768
],
"parameters": {
"jsCode": "return [\n {\n json: {\n from: 'user@example.com',\n subject: 'URGENT: Production database is down',\n body: 'Our main database cluster went offline 10 minutes ago. Customer-facing services are affected. Need all hands on deck immediately.',\n date: '2026-05-20T07:15:00Z'\n }\n },\n {\n json: {\n from: 'user@example.com',\n subject: 'Your weekly digest from Medium',\n body: 'Here are the top stories this week in technology and product management. Click to read more.',\n date: '2026-05-20T06:00:00Z'\n }\n },\n {\n json: {\n from: 'user@example.com',\n subject: 'Re: Technical Product Manager - Next Steps',\n body: 'Hi Salahuddin, thanks for your application. We would like to schedule a call this week. Are you available Thursday at 2pm CET?',\n date: '2026-05-20T08:30:00Z'\n }\n },\n {\n json: {\n from: 'user@example.com',\n subject: '[PROJ-142] Sprint review reminder',\n body: 'Reminder: Sprint review is scheduled for tomorrow at 11am. Please update your ticket statuses before the meeting.',\n date: '2026-05-20T05:45:00Z'\n }\n },\n {\n json: {\n from: 'user@example.com',\n subject: '50% OFF everything today only!',\n body: 'Do not miss our biggest sale of the year. Use code SAVE50 at checkout. Terms and conditions apply.',\n date: '2026-05-20T04:00:00Z'\n }\n },\n {\n json: {\n from: 'user@example.com',\n subject: 'Contract renewal - decision needed by EOD',\n body: 'We need to finalize the renewal terms today. The current contract expires Friday. Can you review the attached proposal and confirm?',\n date: '2026-05-20T09:00:00Z'\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "83b53b6b-6a95-49ca-985f-7e2aeafa7de1",
"name": "AI Email Classifier",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
7392,
8768
],
"parameters": {
"text": "=You are an email triage assistant. Classify emails by urgency. Always respond with valid JSON only. No markdown, no code blocks, no explanation \u2014 just the JSON object.\n\nClassify this email into exactly one category.\n\nFrom: {{ $json.from }}\nSubject: {{ $json.subject }}\nBody: {{ $json.body }}\nDate: {{ $json.date }}\n\nCategories:\n- URGENT: Needs immediate action (outages, deadlines today, time-sensitive decisions)\n- REPLY_TODAY: Needs a response today but not immediately (interview scheduling, meeting prep, colleague requests)\n- FYI: Informational, read when convenient (newsletters, status updates, future reminders)\n- JUNK: Promotional, spam, or irrelevant\n\nRespond with ONLY this JSON, no other text:\n{\"category\": \"CATEGORY_NAME\", \"reason\": \"one sentence why\", \"suggestedAction\": \"what to do\", \"from\": \"the sender\", \"subject\": \"the subject line\"}",
"promptType": "define"
},
"typeVersion": 1.5
},
{
"id": "b0290682-9bbe-4e1d-bcd9-c6440c925ca3",
"name": "Ollama AI Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"position": [
7392,
8960
],
"parameters": {
"model": "gemma4:e4b",
"options": {
"temperature": 0.1
}
},
"credentials": {
"ollamaApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "f7e3bff7-dede-45e3-9a66-e8194c5c7116",
"name": "Parse Email Categories",
"type": "n8n-nodes-base.code",
"position": [
7648,
8768
],
"parameters": {
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n let parsed;\n const output = item.json.text || item.json.response || '';\n\n try {\n // Handle LLM wrapping JSON in markdown code blocks\n const cleaned = output.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n parsed = JSON.parse(cleaned);\n } catch (e) {\n parsed = {\n category: 'FYI',\n reason: 'Could not parse LLM response: ' + output.substring(0, 100),\n suggestedAction: 'Review manually',\n from: 'unknown',\n subject: 'unknown'\n };\n }\n\n return { json: parsed };\n});"
},
"typeVersion": 2
},
{
"id": "3c49d011-4c02-45bc-8d5e-b1b8d58153b5",
"name": "Check If Urgent Email",
"type": "n8n-nodes-base.if",
"position": [
7904,
8656
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-urgent-check",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.category }}",
"rightValue": "URGENT"
}
]
}
},
"typeVersion": 2
},
{
"id": "e43d8aee-f08a-417d-a99f-5f4eb3d1c5bd",
"name": "Create Urgent Email Alert",
"type": "n8n-nodes-base.code",
"position": [
8144,
8560
],
"parameters": {
"jsCode": "const items = $input.all();\n\nreturn items.map(item => ({\n json: {\n alert: `URGENT: ${item.json.subject} (from ${item.json.from})\\nAction: ${item.json.suggestedAction}`,\n category: item.json.category,\n from: item.json.from,\n subject: item.json.subject\n }\n}));"
},
"typeVersion": 2
},
{
"id": "bf7d4a28-a523-47ab-8136-1e611a01d633",
"name": "Skip Urgent Processing",
"type": "n8n-nodes-base.noOp",
"position": [
8144,
8768
],
"parameters": {},
"typeVersion": 1
},
{
"id": "b509d69b-396c-4399-b1c1-f71f46d61705",
"name": "Compile Morning Email Digest",
"type": "n8n-nodes-base.code",
"position": [
7904,
8960
],
"parameters": {
"jsCode": "const items = $input.all();\n\nconst categories = { URGENT: [], REPLY_TODAY: [], FYI: [], JUNK: [] };\n\nitems.forEach(item => {\n const cat = item.json.category || 'FYI';\n if (categories[cat]) {\n categories[cat].push(item.json);\n }\n});\n\nconst formatSection = (title, emails) => {\n if (emails.length === 0) return '';\n const lines = emails.map(e => ` - ${e.subject} (from ${e.from})\\n Action: ${e.suggestedAction}`);\n return `${title} (${emails.length})\\n${lines.join('\\n')}`;\n};\n\nconst digest = [\n `MORNING EMAIL DIGEST - ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}`,\n `${items.length} emails triaged\\n`,\n formatSection('URGENT', categories.URGENT),\n formatSection('REPLY TODAY', categories.REPLY_TODAY),\n formatSection('FYI', categories.FYI),\n formatSection('JUNK', categories.JUNK)\n].filter(s => s !== '').join('\\n\\n');\n\nreturn [{ json: { digest, summary: { total: items.length, urgent: categories.URGENT.length, replyToday: categories.REPLY_TODAY.length, fyi: categories.FYI.length, junk: categories.JUNK.length } } }];"
},
"typeVersion": 2
},
{
"id": "3ba07ee7-4636-45ec-9529-c460b81edda4",
"name": "Workflow Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"position": [
6656,
9168
],
"parameters": {},
"typeVersion": 1
},
{
"id": "57e1450d-934e-49b0-9bf9-1a75972581d9",
"name": "Record Error Information",
"type": "n8n-nodes-base.code",
"position": [
6896,
9168
],
"parameters": {
"jsCode": "const items = $input.all();\nconst error = items[0]?.json;\n\nreturn [{\n json: {\n error: true,\n message: error?.message || 'Unknown error',\n workflow: 'AI Email Triage',\n node: error?.execution?.lastNodeExecuted || 'unknown',\n timestamp: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"connections": {
"Ollama AI Model": {
"ai_languageModel": [
[
{
"node": "AI Email Classifier",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Email Classifier": {
"main": [
[
{
"node": "Parse Email Categories",
"type": "main",
"index": 0
}
]
]
},
"Simulate Email Input": {
"main": [
[
{
"node": "AI Email Classifier",
"type": "main",
"index": 0
}
]
]
},
"Check If Urgent Email": {
"main": [
[
{
"node": "Create Urgent Email Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Skip Urgent Processing",
"type": "main",
"index": 0
}
]
]
},
"Parse Email Categories": {
"main": [
[
{
"node": "Check If Urgent Email",
"type": "main",
"index": 0
},
{
"node": "Compile Morning Email Digest",
"type": "main",
"index": 0
}
]
]
},
"Workflow Error Trigger": {
"main": [
[
{
"node": "Record Error Information",
"type": "main",
"index": 0
}
]
]
},
"Manual Execution Trigger": {
"main": [
[
{
"node": "Simulate Email Input",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
ollamaApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow triages emails with a local Ollama LLM (gemma4:e4b), flags urgent messages for immediate alerting, and compiles a morning digest grouped by urgency categories. Runs manually and generates a sample set of incoming emails. Sends each email to Ollama (gemma4:e4b) with…
Source: https://n8n.io/workflows/15913/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Episode 11: AI shorts factory app. Uses httpRequest, googleSheets, lmChatOpenAi, lmChatOllama. Event-driven trigger; 96 nodes.
This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest — You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract — OCR + parsing pulls out key fie
My workflow 53. Uses formTrigger, httpRequest, lmChatOpenAi, form. Event-driven trigger; 74 nodes.
Episode 23: UGC with nanobanana. Uses lmChatOpenAi, lmChatOllama, lmChatDeepSeek, lmChatOpenRouter. Event-driven trigger; 74 nodes.
Agent Nodes. Uses lmChatOpenAi, slack, stopAndError, errorTrigger. Event-driven trigger; 72 nodes.