AutomationFlowsEmail & Gmail › Summarize Unread Gmail Into a Daily Slack Digest with Claude

Summarize Unread Gmail Into a Daily Slack Digest with Claude

ByCullen Brown @cullen on n8n.io

This workflow runs on a daily schedule, pulls your unread Gmail from the last 24 hours, and uses Anthropic Claude to sort and summarize it by Gmail category — then posts a clean, triage-ready digest to a Slack channel so you can scan your inbox at a glance. Runs automatically on…

Cron / scheduled trigger★★★★☆ complexity10 nodesGmailHTTP RequestSlack
Email & Gmail Trigger: Cron / scheduled Nodes: 10 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #16617 — we link there as the canonical source.

This workflow follows the Gmail → HTTP Request 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
{
  "id": "ugFJyooADTqpOCk4",
  "name": "Summarize unread Gmail into a daily Slack digest with AI",
  "tags": [],
  "nodes": [
    {
      "id": "62032b1c-9672-49b9-bede-72eed9b825ab",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 480,
        "height": 752,
        "content": "## Summarize unread Gmail into a daily Slack digest with AI\n\n### How it works\n\nThis workflow runs once per day at 8am to collect unread Gmail messages from the previous 24 hours. It formats the emails for an Anthropic Claude prompt, requests an AI-generated summary, extracts the response text, and posts the resulting digest to Slack.\n\n### Setup steps\n\n- Configure the Schedule Trigger with the desired daily run time and timezone.\n- Connect Gmail credentials and confirm the Gmail node filters for unread messages from the last 24 hours.\n- Configure the HTTP Request node with Anthropic API authentication, the correct Claude model, and request headers for https://api.anthropic.com/v1/messages.\n- Connect Slack credentials and choose the Slack channel or recipient where the digest should be posted.\n\n### Customization\n\nAdjust the Gmail search query, Claude prompt/model, digest format, schedule time, or Slack destination to match your team's preferred workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "c915533c-bbf9-4704-94c9-daf9c68ba73c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 320,
        "content": "## Collect unread emails\n\nRuns every day at 8am, retrieves unread Gmail messages from the last 24 hours, and formats them into a single categorized text block for the AI prompt."
      },
      "typeVersion": 1
    },
    {
      "id": "a3ea9bc3-3146-400c-8b66-49e0a0b6201f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Generate AI summary\n\nSends the formatted email content to Claude through an HTTP request, then extracts the plain text digest from the API response."
      },
      "typeVersion": 1
    },
    {
      "id": "a29c926b-da40-4fb3-816c-a4774ef63333",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        0
      ],
      "parameters": {
        "color": 7,
        "height": 320,
        "content": "## Post Slack digest\n\nPublishes the final summarized email digest to Slack as the workflow output."
      },
      "typeVersion": 1
    },
    {
      "id": "8a3e6466-837f-4abb-9158-1b873148bcdd",
      "name": "When Daily at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        608,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "45507ccb-71d3-4163-9884-3d10ecc86f28",
      "name": "Fetch Unread Emails from Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        816,
        160
      ],
      "parameters": {
        "filters": {
          "q": "is:unread newer_than:1d"
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "8071b2c5-3c41-4c78-832a-6f1a0b15910b",
      "name": "Prepare AI Email Text",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        160
      ],
      "parameters": {
        "jsCode": "// Combine all fetched emails into one text block for the AI prompt, tagging each with its Gmail category\nconst items = $input.all();\n\nif (items.length === 0) {\n  return [{ json: { emailBlock: 'No new unread emails in the last 24 hours.', hasEmails: false } }];\n}\n\n// Map Gmail's internal category labels to plain-English tags\nconst categoryMap = {\n  CATEGORY_PROMOTIONS: 'Promotions',\n  CATEGORY_SOCIAL: 'Social',\n  CATEGORY_UPDATES: 'Updates',\n  CATEGORY_FORUMS: 'Forums',\n  CATEGORY_PERSONAL: 'Primary'\n};\n\nfunction getCategory(json) {\n  const labelIds = json.labelIds || json.labelIds_resolved || [];\n  if (!Array.isArray(labelIds)) return 'Primary';\n  for (const [labelId, name] of Object.entries(categoryMap)) {\n    if (labelIds.includes(labelId)) return name;\n  }\n  return 'Primary';\n}\n\n// Gmail node output shape varies by n8n version/Simplify setting - check every known shape,\n// falling back to the raw payload.headers array which the Gmail API always includes\nfunction getHeaderValue(json, headerName) {\n  const headers = json.payload?.headers || json.headers;\n  if (Array.isArray(headers)) {\n    const found = headers.find((h) => (h.name || '').toLowerCase() === headerName.toLowerCase());\n    if (found && found.value) return found.value;\n  }\n  return null;\n}\n\nfunction getFrom(json) {\n  if (typeof json.from === 'string' && json.from.trim()) return json.from;\n  if (json.from?.value?.[0]?.address) return json.from.value[0].address;\n  if (json.from?.address) return json.from.address;\n  if (json.From) return json.From;\n  const headerFrom = getHeaderValue(json, 'From');\n  if (headerFrom) return headerFrom;\n  return 'Unknown sender';\n}\n\nfunction getSubject(json) {\n  if (typeof json.subject === 'string' && json.subject.trim()) return json.subject;\n  if (json.Subject) return json.Subject;\n  const headerSubject = getHeaderValue(json, 'Subject');\n  if (headerSubject) return headerSubject;\n  return '(no subject)';\n}\n\n// Count how many unread emails in this batch share the same Gmail thread,\n// so multi-message threads can be flagged instead of listed as separate, unrelated emails\nconst threadCounts = {};\nfor (const item of items) {\n  const threadId = item.json.threadId;\n  if (threadId) threadCounts[threadId] = (threadCounts[threadId] || 0) + 1;\n}\n\nconst lines = items.map((item, i) => {\n  const from = getFrom(item.json);\n  const subject = getSubject(item.json);\n  const snippet = (item.json.snippet || '').replace(/\\n/g, ' ').slice(0, 300);\n  const category = getCategory(item.json);\n  const threadId = item.json.threadId;\n  const threadCount = threadId ? (threadCounts[threadId] || 1) : 1;\n  const threadTag = threadCount > 1 ? ` [Thread: ${threadCount} unread messages in this conversation]` : '';\n  return `${i + 1}. [Category: ${category}]${threadTag} From: ${from}\\n   Subject: ${subject}\\n   Preview: ${snippet}`;\n});\n\nreturn [{ json: { emailBlock: lines.join('\\n\\n'), hasEmails: true, count: items.length } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "44122f7c-8810-4626-a6e2-375ae7d15428",
      "name": "Post to Claude API for Summary",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 4,
      "position": [
        1264,
        160
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ { \"model\": \"claude-sonnet-4-5\", \"max_tokens\": 2500, \"messages\": [ { \"role\": \"user\", \"content\": \"You are summarizing a daily email digest for Slack. Each email below is tagged with its Gmail category (Primary, Promotions, Social, Updates, Forums) and, if applicable, a Thread tag showing how many unread messages share that conversation. Use Slack-friendly formatting (use *text* for bold, and - for bullets, no markdown headers).\\n\\nSender names: the 'From' field may just be a raw email address with no display name (e.g. 'no-reply@southwest.com'). When that happens, infer the likely sender/company from the email address and domain (e.g. 'no-reply@southwest.com' -> 'Southwest Airlines') and use that inferred name instead of showing the raw address or saying 'unknown sender'. Only say 'Unknown sender' if the address itself gives no usable clue at all.\\n\\nFLAGGED / IMPORTANT section (put this at the very top of the digest, above everything else, ONLY if there is something to put there):\\n- Anything tagged with a Thread count of 2 or more unread messages goes here - flag it as 'Part of an active thread (N unread messages)' so it stands out as an ongoing conversation needing attention, not a one-off email.\\n- This section is ONLY for active threads. Do not put anything from Promotions or Social here.\\n- If there is nothing that qualifies for this section, do NOT include the section at all and do NOT say anything like 'nothing flagged' or 'no important emails found' - just omit it entirely and move straight to the category breakdown below.\\n- Anything placed in this Flagged section should still also be skipped in its normal category listing below, so it is not duplicated.\\n\\nHow to handle each remaining category:\\n- Primary: list every one individually (except anything already placed in the Flagged section above). Give sender, one-line topic, and whether it looks like it needs a reply (Yes/No).\\n- Updates and Forums: list individually but keep each to one short line.\\n- Promotions and Social: do NOT list these individually. Just give one summary line, e.g. '+ 14 promotional/social emails, skimmed.' While skimming them, check if any look misfiled - a real reply from a person, a client message, an invoice, a meeting notice, or other genuine correspondence rather than a marketing blast. Do NOT list those individually and do NOT put them in the Flagged section above. Instead, only if you find at least one, add a single short heads-up line at the very bottom of the whole digest, after everything else, like: 'Heads up - a few emails in your Promotions/Social tabs look like they might actually be important. Worth a quick sort through.' If nothing looks misfiled, do not add this line at all - skip it silently, same rule as the Flagged section.\\n\\nPriority order if you start running low on space: 1) The Flagged section is never cut. 2) Primary emails are never cut or compressed - always list every one individually in full. 3) Updates and Forums can switch to shorter one-line entries if space is tight, but never disappear entirely. 4) Promotions and Social: the one-line summary count must ALWAYS appear, even if space is extremely tight - never drop it. The bottom heads-up line is the first thing to drop if space runs out.\\n\\nKeep the whole digest concise. Here are the emails:\\n\\n\" + $json.emailBlock } ] } }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "248ec41f-cf97-4596-9206-50b0b4b466a2",
      "name": "Extract AI Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        160
      ],
      "parameters": {
        "jsCode": "// Pull the plain text summary out of Claude's response\nconst response = $input.first().json;\nconst summaryText = response.content?.[0]?.text || 'No summary returned.';\nreturn [{ json: { summaryText } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e4a48897-288b-4687-ba6b-be53d9c2f74d",
      "name": "Send Digest to Slack Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        1728,
        160
      ],
      "parameters": {
        "text": "=:envelope: *Daily Email Digest*\n\n{{ $json.summaryText }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0BCGJP6KU3",
          "cachedResultName": "emails"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a68cbe30-368b-4ad1-b577-ce964b52e64e",
  "nodeGroups": [],
  "connections": {
    "When Daily at 8am": {
      "main": [
        [
          {
            "node": "Fetch Unread Emails from Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract AI Summary": {
      "main": [
        [
          {
            "node": "Send Digest to Slack Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare AI Email Text": {
      "main": [
        [
          {
            "node": "Post to Claude API for Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Unread Emails from Gmail": {
      "main": [
        [
          {
            "node": "Prepare AI Email Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Claude API for Summary": {
      "main": [
        [
          {
            "node": "Extract AI Summary",
            "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.

Pro

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

About this workflow

This workflow runs on a daily schedule, pulls your unread Gmail from the last 24 hours, and uses Anthropic Claude to sort and summarize it by Gmail category — then posts a clean, triage-ready digest to a Slack channel so you can scan your inbox at a glance. Runs automatically on…

Source: https://n8n.io/workflows/16617/ — 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

This workflow is an automated invoice payment tracking and reminder system for the Polish accounting service iFirma.pl. It monitors unpaid and overdue invoices, then automatically sends escalating rem

HTTP Request, Stop And Error, Slack +1
Email & Gmail

This workflow runs on a schedule to monitor HubSpot deals with upcoming contract expiry dates. It filters deals that are 30, 60, or 90 days away from expiration and processes each one individually. Ba

Gmail, HubSpot, HTTP Request +2
Email & Gmail

This workflow identifies HubSpot deals that have gone untouched for 21+ days and automatically updates their status to Closed Lost. It fetches associated contacts, retrieves their details, and sends p

HubSpot, HTTP Request, Gmail +1
Email & Gmail

This workflow runs daily to pull cloud spend from a billing API, compare it to a Google Sheets rolling baseline, and alert on cost spikes by creating a Jira incident, posting to Slack, emailing Financ

HTTP Request, Google Sheets, Jira +2
Email & Gmail

This workflow automatically monitors solar energy production every 2 hours by fetching data from the Energidataservice API. If the energy output falls below a predefined threshold, it instantly notifi

HTTP Request, Gmail, Google Sheets +1