{ "name": "Sort, draft and hand off inbound email to a public address with Pidgeon", "nodes": [ { "id": "6f0c1d2e-0002-4a00-8000-000000000001", "name": "About this workflow", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-900, -420], "parameters": { "width": 540, "height": 820, "content": "## Sort, draft and hand off inbound email\n\n**Self-hosted n8n only** until the Pidgeon node is verified: it is a community node, `@pidgeon-ai/n8n-nodes-pidgeon`.\n\n### Who it is for\nA team whose public address (`hello@`, `sales@`, `anfragen@`) gets more mail than it can answer by hand.\n\n### How it works\nThe public address is the agent's, so nobody's own inbox is connected. Mail that is spam, from your own addresses, or already taken by a person is left alone. Rules on the subject and text sort the rest, with no model involved: quote requests and orders get a drafted reply saved in the address's Drafts folder, where a person reads, edits and sends it. Complaints, anything the Firewall marks suspicious, and what the rules cannot place are handed to a person, and Slack hears about it. Out-of-office replies are dropped.\n\n### How to set up\n1. In Pidgeon, create the public address, on pidgeon.email or your own domain.\n2. Make a key with **draft** on that one address and add it as a Pidgeon API credential.\n3. Pick the address in the trigger and the two Pidgeon nodes that need it, add your model and Slack credentials, and edit the rules in **Sort the mail**.\n\n### Requirements\nA Pidgeon account, n8n on a public HTTPS URL, a model credential, and Slack.\n\n### How to customise\nPidgeon holds no draft back by itself: a draft waits because nothing here sends it. To approve in Slack instead, add Slack's Send and Wait after the draft, then **Pidgeon → Send a draft** on Approve, which needs a **send** key.\n\nMore: https://pidgeon.ai/docs/n8n?utm_source=n8n" } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000002", "name": "When mail arrives at the public address", "type": "@pidgeon-ai/n8n-nodes-pidgeon.pidgeonTrigger", "typeVersion": 1, "position": [-280, 0], "webhookId": "6f0c1d2e-0002-4a00-8000-0000000000a1", "parameters": { "event": "messageReceived", "address": "", "includeSpam": false, "fetchMessage": true, "body": "clean" } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000003", "name": "Nobody has taken it", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [-60, 0], "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "not-spam", "leftValue": "={{ $json.spam }}", "rightValue": "", "operator": { "type": "boolean", "operation": "false", "singleValue": true } }, { "id": "not-ours", "leftValue": "={{ $json.from_self }}", "rightValue": "", "operator": { "type": "boolean", "operation": "false", "singleValue": true } }, { "id": "agent-owns-it", "leftValue": "={{ $json.thread_owner_type }}", "rightValue": "agent", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" }, "options": {} } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000004", "name": "Sort the mail", "type": "n8n-nodes-base.switch", "typeVersion": 3.2, "position": [180, -20], "parameters": { "rules": { "values": [ { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "suspicious", "leftValue": "={{ $json.firewall_verdict }}", "rightValue": "suspicious", "operator": { "type": "string", "operation": "equals" } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "Suspicious" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "out-of-office", "leftValue": "={{ /out of office|automatic reply|abwesenheit|autoreply/i.test($json.subject ?? '') }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "Out of office" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "complaint", "leftValue": "={{ /complaint|refund|broken|reklamation|beschwerde/i.test(($json.subject ?? '') + ' ' + ($json.text ?? '')) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "Complaint" }, { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "quote-or-order", "leftValue": "={{ /quote|pricing|price|order|angebot|anfrage|bestellung/i.test(($json.subject ?? '') + ' ' + ($json.text ?? '')) }}", "rightValue": "", "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "renameOutput": true, "outputKey": "Quote or order" } ] }, "options": { "fallbackOutput": "extra", "renameFallbackOutput": "Other" } } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000005", "name": "Draft a reply", "type": "@n8n/n8n-nodes-langchain.chainLlm", "typeVersion": 1.7, "position": [480, -40], "parameters": { "promptType": "define", "text": "=You answer email for a small company's public address. Write a short, polite reply to the email below that confirms what they asked for and says a person will follow up with specifics. Write only the reply. Never promise prices, dates or stock.\n\nFrom: {{ $json.from_name ?? $json.from }}\nSubject: {{ $json.subject }}\n\n{{ $json.text }}" } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000006", "name": "Claude", "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", "typeVersion": 1.3, "position": [480, 180], "parameters": { "model": { "__rl": true, "mode": "list", "value": "claude-sonnet-5-5" }, "options": {} } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000007", "name": "Save it for a person to send", "type": "@pidgeon-ai/n8n-nodes-pidgeon.pidgeon", "typeVersion": 1, "position": [800, -40], "parameters": { "resource": "draft", "operation": "create", "identityId": "", "inReplyTo": "={{ $('When mail arrives at the public address').item.json.message_id }}", "to": "={{ $('When mail arrives at the public address').item.json.from }}", "subject": "", "text": "={{ $json.text }}", "additionalFields": {} } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000008", "name": "Hand it to a person", "type": "@pidgeon-ai/n8n-nodes-pidgeon.pidgeon", "typeVersion": 1, "position": [480, 400], "parameters": { "resource": "thread", "operation": "handToPerson", "threadId": "={{ $json.thread_id }}", "assigneeId": "", "reason": "={{ $json.firewall_verdict === 'suspicious' ? 'The Firewall marked it suspicious' : 'Needs a person: ' + ($json.subject ?? 'no subject') }}" } }, { "id": "6f0c1d2e-0002-4a00-8000-000000000009", "name": "Tell the team", "type": "n8n-nodes-base.slack", "typeVersion": 2.3, "position": [720, 400], "parameters": { "select": "channel", "channelId": { "__rl": true, "mode": "list", "value": "" }, "text": "=A thread needs a person: {{ $json.subject ?? 'no subject' }}, from {{ $json.from }}. It is in Pidgeon, marked as yours to answer.", "otherOptions": {} } } ], "connections": { "When mail arrives at the public address": { "main": [[{ "node": "Nobody has taken it", "type": "main", "index": 0 }]] }, "Nobody has taken it": { "main": [[{ "node": "Sort the mail", "type": "main", "index": 0 }], []] }, "Sort the mail": { "main": [ [{ "node": "Hand it to a person", "type": "main", "index": 0 }], [], [{ "node": "Hand it to a person", "type": "main", "index": 0 }], [{ "node": "Draft a reply", "type": "main", "index": 0 }], [{ "node": "Hand it to a person", "type": "main", "index": 0 }] ] }, "Claude": { "ai_languageModel": [[{ "node": "Draft a reply", "type": "ai_languageModel", "index": 0 }]] }, "Draft a reply": { "main": [[{ "node": "Save it for a person to send", "type": "main", "index": 0 }]] }, "Hand it to a person": { "main": [[{ "node": "Tell the team", "type": "main", "index": 0 }]] } }, "settings": { "executionOrder": "v1" }, "pinData": {} }