{
  "name": "summarize_emails",
  "nodes": [
    {
      "parameters": {},
      "id": "execute-workflow-trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare parameters for email fetching\nconst input = $input.all()[0].json || {};\n\n// Default to today's emails if no specific timeframe provided\nconst hoursBack = input.hoursBack || 24;\nconst maxEmails = input.maxEmails || 20;\nconst onlyUnread = input.onlyUnread !== false; // default true\n\n// Calculate date filter (Gmail uses YYYY/MM/DD format)\nconst cutoffDate = new Date();\ncutoffDate.setHours(cutoffDate.getHours() - hoursBack);\nconst dateFilter = cutoffDate.toISOString().split('T')[0].replace(/-/g, '/');\n\n// Build Gmail search query\nlet query = `after:${dateFilter}`;\nif (onlyUnread) {\n  query += ' is:unread';\n}\n// Exclude automated emails\nquery += ' -from:noreply -from:no-reply -from:donotreply';\n\nreturn [{\n  json: {\n    query: query,\n    maxEmails: maxEmails,\n    hoursBack: hoursBack,\n    dateFilter: dateFilter,\n    onlyUnread: onlyUnread\n  }\n}];"
      },
      "id": "prepare-email-query",
      "name": "Prepare Email Query",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "getAll",
        "returnAll": false,
        "limit": "={{ $json.maxEmails }}",
        "filters": {
          "q": "={{ $json.query }}"
        },
        "options": {
          "format": "full",
          "includeSpamTrash": false
        }
      },
      "id": "fetch-emails",
      "name": "Fetch Recent Emails",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        680,
        300
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Process and clean email data for AI summarization\nconst items = $input.all();\n\nif (items.length === 0 || !items[0].json) {\n  return [{\n    json: {\n      emailCount: 0,\n      summary: \"No recent emails found to summarize.\",\n      ok: true,\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString()\n    }\n  }];\n}\n\nconst emails = items.map(item => item.json);\n\n// Helper function to extract plain text from email body\nfunction extractTextFromEmail(email) {\n  let text = '';\n  \n  if (email.payload) {\n    // Handle multipart emails\n    if (email.payload.parts) {\n      for (const part of email.payload.parts) {\n        if (part.mimeType === 'text/plain' && part.body.data) {\n          text += Buffer.from(part.body.data, 'base64').toString('utf-8');\n        }\n      }\n    }\n    // Handle single part emails\n    else if (email.payload.mimeType === 'text/plain' && email.payload.body.data) {\n      text = Buffer.from(email.payload.body.data, 'base64').toString('utf-8');\n    }\n  }\n  \n  // Clean up the text\n  return text\n    .replace(/\\r\\n/g, '\\n')\n    .replace(/\\n{3,}/g, '\\n\\n')\n    .trim()\n    .substring(0, 1000); // Limit to 1000 chars per email\n}\n\n// Helper function to get header value\nfunction getHeader(email, name) {\n  if (!email.payload || !email.payload.headers) return '';\n  const header = email.payload.headers.find(h => h.name.toLowerCase() === name.toLowerCase());\n  return header ? header.value : '';\n}\n\n// Process emails for summarization\nconst processedEmails = emails.map((email, index) => {\n  return {\n    from: getHeader(email, 'From'),\n    subject: getHeader(email, 'Subject'),\n    date: getHeader(email, 'Date'),\n    snippet: email.snippet || '',\n    bodyText: extractTextFromEmail(email)\n  };\n}).filter(email => \n  email.from && email.subject && \n  !email.from.includes('noreply') && \n  !email.from.includes('no-reply')\n);\n\nif (processedEmails.length === 0) {\n  return [{\n    json: {\n      emailCount: 0,\n      summary: \"No relevant emails found to summarize (filtered out automated emails).\",\n      ok: true,\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      needsSummary: false // Explicitly set to false\n    }\n  }];\n}\n\n// Prepare data for Gemini API call\nconst emailsForAI = processedEmails.slice(0, 10); // Limit to 10 emails max\n\nreturn [{\n  json: {\n    emailCount: emailsForAI.length,\n    emails: emailsForAI,\n    needsSummary: true\n  }\n}];"
      },
      "id": "process-emails",
      "name": "Process Email Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "needs-summary-check",
              "leftValue": "={{ $json.needsSummary }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "needs-summary-check",
      "name": "Needs AI Summary?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Prepare Gemini API request for email summarization\nconst data = $input.all()[0].json;\n\n// Add a safeguard in case this node is reached without emails\nif (!data.emails || data.emails.length === 0) {\n  return [{\n    json: {\n      ok: false,\n      error: \"Attempted to summarize with no emails.\",\n      code: \"LOGIC_ERROR\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString()\n    }\n  }];\n}\n\nconst emails = data.emails;\n\n// Build context for Gemini\nconst emailContext = emails.map((email, i) => \n  `EMAIL ${i + 1}:\n` +\n  `From: ${email.from}\n` +\n  `Subject: ${email.subject}\n` +\n  `Date: ${email.date}\n` +\n  `Content: ${email.bodyText || email.snippet}\n` +\n  `---`\n).join('\\n\\n');\n\nconst prompt = `You are an AI assistant helping summarize recent emails. Please analyze these ${emails.length} emails and create a concise, organized summary.\n\n${emailContext}\n\nPlease provide a summary in this format:\n\n# Email Summary (${emails.length} emails)\n\n## \ud83d\udd25 Urgent/Important\n[List any urgent emails requiring immediate attention]\n\n## \ud83d\udcc5 Meetings & Events\n[List any meeting invitations, calendar events, or scheduling]\n\n## \ud83d\udcbc Work Updates\n[List work-related updates, project communications, etc.]\n\n## \ud83d\udce7 Other Communications\n[List other notable emails]\n\n## \ud83d\udcca Summary Stats\n- Total emails: ${emails.length}\n- Time period: Last 24 hours\n- Unread count: [count if available]\n\nKeep it concise but informative. Focus on actionable items and important communications.`;\n\nconst geminiPayload = {\n  contents: [{\n    parts: [{\n      text: prompt\n    }]\n  }],\n  generationConfig: {\n    temperature: 0.3,\n    topK: 40,\n    topP: 0.95,\n    maxOutputTokens: 1000\n  }\n};\n\nreturn [{\n  json: {\n    geminiPayload: geminiPayload,\n    emailCount: emails.length,\n    originalData: data\n  }\n}];"
      },
      "id": "prepare-gemini-request",
      "name": "Prepare Gemini Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        480
      ]
    },
    {
      "parameters": {
        "jsCode": "// Call Gemini API using built-in fetch\nconst inputItems = $input.all();\n\n// Debug: Check if we have input data\nif (!inputItems || inputItems.length === 0) {\n  return [{\n    json: {\n      ok: false,\n      error: \"No input data received\",\n      code: \"NO_INPUT_DATA\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      debug: { inputLength: inputItems ? inputItems.length : 'undefined' }\n    }\n  }];\n}\n\nconst data = inputItems[0].json;\n\n// Safeguard in case this node is reached without proper data\nif (!data || !data.geminiPayload) {\n  return [{\n    json: {\n      ok: false,\n      error: \"No Gemini payload found\",\n      code: \"LOGIC_ERROR\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      debug: { \n        hasData: !!data,\n        hasGeminiPayload: !!(data && data.geminiPayload),\n        dataKeys: data ? Object.keys(data) : 'no data'\n      }\n    }\n  }];\n}\n\nconst geminiPayload = data.geminiPayload;\nconst apiKey = process.env.GEMINI_API_KEY || $env.GEMINI_API_KEY;\nconst url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`;\n\nif (!apiKey) {\n  return [{\n    json: {\n      ok: false,\n      error: \"GEMINI_API_KEY not configured\",\n      code: \"CONFIGURATION_ERROR\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString()\n    }\n  }];\n}\n\ntry {\n  const response = await fetch(url, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify(geminiPayload)\n  });\n\n  if (!response.ok) {\n    throw new Error(`HTTP error! status: ${response.status}`);\n  }\n\n  const responseData = await response.json();\n\n  return [{\n    json: responseData\n  }];\n} catch (error) {\n  console.error('Gemini API error:', error);\n  return [{\n    json: {\n      ok: false,\n      error: \"Failed to generate AI summary\",\n      code: \"GEMINI_API_ERROR\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      fallbackSummary: `Found ${data.emailCount || 0} recent emails. AI summarization temporarily unavailable.`,\n      errorDetails: error.message\n    }\n  }];\n}"
      },
      "id": "call-gemini-api",
      "name": "Call Gemini API",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        480
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process Gemini response and format final output\nconst inputItems = $input.all();\n\n// Debug: Check if we have input data\nif (!inputItems || inputItems.length === 0) {\n  return [{\n    json: {\n      ok: false,\n      error: \"No input data received in format response\",\n      code: \"NO_INPUT_DATA\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString()\n    }\n  }];\n}\n\nconst geminiResponse = inputItems[0].json;\n\n// Check if this is an error response from the Gemini API call\nif (!geminiResponse) {\n  return [{\n    json: {\n      ok: false,\n      error: \"No response data from Gemini API\",\n      code: \"EMPTY_RESPONSE\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString()\n    }\n  }];\n}\n\n// If it's already an error response, pass it through\nif (geminiResponse.ok === false) {\n  return [{ json: geminiResponse }];\n}\n\ntry {\n  // Extract the summary from Gemini response\n  let summary = \"Unable to generate summary.\";\n  \n  if (geminiResponse.candidates && \n      geminiResponse.candidates[0] && \n      geminiResponse.candidates[0].content && \n      geminiResponse.candidates[0].content.parts && \n      geminiResponse.candidates[0].content.parts[0]) {\n    \n    summary = geminiResponse.candidates[0].content.parts[0].text;\n  }\n  \n  // Clean up the summary\n  summary = summary\n    .replace(/\\*\\*/g, '**') // Ensure markdown formatting\n    .replace(/\\n{3,}/g, '\\n\\n') // Clean excessive newlines\n    .trim();\n  \n  return [{\n    json: {\n      ok: true,\n      summary: summary,\n      emailCount: geminiResponse.emailCount || 'unknown',\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      metadata: {\n        generatedBy: \"gemini-2.0-flash-exp\",\n        processingTime: new Date().toISOString()\n      }\n    }\n  }];\n  \n} catch (error) {\n  // Handle Gemini API errors gracefully\n  console.error('Gemini API error:', error);\n  \n  return [{\n    json: {\n      ok: false,\n      error: \"Failed to process AI summary\",\n      code: \"PROCESSING_ERROR\",\n      brick: \"summarize_emails\",\n      timestamp: new Date().toISOString(),\n      fallbackSummary: \"AI summarization temporarily unavailable.\",\n      errorDetails: error.message\n    }\n  }];\n}"
      },
      "id": "format-final-response",
      "name": "Format Final Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1780,
        480
      ]
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Prepare Email Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Email Query": {
      "main": [
        [
          {
            "node": "Fetch Recent Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Recent Emails": {
      "main": [
        [
          {
            "node": "Process Email Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Email Data": {
      "main": [
        [
          {
            "node": "Needs AI Summary?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs AI Summary?": {
      "main": [
        [],
        [
          {
            "node": "Prepare Gemini Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Gemini Request": {
      "main": [
        [
          {
            "node": "Call Gemini API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Gemini API": {
      "main": [
        [
          {
            "node": "Format Final Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": {},
  "tags": [
    "pulse",
    "brick",
    "email",
    "ai"
  ],
  "triggerCount": 0,
  "updatedAt": "2025-08-04T00:00:00.000Z",
  "versionId": "2"
}