AutomationFlowsEmail & Gmail › Email → AI Triage → CRM Pipeline (template)

Email → AI Triage → CRM Pipeline (template)

Email → AI triage → CRM pipeline (template). Uses gmailTrigger, httpRequest, googleSheets, gmail. Event-driven trigger; 17 nodes.

Event trigger★★★★☆ complexity17 nodesGmail TriggerHTTP RequestGoogle SheetsGmailError Trigger
Email & Gmail Trigger: Event Nodes: 17 Complexity: ★★★★☆ Added:

This workflow follows the Error Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.

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 →

Download .json
{
  "name": "Email \u2192 AI triage \u2192 CRM pipeline (template)",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 1,
              "unit": "minutes"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "has:attachment filename:pdf"
        },
        "options": {
          "dataPropertyAttachmentsPrefixName": "attachment_",
          "downloadAttachments": true
        }
      },
      "id": "1f0a6297-192e-4c86-a3d9-044f1476b891",
      "name": "Email Received",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.3,
      "position": [
        -800,
        0
      ]
    },
    {
      "parameters": {
        "operation": "binaryToPropery",
        "binaryPropertyName": "attachment_0",
        "options": {}
      },
      "id": "130f68ae-0b7b-47e6-ab4f-9c1d41ae2e49",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        -576,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const from = $('Email Received').item.json.from?.text || '';\nconst subject = $('Email Received').item.json.subject || '';\nconst body = ($('Email Received').item.json.text || '').substring(0, 800);\n\n// CUSTOMIZE: replace these categories and rules with your own.\nconst systemPrompt = `You are an email triage assistant. Classify the email into EXACTLY one category. Reply with a single word only: QUOTE, FOLLOW_UP, OTHER.\n\n- QUOTE: a new inbound request that needs data extracted from the attachment and a quote prepared.\n- FOLLOW_UP: a customer replying about an existing request (a question, an acceptance, a change).\n- OTHER: anything else (newsletters, internal mail, spam).`;\n\nreturn {\n  json: {\n    body: JSON.stringify({\n      model: 'claude-haiku-4-5',\n      max_tokens: 16,\n      temperature: 0,\n      system: systemPrompt,\n      messages: [{ role: 'user', content: 'From: ' + from + '\\nSubject: ' + subject + '\\nBody: ' + body }]\n    })\n  }\n};"
      },
      "id": "a9a66c33-0c13-427a-8fb9-15dcf456ef50",
      "name": "Build Triage Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -368,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ $json.body }}",
        "options": {}
      },
      "id": "d4e4ce2d-d343-48cf-b0f6-165c5c9f6dbf",
      "name": "Claude Triage (Haiku)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        -144,
        0
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.content[0].text.trim() }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "QUOTE"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "QUOTE"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.content[0].text.trim() }}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "FOLLOW_UP"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "FOLLOW_UP"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "OTHER"
        }
      },
      "id": "72ba7122-4a99-42bf-80bb-05602817c268",
      "name": "Route by Category",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        80,
        0
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const pdfBase64 = $('Extract PDF Text').item.json.data;\nconst subject = $('Email Received').item.json.subject || '';\nconst body = ($('Email Received').item.json.text || '').substring(0, 2000);\n\n// CUSTOMIZE: describe the fields you want and any business rules.\n// Keep calculations OUT of the model \u2014 let the AI read, let your sheet/code compute.\nconst systemPrompt = `You extract structured data from the attached PDF and return JSON only.\n\nReturn ONLY raw JSON, no preamble, no markdown fences.\nSchema: { \"customer_name\": string, \"reference\": string, \"destination\": string, \"amount\": number }`;\n\nreturn {\n  json: {\n    body: {\n      model: 'claude-sonnet-4-5',\n      max_tokens: 1024,\n      temperature: 0,\n      system: systemPrompt,\n      messages: [{\n        role: 'user',\n        content: [\n          { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: pdfBase64 } },\n          { type: 'text', text: 'Subject: ' + subject + '\\n\\n' + body }\n        ]\n      }]\n    }\n  }\n};"
      },
      "id": "b2d9fbe7-e3ce-4c20-80b8-973aafb5a94b",
      "name": "Build Extraction Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        304,
        -208
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            },
            {
              "name": "anthropic-beta",
              "value": "pdfs-2024-09-25"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.body) }}",
        "options": {}
      },
      "id": "9839f088-f6b6-4e98-90a5-a8c275e4a7f3",
      "name": "Claude Extract (Sonnet)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        528,
        -208
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const content = $input.item.json.content || [];\nconst textBlock = content.find(b => b.type === 'text');\nconst raw = textBlock ? textBlock.text : '{}';\nconst clean = raw.replace(/```json/g, '').replace(/```/g, '').trim();\nlet data;\ntry {\n  data = JSON.parse(clean);\n} catch (e) {\n  throw new Error('Could not parse JSON from model output: ' + clean.slice(0, 200));\n}\n\n// Carry a couple of email metadata fields for the CRM / draft\nconst gmail = $('Email Received').item.json;\ndata.email_client = (gmail.from && gmail.from.text) || '';\ndata.thread_id = gmail.threadId || '';\nreturn { json: data };"
      },
      "id": "32eae7d1-d5fd-4be4-96df-3a8cb94a87d4",
      "name": "Parse JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        752,
        -208
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "id": "fb92750b-c969-4cc0-b56d-b638bb6ed986",
      "name": "Append to CRM",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        960,
        -208
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "=Quote \u2014 {{ $('Parse JSON').item.json.customer_name }} \u2014 {{ $('Parse JSON').item.json.reference }}",
        "message": "=Hello {{ $('Parse JSON').item.json.customer_name }},\n\n[CUSTOMIZE: your reply template here]\n\nReference: {{ $('Parse JSON').item.json.reference }}",
        "options": {
          "threadId": "={{ $('Parse JSON').item.json.thread_id }}",
          "sendTo": "={{ $('Parse JSON').item.json.email_client }}"
        }
      },
      "id": "4ec7867b-767d-460f-8010-feb9ceeaf5fe",
      "name": "Draft Reply",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1184,
        -208
      ]
    },
    {
      "parameters": {
        "sendTo": "<__PLACEHOLDER_VALUE__Your team inbox, e.g. team@yourcompany.com__>",
        "subject": "=Follow-up from {{ $('Email Received').item.json.from?.text }}",
        "message": "=A customer replied about an existing request.\n\nFrom: {{ $('Email Received').item.json.from?.text }}\nSubject: {{ $('Email Received').item.json.subject }}\n\n{{ ($('Email Received').item.json.text || '').substring(0, 800) }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "0a274454-be09-4b1b-9f02-de2f88e9c738",
      "name": "Notify: Follow-up",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        304,
        112
      ]
    },
    {
      "parameters": {
        "sendTo": "<__PLACEHOLDER_VALUE__Your team inbox, e.g. team@yourcompany.com__>",
        "subject": "=Unclassified email \u2014 {{ $('Email Received').item.json.from?.text }}",
        "message": "=This email was not classified as QUOTE or FOLLOW_UP.\n\nFrom: {{ $('Email Received').item.json.from?.text }}\nSubject: {{ $('Email Received').item.json.subject }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "5e508dbc-5353-4327-8b21-cef8fedbcbf9",
      "name": "Notify: Unclassified",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        304,
        368
      ]
    },
    {
      "parameters": {},
      "id": "c3a40ceb-2dc0-4b18-8b9f-d391522565e1",
      "name": "On Error",
      "type": "n8n-nodes-base.errorTrigger",
      "typeVersion": 1,
      "position": [
        -800,
        -400
      ]
    },
    {
      "parameters": {
        "sendTo": "<__PLACEHOLDER_VALUE__Your team inbox, e.g. team@yourcompany.com__>",
        "subject": "=Workflow error: {{ $json.execution.error.node.name }}",
        "message": "=Node: {{ $json.execution.error.node.name }}\nError: {{ $json.execution.error.message }}",
        "options": {
          "appendAttribution": false
        }
      },
      "id": "dc770160-5547-4715-a6ff-e627d23a217e",
      "name": "Send Error Alert",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        -576,
        -400
      ]
    },
    {
      "parameters": {
        "content": "## 1) Binary rule\nExtract From File MUST sit right after the trigger \u2014 PDF binary data does not survive Code/HTTP nodes.",
        "height": 200,
        "width": 300,
        "color": 4
      },
      "id": "1970e7a7-7731-4ad6-9784-18e5fe776ef7",
      "name": "Sticky Note d3cfaab3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -624,
        -48
      ]
    },
    {
      "parameters": {
        "content": "## 2) Customize your prompts\nEdit the two Code nodes: triage categories (Haiku) and extraction schema (Sonnet). Keep price/maths out of the model \u2014 AI reads, your sheet/code calculates.",
        "height": 400,
        "width": 960,
        "color": 3
      },
      "id": "af006176-91d2-4286-859a-f697d8b7c243",
      "name": "Sticky Note 5bd13e62",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -416,
        -256
      ]
    },
    {
      "parameters": {
        "content": "## 3) Set credentials & targets\nAdd your Gmail + Anthropic API + Google Sheets credentials, pick your sheet, and set the team inbox on the notify nodes.",
        "height": 200,
        "width": 300,
        "color": 5
      },
      "id": "d5b1273a-44e0-47f3-99ef-9befacf084d7",
      "name": "Sticky Note 68727839",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        912,
        -256
      ]
    }
  ],
  "connections": {
    "Email Received": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Build Triage Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Triage Prompt": {
      "main": [
        [
          {
            "node": "Claude Triage (Haiku)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Triage (Haiku)": {
      "main": [
        [
          {
            "node": "Route by Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Category": {
      "main": [
        [
          {
            "node": "Build Extraction Prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify: Follow-up",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify: Unclassified",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Extraction Prompt": {
      "main": [
        [
          {
            "node": "Claude Extract (Sonnet)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Extract (Sonnet)": {
      "main": [
        [
          {
            "node": "Parse JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse JSON": {
      "main": [
        [
          {
            "node": "Append to CRM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append to CRM": {
      "main": [
        [
          {
            "node": "Draft Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On Error": {
      "main": [
        [
          {
            "node": "Send Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": true
  },
  "versionId": "aaa7fa4c-1f05-4107-b06c-cc232bea8ac5",
  "meta": {
    "aiBuilderAssisted": true,
    "builderVariant": "mcp"
  },
  "nodeGroups": [],
  "id": "uFEwYUKjb1OzSNPM",
  "tags": []
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Email → AI triage → CRM pipeline (template). Uses gmailTrigger, httpRequest, googleSheets, gmail. Event-driven trigger; 17 nodes.

Source: https://gist.github.com/oulaijeffpro-ux/1e2fd8eb649f4de573e333ccf2086eaf — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

📘 Description

HTTP Request, Gmail Trigger, ClickUp +3
Email & Gmail

02 · AI Email Triage → Draft, Alert or Archive. Uses gmailTrigger, httpRequest, telegram, gmail. Event-driven trigger; 10 nodes.

Gmail Trigger, HTTP Request, Telegram +2
Email & Gmail

This workflow automatically handles every resolved Jira bug by verifying the fix, notifying the customer, updating HubSpot, commenting on the Jira issue, alerting the team on Slack, and logging everyt

Jira Trigger, Jira, Gmail +4
Email & Gmail

This workflow runs daily to read chronic care vitals from Google Sheets, merge them into a per-patient record, score risk using AHA/ADA threshold rules, and notify clinicians via Telegram and Gmail wh

Error Trigger, Slack, Google Sheets +2
Email & Gmail

The goal is to reduce inbox noise and automatically organize repetitive types of emails so that imprtant messages remain visible while unsolicited or promotional emails are handled automatically. When

Gmail, Gmail Trigger, Google Sheets