AI Email Analysis with DeepSeek - Intelligent Email Processing Assistant
AI Email Analysis with DeepSeek
An intelligent email monitoring and analysis system powered by DeepSeek AI, built entirely in Node-RED. Automatically analyzes incoming emails and provides actionable insights including intent detection, key information extraction, and suggested actions.
Features
- Real-time Email Monitoring: Connects to Gmail via IMAP to monitor new emails every 30 seconds
- AI-Powered Analysis: Uses DeepSeek API to analyze email content and extract key insights
- Intent Detection: Automatically identifies the sender's real intent and needs
- Action Suggestions: Provides specific, professional reply recommendations
- Modern Dashboard: Clean web interface to view all analyzed emails
- History Tracking: Maintains analysis history with configurable retention
- Bilingual Support: Analyzes emails in any language, outputs in English
What's Included
- IMAP email monitoring node
- DeepSeek AI integration for content analysis
- Two-stage analysis (detailed + extraction)
- Web dashboard at
/notifications
- REST API at
/api/notifications
- Delete and clear functions
- Manual test injection node
Prerequisites
- Node-RED installed
- Gmail account with App Password enabled
- DeepSeek API key (Get free key)
node-red-node-email
installed
Quick Setup
- Import this flow into Node-RED
- Configure IMAP node:
- Server:
imap.gmail.com
- Username: Your Gmail address
- Password: Gmail App Password (16-digit)
- Server:
- Update API keys in 2 function nodes:
- "Build DeepSeek Request"
- "Build Extraction Request"
- Replace
YOUR_DEEPSEEK_API_KEY_HERE
with your actual key
- Deploy and visit
http://localhost:1880/notifications
[{"id":"a01d4dab2d3b22de","type":"tab","label":"email AnalysisTest","disabled":false,"info":""},{"id":"2f24670f02bcc57a","type":"e-mail in","z":"a01d4dab2d3b22de","name":"IMAP Email Monitor","protocol":"IMAP","server":"imap.gmail.com","useSSL":true,"autotls":"never","port":"993","authtype":"BASIC","saslformat":false,"token":"YOUR_OAUTH_ACCESS_TOKEN_HERE","box":"INBOX","disposition":"Read","criteria":"UNSEEN","repeat":"30","fetch":"auto","inputs":0,"x":390,"y":480,"wires":[["f0d909af95855e18"]]},{"id":"f0d909af95855e18","type":"function","z":"a01d4dab2d3b22de","name":"Process Email Content","func":"// Extract key email information\nconst emailData = {\n from: msg.from || 'Unknown Sender',\n to: msg.to || 'Unknown Recipient', \n subject: msg.subject || 'No Subject',\n text: msg.text || msg.payload || 'No Content',\n date: msg.date || new Date().toISOString(),\n messageId: msg.messageId || 'unknown'\n};\n\n// Build prompt for DeepSeek, emphasizing current email priority\nconst prompt = `Please analyze the following email, focusing on the latest content sent, with historical correspondence only as context reference:\n\n๐ง Email Details:\nFrom: ${emailData.from}\nTo: ${emailData.to}\nSubject: ${emailData.subject}\nSent Time: ${emailData.date}\nFull Content: ${emailData.text}\n\n๐ฏ Analysis Focus (by priority):\n1. **Current Email Core Content**: Focus on analyzing the sender's latest intent and needs in this email\n2. **Historical Context Reference**: Use historical correspondence to understand background, but don't let historical content interfere with judgment of current email\n3. **Timeliness Assessment**: Prioritize identifying items in current email that need immediate attention\n\n๐ Please provide the following analysis, each within 30 words:\n1. [Intent Analysis]: Main purpose of email and sender's real needs\n2. [Key Information]: Key points to focus on, such as time, place, people, etc.\n3. [Suggested Reply]: Provide specific reply content (concise, professional, targeted)\n\nPlease reply in English, keep it concise, professional and practical, no unnecessary words.\nโ ๏ธ Important: Ensure all analysis focuses on the current email, with historical information only used to supplement background understanding.`;\n\n// Save original email information\nmsg.originalEmail = emailData;\nmsg.analysisPrompt = prompt;\n\n// Add logging\nnode.log(`Received new email - From: ${emailData.from}, Subject: ${emailData.subject}`);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":480,"wires":[["91c0bdb1985b621a"]]},{"id":"91c0bdb1985b621a","type":"function","z":"a01d4dab2d3b22de","name":"Build DeepSeek Request","func":"// Set your DeepSeek API Key\nconst API_KEY = \"YOUR_DEEPSEEK_API_KEY_HERE\"; // Please replace with your actual API Key\n\n// Build request headers\nmsg.headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${API_KEY}`\n};\n\n// Build request body\nmsg.payload = {\n \"model\": \"deepseek-chat\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are an experienced and highly focused email processing expert and business communication consultant. You excel at quickly understanding email intent and providing appropriate, efficient reply suggestions. Emails may be written in Chinese/English, but you need to analyze them uniformly in English for me\"\n },\n {\n \"role\": \"user\",\n \"content\": msg.analysisPrompt\n }\n ],\n \"max_tokens\": 1000,\n \"temperature\": 0.2\n};\n\n// Set request method and URL\nmsg.method = \"POST\";\nmsg.url = \"https://api.deepseek.com/v1/chat/completions\";\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":480,"wires":[["0159391eb73aecde"]]},{"id":"0159391eb73aecde","type":"http request","z":"a01d4dab2d3b22de","name":"Call DeepSeek API","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1130,"y":480,"wires":[["6e0ca75207d5497b"]]},{"id":"6e0ca75207d5497b","type":"function","z":"a01d4dab2d3b22de","name":"Process AI Analysis Results","func":"// Check response status\nif (msg.statusCode !== 200) {\n msg.payload = {\n error: true,\n statusCode: msg.statusCode,\n message: \"AI analysis failed\",\n originalEmail: msg.originalEmail,\n timestamp: new Date().toISOString()\n };\n node.error(`API call failed: ${msg.statusCode}`);\n return msg;\n}\n\n// Extract AI analysis results\nif (msg.payload && msg.payload.choices && msg.payload.choices.length > 0) {\n const aiAnalysis = msg.payload.choices[0].message.content;\n \n // Build final result\n msg.payload = {\n success: true,\n timestamp: new Date().toISOString(),\n emailSummary: {\n from: msg.originalEmail.from,\n subject: msg.originalEmail.subject,\n preview: msg.originalEmail.text.substring(0, 150) + (msg.originalEmail.text.length > 150 ? '...' : ''),\n date: msg.originalEmail.date,\n messageId: msg.originalEmail.messageId\n },\n fullEmailContent: msg.originalEmail.text,\n aiAnalysis: aiAnalysis,\n tokenUsage: msg.payload.usage || {}\n };\n \n // Set notification title\n msg.topic = `๐ง New Email AI Analysis Complete - ${msg.originalEmail.subject}`;\n \n node.log(`AI analysis complete - Email: ${msg.originalEmail.subject}`);\n \n} else {\n msg.payload = {\n error: true,\n message: \"AI response format abnormal\",\n originalEmail: msg.originalEmail,\n rawResponse: msg.payload\n };\n node.error(\"AI response format abnormal\");\n}\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1390,"y":480,"wires":[["4d7b88e832481541","384b04fd6ff1b844"]]},{"id":"384b04fd6ff1b844","type":"function","z":"a01d4dab2d3b22de","name":"Build Extraction Request","func":"// Only process successful analysis results\nif (!msg.payload.success) {\n return null;\n}\n\n// Save original analysis result\nmsg.originalAnalysis = msg.payload;\n\n// Set your DeepSeek API Key\nconst API_KEY = \"YOUR_DEEPSEEK_API_KEY_HERE\"; // Please replace with your actual API Key\n\n// Build request headers\nmsg.headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${API_KEY}`\n};\n\n// Build specialized prompt for extracting intent and action\nconst extractPrompt = `Please extract the three most core pieces of information from the following email analysis result:\n\n${msg.payload.aiAnalysis}\n\nPlease output strictly in the following format, do not add any other content:\nIntent: [One sentence summarizing the real intent of the email]\nKey Information: [Key points to focus on, such as time, place, people, etc.]\nAction: [One sentence explaining what I should do]\n\nRequirements:\n- Each item no more than 30 characters\n- Give conclusions directly, no explanations\n- Express in the most concise English`;\n\n// Build request body\nmsg.payload = {\n \"model\": \"deepseek-chat\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are an information extraction expert, skilled at extracting key information from complex text and expressing it in the most concise language.\"\n },\n {\n \"role\": \"user\",\n \"content\": extractPrompt\n }\n ],\n \"max_tokens\": 200,\n \"temperature\": 0.1\n};\n\n// Set request method and URL\nmsg.method = \"POST\";\nmsg.url = \"https://api.deepseek.com/v1/chat/completions\";\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1650,"y":480,"wires":[["d5f0bc1480ee58e4"]]},{"id":"d5f0bc1480ee58e4","type":"http request","z":"a01d4dab2d3b22de","name":"Call Extraction API","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1870,"y":480,"wires":[["34df146b6425a76d"]]},{"id":"34df146b6425a76d","type":"function","z":"a01d4dab2d3b22de","name":"Process Extraction Results","func":"// Check response status\nif (msg.statusCode !== 200) {\n // Extraction failed, use default values\n msg.payload = {\n intent: \"Email needs processing\",\n keyInfo: \"View detailed content\",\n action: \"View detailed analysis\"\n };\n node.warn(\"AI extraction failed, using default values\");\n} else if (msg.payload && msg.payload.choices && msg.payload.choices.length > 0) {\n const extractedText = msg.payload.choices[0].message.content.trim();\n\n // Parse AI extraction results\n let intent = \"Email needs processing\";\n let keyInfo = \"View detailed content\";\n let action = \"View detailed analysis\";\n\n // Extract intent\n const intentMatch = extractedText.match(/Intent[๏ผ:](.+?)(?=\\n|Key Information|Action|$)/s);\n if (intentMatch) {\n intent = intentMatch[1].trim();\n }\n\n // Extract key information\n const keyInfoMatch = extractedText.match(/Key Information[๏ผ:](.+?)(?=\\n|Action|$)/s);\n if (keyInfoMatch) {\n keyInfo = keyInfoMatch[1].trim();\n }\n\n // Extract action\n const actionMatch = extractedText.match(/Action[๏ผ:](.+?)(?=\\n|$)/s);\n if (actionMatch) {\n action = actionMatch[1].trim();\n }\n\n msg.payload = {\n intent: intent,\n keyInfo: keyInfo,\n action: action,\n extractedText: extractedText\n };\n} else {\n // Response format abnormal, use default values\n msg.payload = {\n intent: \"Email needs processing\",\n keyInfo: \"View detailed content\",\n action: \"View detailed analysis\"\n };\n node.warn(\"AI response format abnormal, using default values\");\n}\n\n// Restore original data for subsequent processing\nmsg.emailSummary = msg.originalAnalysis.emailSummary;\nmsg.fullAnalysis = msg.originalAnalysis.aiAnalysis;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2120,"y":480,"wires":[["62c642a587fa88b5","0264967dc6991f1b"]]},{"id":"62c642a587fa88b5","type":"function","z":"a01d4dab2d3b22de","name":"Save History Record","func":"// Build history record\nconst historyRecord = {\n id: Date.now().toString(),\n timestamp: new Date().toISOString(),\n date: new Date().toLocaleString('en-US'),\n emailInfo: {\n from: msg.emailSummary.from,\n subject: msg.emailSummary.subject,\n preview: msg.emailSummary.preview,\n messageId: msg.emailSummary.messageId\n },\n analysis: {\n intent: msg.payload.intent || 'Not analyzed',\n keyInfo: msg.payload.keyInfo || 'No key information',\n action: msg.payload.action || 'No action suggestion'\n },\n fullAnalysis: msg.fullAnalysis || 'Full analysis unavailable'\n};\n\n// Get existing history records\nlet emailHistory = context.get('emailHistory') || [];\n\n// Add new record to beginning\nemailHistory.unshift(historyRecord);\n\n// Get user-set retention count, default 30 records\nconst maxRecords = global.get('emailHistoryLimit') || 30;\n\n// Maintain specified number of records\nif (emailHistory.length > maxRecords) {\n emailHistory = emailHistory.slice(0, maxRecords);\n}\n\n// Save updated history\ncontext.set('emailHistory', emailHistory);\n\n// Update statistics\nlet stats = context.get('emailStats') || { totalProcessed: 0, lastUpdate: null };\nstats.totalProcessed += 1;\nstats.lastUpdate = new Date().toISOString();\ncontext.set('emailStats', stats);\n\n// Set node status display\nnode.status({\n fill: \"blue\",\n shape: \"dot\",\n text: `Saved ${emailHistory.length}/${maxRecords} records`\n});\n\n// Log record\nnode.log(`History record saved - Currently ${emailHistory.length} records`);\n\n// Pass data to next node\nmsg.historyInfo = {\n saved: true,\n recordId: historyRecord.id,\n totalRecords: emailHistory.length,\n historyRecord: historyRecord\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2380,"y":420,"wires":[["d08599a07daeeae5"]]},{"id":"d08599a07daeeae5","type":"debug","z":"a01d4dab2d3b22de","name":"๐ History Record Status","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"historyInfo","targetType":"msg","statusVal":"","statusType":"auto","x":2670,"y":420,"wires":[]},{"id":"4d7b88e832481541","type":"debug","z":"a01d4dab2d3b22de","name":"๐ง Complete Analysis Results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1690,"y":340,"wires":[]},{"id":"0264967dc6991f1b","type":"function","z":"a01d4dab2d3b22de","name":"Prepare Web Data","func":"// Build email notification data\nconst intent = msg.payload.intent || \"Email needs processing\";\nconst keyInfo = msg.payload.keyInfo || \"View detailed content\";\nconst action = msg.payload.action || \"View detailed analysis\";\nconst from = msg.emailSummary.from || \"Unknown sender\";\nconst subject = msg.emailSummary.subject || \"No subject\";\nconst preview = msg.emailSummary.preview || \"No content preview\";\n\n// Build notification object\nconst notification = {\n id: Date.now(),\n timestamp: new Date().toISOString(),\n displayTime: new Date().toLocaleString('en-US'),\n from: from,\n subject: subject,\n preview: preview,\n analysis: {\n intent: intent,\n keyInfo: keyInfo,\n action: action\n },\n status: 'unread'\n};\n\n// Get existing notifications\nlet notifications = global.get('emailNotifications') || [];\n\n// Add new notification to beginning\nnotifications.unshift(notification);\n\n// Keep recent 20 notifications\nif (notifications.length > 20) {\n notifications = notifications.slice(0, 20);\n}\n\n// Save notification list\nglobal.set('emailNotifications', notifications);\n\n// Pass data to HTTP response\nmsg.payload = {\n success: true,\n notification: notification,\n totalNotifications: notifications.length\n};\n\nnode.log(`New email notification added: ${subject}`);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2350,"y":540,"wires":[["63c3aad808ac5d43"]]},{"id":"63c3aad808ac5d43","type":"debug","z":"a01d4dab2d3b22de","name":"Web Notification Data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2660,"y":540,"wires":[]},{"id":"0fa207fe6b8e0889","type":"inject","z":"a01d4dab2d3b22de","name":"๐งช Manual Email Test","props":[{"p":"from","v":"Customer Zhang CEO <[email protected]>","vt":"str"},{"p":"to","v":"My Email <[email protected]>","vt":"str"},{"p":"subject","v":"Urgent: Project Delay Issue Needs Immediate Discussion","vt":"str"},{"p":"text","v":"Hello,\\n\\nI just received a report from the project team saying our important project may be delayed by 2 weeks. This project is very critical for our Q4 performance, and I'm worried it will affect our partnership.\\n\\nCan we arrange an urgent meeting this afternoon to discuss solutions? We need to quickly develop a response plan.\\n\\nAlso, please tell me the specific reasons for the delay and your remedial measures.\\n\\nLooking forward to your quick reply.\\n\\nCEO Zhang\\nSeptember 22, 2025","vt":"str"},{"p":"date","v":"2025-09-22T14:30:00+08:00","vt":"str"},{"p":"messageId","v":"[email protected]","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":400,"y":680,"wires":[["f0d909af95855e18"]]},{"id":"e5f109b58ad01aa5","type":"http in","z":"a01d4dab2d3b22de","name":"Email Notification Page","url":"/notifications","method":"get","upload":false,"swaggerDoc":"","x":450,"y":920,"wires":[["3ffb007b1f4463a2"]]},{"id":"3ffb007b1f4463a2","type":"template","z":"a01d4dab2d3b22de","name":"Build Modern HTML Page","field":"payload","fieldType":"msg","format":"html","syntax":"plain","template":"<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Email AI Assistant Dashboard</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'-apple-system',BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:#0a0b0d;color:#fff;min-height:100vh}.dashboard{display:flex;min-height:100vh}.sidebar{width:80px;background:#1a1b1e;display:flex;flex-direction:column;align-items:center;padding:20px 0;border-right:1px solid #2a2b2e}.logo{width:40px;height:40px;background:#007acc;border-radius:12px;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:bold;margin-bottom:40px}.nav-item{width:48px;height:48px;background:#2a2b2e;border-radius:12px;display:flex;align-items:center;justify-content:center;margin-bottom:12px;cursor:pointer;transition:all 0.3s ease}.nav-item:hover{background:#3a3b3e}.nav-item.active{background:#007acc}.main-content{flex:1;padding:20px 30px;background:#0f1014}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:30px}.greeting h1{font-size:28px;font-weight:600;margin-bottom:5px}.greeting p{color:#888;font-size:16px}.user-controls{display:flex;align-items:center;gap:15px}.refresh-btn{background:#2a2b2e;border:none;color:#fff;padding:10px 16px;border-radius:8px;cursor:pointer;font-size:14px}.refresh-btn:hover{background:#3a3b3e}.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:20px;margin-bottom:30px}.stat-card{background:#1a1b1e;border-radius:16px;padding:24px;border:1px solid #2a2b2e}.stat-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.stat-title{color:#888;font-size:14px}.stat-value{font-size:32px;font-weight:700;margin-bottom:8px}.stat-change{font-size:14px}.positive{color:#10b981}.negative{color:#f59e0b}.content-section{background:#1a1b1e;border-radius:16px;padding:24px;border:1px solid #2a2b2e}.section-header{display:flex;justify-content:between;align-items:center;margin-bottom:20px}.section-title{font-size:20px;font-weight:600}.controls{display:flex;gap:12px;margin-bottom:20px}.btn{background:#2a2b2e;border:none;color:#fff;padding:8px 16px;border-radius:8px;cursor:pointer;font-size:14px;transition:all 0.3s}.btn:hover{background:#3a3b3e}.btn-primary{background:#007acc}.btn-primary:hover{background:#0066aa}.btn-danger{background:#dc2626}.btn-danger:hover{background:#b91c1c}.email-list{display:flex;flex-direction:column;gap:12px}.email-item{background:#0f1014;border:1px solid #2a2b2e;border-radius:12px;padding:20px;transition:all 0.3s ease}.email-item:hover{border-color:#007acc;transform:translateY(-1px)}.email-header{display:flex;justify-content:space-between;align-items:start;margin-bottom:16px}.email-info h3{font-size:16px;font-weight:600;margin-bottom:4px}.email-info p{color:#888;font-size:14px}.email-time{color:#666;font-size:12px}.email-analysis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-top:16px}.analysis-item{background:#1a1b1e;padding:12px 16px;border-radius:8px;border-left:3px solid #007acc}.analysis-label{color:#888;font-size:12px;margin-bottom:4px}.analysis-value{font-size:14px;line-height:1.4}.delete-btn{background:#dc2626;border:none;color:#fff;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:12px}.delete-btn:hover{background:#b91c1c}.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:20px;padding-top:20px;border-top:1px solid #2a2b2e}.page-info{color:#888;font-size:14px}.empty-state{text-align:center;padding:60px 20px;color:#666}.empty-icon{font-size:48px;margin-bottom:16px}.select-dropdown{background:#2a2b2e;border:1px solid #3a3b3e;color:#fff;padding:8px 12px;border-radius:6px}</style></head><body><div class=\"dashboard\"><div class=\"sidebar\"><div class=\"logo\">EA</div><div class=\"nav-item active\">๐ง</div><div class=\"nav-item\">๐</div><div class=\"nav-item\">โ๏ธ</div></div><div class=\"main-content\"><div class=\"header\"><div class=\"greeting\"><h1>Email AI Assistant</h1><p>Intelligent Email Analysis & Processing Center</p></div><div class=\"user-controls\"><button class=\"refresh-btn\" onclick=\"location.reload()\">๐ Refresh</button></div></div><div class=\"stats-grid\"><div class=\"stat-card\"><div class=\"stat-header\"><span class=\"stat-title\">Total Notifications</span><span>๐ฌ</span></div><div class=\"stat-value\" id=\"totalCount\">0</div><div class=\"stat-change positive\">+0 Today</div></div><div class=\"stat-card\"><div class=\"stat-header\"><span class=\"stat-title\">Processed Today</span><span>โ
</span></div><div class=\"stat-value\" id=\"todayCount\">0</div><div class=\"stat-change positive\" id=\"todayChange\">+0%</div></div><div class=\"stat-card\"><div class=\"stat-header\"><span class=\"stat-title\">Last Update</span><span>๐</span></div><div class=\"stat-value\" style=\"font-size:18px\" id=\"lastUpdate\">--:--</div><div class=\"stat-change\">Real-time Sync</div></div></div><div class=\"content-section\"><div class=\"section-header\"><div class=\"section-title\">Email Notifications</div></div><div class=\"controls\"><select class=\"select-dropdown\" id=\"pageSize\" onchange=\"changePageSize(this.value)\"><option value=\"5\">5/Page</option><option value=\"10\" selected>10/Page</option><option value=\"20\">20/Page</option></select><button class=\"btn btn-danger\" onclick=\"clearAll()\">Clear All</button><button class=\"btn\" onclick=\"prevPage()\">Previous</button><button class=\"btn\" onclick=\"nextPage()\">Next</button></div><div id=\"notifications\" class=\"email-list\"></div><div class=\"pagination\"><div class=\"page-info\" id=\"pageInfo\">Page 1 of 1</div></div></div></div></div><script>let currentPage=1,pageSize=10,allData=[];function loadData(){fetch('/api/notifications').then(r=>r.json()).then(d=>{allData=d.notifications||[];updateStats(d);renderPage()}).catch(e=>console.error('Load error:',e))}function updateStats(data){document.getElementById('totalCount').textContent=data.total||0;document.getElementById('todayCount').textContent=data.todayCount||0;document.getElementById('lastUpdate').textContent=data.lastUpdate||'--:--';const change=data.todayCount>0?`+${Math.round((data.todayCount/data.total)*100)}%`:'0%';document.getElementById('todayChange').textContent=change}function renderPage(){const start=(currentPage-1)*pageSize;const items=allData.slice(start,start+pageSize);const container=document.getElementById('notifications');if(items.length===0){container.innerHTML='<div class=\"empty-state\"><div class=\"empty-icon\">๐ญ</div><h3>No Email Notifications</h3><p>AI analysis results will be displayed here</p></div>';updatePageInfo();return}container.innerHTML=items.map(item=>`<div class=\"email-item\"><div class=\"email-header\"><div class=\"email-info\"><h3>${escapeHtml(item.subject||'No Subject')}</h3><p>From: ${escapeHtml(item.from||'Unknown Sender')}</p></div><div style=\"display:flex;align-items:center;gap:12px\"><span class=\"email-time\">${item.displayTime||''}</span><button class=\"delete-btn\" onclick=\"deleteItem('${item.id}')\">Delete</button></div></div><div class=\"email-analysis\"><div class=\"analysis-item\" style=\"border-left-color:#10b981\"><div class=\"analysis-label\">Intent Analysis</div><div class=\"analysis-value\">${escapeHtml(item.analysis?.intent||'Not analyzed')}</div></div><div class=\"analysis-item\" style=\"border-left-color:#f59e0b\"><div class=\"analysis-label\">Key Information</div><div class=\"analysis-value\">${escapeHtml(item.analysis?.keyInfo||'None')}</div></div><div class=\"analysis-item\" style=\"border-left-color:#dc2626\"><div class=\"analysis-label\">Suggested Action</div><div class=\"analysis-value\">${escapeHtml(item.analysis?.action||'None')}</div></div></div></div>`).join('');updatePageInfo()}function updatePageInfo(){const totalPages=Math.ceil(allData.length/pageSize);document.getElementById('pageInfo').textContent=`Page ${currentPage} of ${totalPages} (${allData.length} records)`}function changePageSize(size){pageSize=parseInt(size);currentPage=1;renderPage()}function prevPage(){if(currentPage>1){currentPage--;renderPage()}}function nextPage(){const totalPages=Math.ceil(allData.length/pageSize);if(currentPage<totalPages){currentPage++;renderPage()}}function deleteItem(id){if(confirm('Are you sure you want to delete this notification?')){fetch('/api/notifications/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id})}).then(r=>r.json()).then(d=>{if(d.success){loadData()}else{alert('Delete failed')}}).catch(e=>{console.error('Delete error:',e);alert('Delete failed')})}}function clearAll(){if(confirm('Are you sure you want to clear all notifications? This action cannot be undone!')){fetch('/api/notifications/clear',{method:'POST'}).then(r=>r.json()).then(d=>{if(d.success){loadData()}else{alert('Clear failed')}}).catch(e=>{console.error('Clear error:',e);alert('Clear failed')})}}function escapeHtml(text){const div=document.createElement('div');div.textContent=text;return div.innerHTML}loadData();setInterval(loadData,30000)</script></body></html>","output":"str","x":750,"y":920,"wires":[["cd8c691595f171a6"]]},{"id":"cd8c691595f171a6","type":"http response","z":"a01d4dab2d3b22de","name":"Return HTML Page","statusCode":"","headers":{},"x":1030,"y":920,"wires":[]},{"id":"89b3981b905f10f2","type":"http in","z":"a01d4dab2d3b22de","name":"Notification API Interface","url":"/api/notifications","method":"get","upload":false,"swaggerDoc":"","x":450,"y":980,"wires":[["54327893aa33d5c6"]]},{"id":"54327893aa33d5c6","type":"function","z":"a01d4dab2d3b22de","name":"Get Notification Data","func":"const notifications = global.get('emailNotifications') || [];\nconst today = new Date().toDateString();\nconst todayCount = notifications.filter(n => new Date(n.timestamp).toDateString() === today).length;\nconst lastUpdate = notifications.length > 0 ? new Date(notifications[0].timestamp).toLocaleTimeString('en-US', {hour: '2-digit', minute: '2-digit'}) : '--:--';\n\nmsg.payload = {\n success: true,\n total: notifications.length,\n todayCount: todayCount,\n lastUpdate: lastUpdate,\n notifications: notifications\n};\n\nmsg.statusCode = 200;\nmsg.headers = {'Content-Type': 'application/json'};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":980,"wires":[["73b09e7f198d2795"]]},{"id":"73b09e7f198d2795","type":"http response","z":"a01d4dab2d3b22de","name":"Return API Data","statusCode":"","headers":{},"x":1020,"y":980,"wires":[]},{"id":"a099543d56385140","type":"http in","z":"a01d4dab2d3b22de","name":"Delete Single Notification","url":"/api/notifications/delete","method":"post","upload":false,"swaggerDoc":"","x":450,"y":1040,"wires":[["9f7eb24ba998371a"]]},{"id":"9f7eb24ba998371a","type":"function","z":"a01d4dab2d3b22de","name":"Delete Single Notification Handler","func":"const { id } = msg.payload;\nif (!id) {\n msg.payload = { success: false, error: 'Missing notification ID' };\n msg.statusCode = 400;\n return msg;\n}\n\nlet notifications = global.get('emailNotifications') || [];\nconst originalLength = notifications.length;\nnotifications = notifications.filter(n => n.id != id);\nglobal.set('emailNotifications', notifications);\n\nif (notifications.length < originalLength) {\n msg.payload = { success: true, message: 'Notification deleted successfully', remainingCount: notifications.length };\n node.log(`Notification deleted successfully: ${id}`);\n} else {\n msg.payload = { success: false, error: 'Specified notification not found' };\n}\n\nmsg.statusCode = 200;\nmsg.headers = { 'Content-Type': 'application/json' };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":1040,"wires":[["9f88010df35759d7"]]},{"id":"7c6bfa6a168a8cc8","type":"http in","z":"a01d4dab2d3b22de","name":"Clear All Notifications","url":"/api/notifications/clear","method":"post","upload":false,"swaggerDoc":"","x":450,"y":1100,"wires":[["649786ea62dd5687"]]},{"id":"649786ea62dd5687","type":"function","z":"a01d4dab2d3b22de","name":"Clear All Notifications Handler","func":"const notifications = global.get('emailNotifications') || [];\nconst clearedCount = notifications.length;\nglobal.set('emailNotifications', []);\n\nmsg.payload = { success: true, message: `All notifications cleared`, clearedCount: clearedCount };\nmsg.statusCode = 200;\nmsg.headers = { 'Content-Type': 'application/json' };\nnode.log(`All notifications cleared: ${clearedCount} items`);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":1100,"wires":[["9f88010df35759d7"]]},{"id":"9f88010df35759d7","type":"http response","z":"a01d4dab2d3b22de","name":"Return Delete Result","statusCode":"","headers":{},"x":1080,"y":1080,"wires":[]},{"id":"1b404f72863f7d0d","type":"global-config","env":[],"modules":{"node-red-node-email":"3.1.0"}}]