AutomationFlowsEmail & Gmail › Shiny Gmail Daily Orchestrator V4.0 Lean

Shiny Gmail Daily Orchestrator V4.0 Lean

Shiny Gmail Daily Orchestrator V4.0 Lean. Uses postgres, gmail, telegram. Scheduled trigger; 25 nodes.

Cron / scheduled trigger★★★★☆ complexity25 nodesPostgresGmailTelegram
Email & Gmail Trigger: Cron / scheduled Nodes: 25 Complexity: ★★★★☆ Added:

This workflow follows the Gmail → Postgres 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": "Shiny Gmail Daily Orchestrator V4.0 Lean",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      },
      "id": "b9ee593d-c820-40d0-ae6b-212c03a02003",
      "name": "Daily Schedule 6AM Manila",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const crypto = require('crypto');\nfunction intEnv(k,d,min,max){const n=Number($env[k]);return Number.isFinite(n)?Math.min(Math.max(Math.trunc(n),min),max):d;}\nconst account=$env.GMAIL_ACCOUNT||'primary';\nconst required=['GEMINI_API_KEY','TELEGRAM_CHAT_ID'];\nconst missing=required.filter(k=>!$env[k]);\nreturn [{json:{workflowName:'Shiny Gmail Daily Orchestrator V4.0 Lean',runId:crypto.randomUUID(),account,startedAt:new Date().toISOString(),envOk:missing.length===0,missingEnv:missing,batchSize:intEnv('GMAIL_BATCH_SIZE',50,1,500),waitSeconds:intEnv('WAIT_BETWEEN_EMAILS',5,0,300),categories:['priority','finances','accounts-subscriptions','account-security','unimportant','needs-review'],processedLabel:'processed'}}];"
      },
      "id": "bb73e54d-1381-4f77-a369-80d41f208227",
      "name": "Build Context And Validate Env",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.envOk }}",
              "value2": true
            }
          ]
        }
      },
      "id": "50d9a32c-ad6a-4679-b890-95229e9c1741",
      "name": "Env OK?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const c=$('Build Context And Validate Env').first().json;return [{json:{chatId:$env.TELEGRAM_CHAT_ID,text:`\ud83d\udeab <b>Shiny Gmail V4.0 blocked</b>\n\nMissing env var(s): <code>${(c.missingEnv||[]).join(', ')}</code>\nNo emails were touched.`}}];"
      },
      "id": "57c4ea99-dadc-4a79-a880-0ffc1298bbf4",
      "name": "Build Env Error Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        160
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{\"WITH expired AS (UPDATE gmail_workflow_runs SET status='timed_out', error_message='Stale run auto-cleaned by next daily run.', finished_at=NOW(), updated_at=NOW() WHERE account='\" + $('Build Context And Validate Env').first().json.account.replace(/'/g,\"''\") + \"' AND status='running' AND started_at <= NOW() - INTERVAL '30 minutes' RETURNING run_id), active AS (SELECT run_id, started_at FROM gmail_workflow_runs WHERE account='\" + $('Build Context And Validate Env').first().json.account.replace(/'/g,\"''\") + \"' AND status='running' AND started_at > NOW() - INTERVAL '30 minutes' ORDER BY started_at DESC LIMIT 1), last_done AS (SELECT finished_at, completed_count FROM gmail_workflow_runs WHERE account='\" + $('Build Context And Validate Env').first().json.account.replace(/'/g,\"''\") + \"' AND status LIKE 'completed%' AND finished_at IS NOT NULL ORDER BY finished_at DESC LIMIT 1) SELECT EXISTS(SELECT 1 FROM active) AS active_run, (SELECT COUNT(*) FROM expired) AS expired_count, (SELECT run_id FROM active) AS active_run_id, (SELECT finished_at FROM last_done) AS last_completed_at, (SELECT completed_count FROM last_done) AS last_completed_count;\"}}",
        "options": {
          "connectionTimeout": 10,
          "delayClosingIdleConnection": 5
        }
      },
      "id": "8074ef0c-ffe2-4d0a-b992-744552f277ef",
      "name": "Simple Run Guard Query",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        660,
        -80
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const c=$('Build Context And Validate Env').first().json;const row=$input.first()?.json||{};const dbError=!!row.error;return [{json:{...c,activeRun:dbError?true:Boolean(row.active_run),activeRunId:row.active_run_id||null,expiredStaleRuns:Number(row.expired_count||0),lastCompletedAt:row.last_completed_at||null,lastCompletedCount:row.last_completed_count??null,guardDbError:dbError,guardErrorMessage:row.error?.message||null}}];"
      },
      "id": "cf76a126-4000-4776-8497-afcc75e2c19f",
      "name": "Evaluate Run Guard",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        -80
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.activeRun }}",
              "value2": true
            }
          ]
        }
      },
      "id": "3e882314-83b2-40ef-b119-b8048dc7725d",
      "name": "Active Run?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1100,
        -80
      ]
    },
    {
      "parameters": {
        "jsCode": "const j=$input.first().json;const reason=j.guardDbError?'Run guard database check failed; fail-safe skip activated.':`Another run is already active${j.activeRunId?` (${j.activeRunId})`:''}.`;let text=`\u23ed\ufe0f <b>Shiny Gmail V4.0 skipped</b>\n\n${reason}\n\nNo emails were touched.`;if(j.expiredStaleRuns)text+=`\n\n\u26a0\ufe0f Auto-cleaned ${Number(j.expiredStaleRuns)} stale run(s) from a previous crash before skipping.`;return [{json:{chatId:$env.TELEGRAM_CHAT_ID,text}}];"
      },
      "id": "7f6cb461-3db4-4db4-a064-4984f88f80d5",
      "name": "Build Skipped Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        60
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{\"INSERT INTO gmail_workflow_runs (run_id, account, status, started_at, created_at, updated_at) VALUES ('\" + $json.runId.replace(/'/g,\"''\") + \"','\" + $json.account.replace(/'/g,\"''\") + \"','running', NOW(), NOW(), NOW()) RETURNING run_id, account, status, started_at;\"}}",
        "options": {
          "connectionTimeout": 10,
          "delayClosingIdleConnection": 5
        }
      },
      "id": "30a4531d-1e0c-4cbf-b92a-ffb26d49ce2e",
      "name": "Mark Run Started",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1320,
        -180
      ],
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "resource": "label",
        "operation": "getAll",
        "returnAll": true
      },
      "id": "f37ee71a-d7f5-4a1a-a031-2a4d2f74fce9",
      "name": "Get Gmail Labels",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1540,
        -180
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{ \"SELECT sender_pattern, match_type, rule_type, priority, enabled, created_at FROM gmail_sender_rules WHERE account='\" + $('Build Context And Validate Env').first().json.account.replace(/'/g, \"''\") + \"' AND enabled = true ORDER BY priority ASC, CASE WHEN match_type='exact' THEN 0 ELSE 1 END, created_at DESC;\" }}",
        "options": {
          "connectionTimeout": 10,
          "delayClosingIdleConnection": 5
        }
      },
      "id": "243af4ae-c5e8-4170-b7db-0c713b34619a",
      "name": "Get Sender Rules",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1760,
        -180
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "getAll",
        "returnAll": false,
        "limit": "={{ $('Build Context And Validate Env').first().json.batchSize }}",
        "filters": {
          "q": "is:inbox -label:processed"
        }
      },
      "id": "b0aa08ed-d604-4203-abd0-cb0c65bcf941",
      "name": "Fetch Inbox Emails",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1980,
        -180
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const context=$('Build Context And Validate Env').first().json;\nconst guard=(()=>{try{return $('Evaluate Run Guard').first().json}catch{return {}}})();\nfunction all(name){try{return $(name).all().map(i=>i.json)}catch{return []}}\nfunction errorCode(err){const msg=String(err?.message||err||'');const m=msg.match(/status\\s*code\\s*(\\d{3})/i)||msg.match(/\\b(401|403|429|5\\d\\d)\\b/);return Number(err?.httpCode||err?.statusCode||err?.code||(m?m[1]:0));}\nfunction gmailHint(err,what){const c=errorCode(err);if(c===401)return `${what}: Gmail credential expired. Re-authorize in n8n credentials.`;if(c===403)return `${what}: Gmail API access forbidden. Check OAuth scopes.`;if(c===429)return `${what}: Gmail API rate limit hit. Will retry on next run.`;if(c>=500)return `${what}: Gmail server error. Transient \u2014 will retry on next run.`;return `${what}: Unexpected Gmail error (${c||'unknown'}).`;}\nfunction empty(status,msg,extra={}){return [{json:{noWork:true,runId:context.runId,account:context.account,startedAt:context.startedAt,status,totalDiscovered:0,completed:0,failed:0,needsReview:0,batchSaturated:false,categoryCounts:{},sourceCounts:{},confidenceBands:{high:0,medium:0,review:0,failed:0},inlineDigest:[],reviewDetails:[],failedDetails:[],warningDetails:[],warningCount:0,reviewReasonCounts:{},ruleSuggestions:[],ruleWarning:null,lastCompletedAt:guard.lastCompletedAt||null,lastCompletedCount:guard.lastCompletedCount??null,errorMessage:msg,...extra}}]}\ntry{\n const runStarted=$('Mark Run Started').first()?.json||{};\n function extractEmail(raw){const m=String(raw||'').match(/[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}/i);return m?m[0].toLowerCase():'';}\n function getHeader(email,name){const h=(email.payload?.headers||email.headers||[]).find(x=>String(x.name||'').toLowerCase()===name.toLowerCase());return h?.value||'';}\n function domainOf(sender){return String(sender||'').split('@').pop().toLowerCase()}\n function parseDateMs(s){const ms=new Date(s||0).getTime();return Number.isFinite(ms)?ms:0;}\n function ruleCategory(rule){const t=String(rule.rule_type||'').toLowerCase();if(t==='whitelist')return'priority';if(t==='blacklist')return'unimportant';if(['receipt','force_receipts','finance','force_finance'].includes(t))return'finances';if(['review','force_review'].includes(t))return'needs-review';return null;}\n function ruleToSource(rule){const t=String(rule.rule_type||'').toLowerCase();if(t==='whitelist')return'whitelist';if(t==='blacklist')return'blacklist';if(['receipt','force_receipts','finance','force_finance'].includes(t))return'force_finance';if(['review','force_review'].includes(t))return'force_review';return rule.rule_type||'rule_matched';}\n function ruleSort(a,b){const p=Number(a.priority??100)-Number(b.priority??100);if(p!==0)return p;const mt=(a.match_type==='exact'?0:1)-(b.match_type==='exact'?0:1);if(mt!==0)return mt;return String(b.created_at||'').localeCompare(String(a.created_at||''));}\n function findRule(sender,rules){const d=domainOf(sender);for(const r of rules.filter(x=>x.enabled!==false&&x.enabled!=='false').sort(ruleSort)){const p=String(r.sender_pattern||'').toLowerCase().trim();const mt=String(r.match_type||'exact').toLowerCase();if(!p)continue;if(mt==='exact'&&sender===p)return r;if(mt==='domain'&&(d===p||d.endsWith('.'+p)))return r;}return null;}\n function rank(item){const c=item.json.ruleCategory;if(c==='priority')return 0;if(c==='finances')return 1;if(c==='needs-review')return 2;if(c==='unimportant')return 3;if(item.json.needsGemini)return 4;return 5;}\n if(runStarted.error||!runStarted.run_id)return empty('failed',`Run record could not be created. Gmail label/email fetch may have occurred, but no Gmail labels/archive mutations were performed.${runStarted.error?.message?' Reason: '+String(runStarted.error.message).slice(0,300):''}`,{runStartError:true});\n const labelRows=all('Get Gmail Labels');const labelErr=labelRows.find(r=>r.error);if(labelErr)return empty('failed',`${gmailHint(labelErr.error,'Could not load Gmail labels')} No Gmail mutations were performed.`,{labelError:true});\n const fetchRows=all('Fetch Inbox Emails');const fetchErr=fetchRows.find(r=>r.error);if(fetchErr)return empty('failed',`${gmailHint(fetchErr.error,'Could not fetch inbox')} No Gmail mutations were performed.`,{gmailFetchError:true});\n const labelMap={};for(const l of labelRows){const name=l.name||l.Name||l.labelName||'';const id=l.id||l.labelId||l.Id||'';if(name&&id)labelMap[name]=id;}\n const missing=[...context.categories,context.processedLabel].filter(x=>!labelMap[x]);if(missing.length)return empty('failed',`Missing Gmail label(s): ${missing.join(', ')}. Create them at: https://mail.google.com/mail/u/0/#settings/labels`,{labelError:true});\n const ruleRows=all('Get Sender Rules');const ruleDbError=ruleRows.some(r=>r.error);const rules=ruleDbError?[]:ruleRows;const ruleWarning=ruleDbError?'Sender rules unavailable; processed using AI/rule-free path.':null;\n const seen=new Set(),out=[];\n for(const email of fetchRows){const messageId=email.id||email.messageId||email.gmail_message_id;if(!messageId||seen.has(messageId))continue;seen.add(messageId);const fromRaw=email.From||email.from||getHeader(email,'from');const toRaw=email.To||email.to||getHeader(email,'to')||'';const ccRaw=email.Cc||email.cc||getHeader(email,'cc')||'';const sender=extractEmail(fromRaw);const subject=email.Subject||email.subject||getHeader(email,'subject')||'(no subject)';const subjectClean=String(subject||'').trim();const snippet=String(email.snippet||'').slice(0,300);const barrenMetadata=!sender&&(!subjectClean||subjectClean==='(no subject)')&&!snippet.trim();const matched=sender?findRule(sender,rules):null;const category=matched?ruleCategory(matched):(barrenMetadata?'needs-review':null);const source=matched?ruleToSource(matched):(barrenMetadata?'metadata_empty':null);const date=email.Date||email.date||getHeader(email,'date')||'';out.push({json:{runId:context.runId,account:context.account,startedAt:context.startedAt,totalDiscovered:seen.size,messageId,threadId:email.threadId||email.thread_id||null,sender,fromRaw:String(fromRaw||'').trim(),toRaw:String(toRaw).slice(0,200),ccRaw:String(ccRaw).slice(0,150),subject:String(subject).slice(0,200),date,snippet,labelMap,processedLabelId:labelMap[context.processedLabel],ruleCategory:category,ruleSource:source,confidence:barrenMetadata?0:undefined,reasoning:barrenMetadata?'No usable sender, subject, or snippet metadata was available.':undefined,needsGemini:!category,ruleMode:ruleDbError?'ai_only_rules_unavailable':'rules_enabled',ruleWarning,lastCompletedAt:guard.lastCompletedAt||null,lastCompletedCount:guard.lastCompletedCount??null}})}\n if(!out.length)return empty('completed',null,{ruleWarning});out.sort((a,b)=>{const r=rank(a)-rank(b);if(r!==0)return r;if(a.json.needsGemini&&b.json.needsGemini){const ta=a.json.threadId||'',tb=b.json.threadId||'';if(ta!==tb)return ta<tb?-1:1;}return parseDateMs(a.json.date)-parseDateMs(b.json.date);});const total=out.length;const batchSaturated=total>=context.batchSize;for(const item of out){item.json.totalDiscovered=total;item.json.batchSaturated=batchSaturated;}return out;\n}catch(e){return empty('failed',`Normalization failed before Gmail mutations. ${String(e.message||e).slice(0,300)}`,{normalizationError:true});}"
      },
      "id": "f173d740-0fd3-4b38-bf0d-03b641d73956",
      "name": "Normalize Emails And Apply Rules",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        -180
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ Boolean($json.noWork) }}",
              "value2": true
            }
          ]
        }
      },
      "id": "ec0554cd-e643-4e9c-8de9-3c5fcad40143",
      "name": "No Work?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        2420,
        -180
      ]
    },
    {
      "parameters": {
        "jsCode": "const j=$input.first().json;return [{json:{runId:j.runId,account:j.account,startedAt:j.startedAt,finishedAt:new Date().toISOString(),status:j.status||'completed',totalDiscovered:j.totalDiscovered||0,completed:j.completed||0,failed:j.failed||0,needsReview:j.needsReview||0,batchSaturated:!!j.batchSaturated,categoryCounts:j.categoryCounts||{},sourceCounts:j.sourceCounts||{},confidenceBands:j.confidenceBands||{high:0,medium:0,review:0,failed:0},inlineDigest:j.inlineDigest||[],reviewDetails:j.reviewDetails||[],failedDetails:j.failedDetails||[],warningDetails:j.warningDetails||[],warningCount:j.warningCount||0,reviewReasonCounts:j.reviewReasonCounts||{},ruleSuggestions:j.ruleSuggestions||[],ruleWarning:j.ruleWarning||null,lastCompletedAt:j.lastCompletedAt||null,lastCompletedCount:j.lastCompletedCount??null,errorMessage:j.errorMessage||null,labelError:!!j.labelError,runStartError:!!j.runStartError,gmailFetchError:!!j.gmailFetchError,normalizationError:!!j.normalizationError}}];"
      },
      "id": "b1db2b00-32a4-4480-904f-566e4962d3a2",
      "name": "Build No Work Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        -40
      ]
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "dcff46fb-5981-41db-bb17-ffe519dcf64c",
      "name": "Split Emails",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        2640,
        -300
      ]
    },
    {
      "parameters": {
        "workflowId": "REPLACE_WITH_WORKFLOW_B_ID",
        "options": {}
      },
      "id": "7275fe04-8556-43f4-9482-28733b6a66db",
      "name": "Call Single Email Processor",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.1,
      "position": [
        2860,
        -420
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const input=$input.first()?.json||{};const parent=(()=>{try{return $('Split Emails').first().json}catch{return {}}})();function isAiSource(s){return ['ai','ai_low_confidence','ai_failed'].includes(String(s||''));}function waitMultiplierFrom(obj){const err=String(obj.errorMessage||obj.error?.message||'').toLowerCase();return(err.includes('429')||err.includes('503')||err.includes('unavailable')||err.includes('resource_exhausted')||err.includes('rate'))?3:1;}if(input.error)return[{json:{status:'failed',messageId:parent.messageId||input.messageId||null,sender:parent.sender||null,subject:parent.subject||null,category:'needs-review',source:'child_execution_failed',confidence:0,reasoning:'Child workflow execution failed.',errorMessage:String(input.error.message||input.error||'Unknown child error').slice(0,500),failureStage:'child_execution_failed',totalDiscovered:parent.totalDiscovered||0,batchSaturated:!!parent.batchSaturated,ruleWarning:parent.ruleWarning||null,usedGeminiBudget:Boolean(parent.needsGemini),dynamicWaitMultiplier:waitMultiplierFrom(input)}}];const usedGeminiBudget=Boolean(parent.needsGemini||isAiSource(input.source));return[{json:{...input,totalDiscovered:parent.totalDiscovered||input.totalDiscovered||0,batchSaturated:!!(parent.batchSaturated||input.batchSaturated),ruleWarning:parent.ruleWarning||input.ruleWarning||null,usedGeminiBudget,dynamicWaitMultiplier:waitMultiplierFrom(input)}}];"
      },
      "id": "4ca3c517-0cc1-4972-8316-14333c1e0968",
      "name": "Normalize Child Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3080,
        -420
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ Boolean($json.usedGeminiBudget) }}",
              "value2": true
            }
          ]
        }
      },
      "id": "b1bb743c-109d-4bee-bd14-255c48cfc88b",
      "name": "Used Gemini?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        3300,
        -420
      ]
    },
    {
      "parameters": {
        "amount": "={{ $('Build Context And Validate Env').first().json.waitSeconds * ($json.dynamicWaitMultiplier || 1) }}",
        "unit": "seconds"
      },
      "id": "a96f425e-6037-412e-ba77-e16b3e748bc7",
      "name": "Wait Between Emails",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        3520,
        -500
      ]
    },
    {
      "parameters": {
        "jsCode": "function countBy(arr,key){const o={};for(const r of arr){const v=r[key]||'unknown';o[v]=(o[v]||0)+1;}return o;}function reviewHint(r){if(!r.sender)return'missing sender';if(r.source==='metadata_empty')return'missing metadata';if(r.source==='ledger_reuse')return'ledger-inherited';if(r.source==='ai_low_confidence')return'low AI confidence';if(r.source==='ai_failed')return'AI unavailable';if(r.source==='force_review')return'operator rule';if(r.source==='thread_reuse')return'thread-inherited';return'ambiguous metadata';}\nconst context=$('Build Context And Validate Env').first().json;const guard=(()=>{try{return $('Evaluate Run Guard').first().json}catch{return {}}})();const results=$input.all().map(i=>i.json||{}).filter(r=>r.messageId||r.status);const completedItems=[],failedItems=[],reviewItems=[],warningItems=[];let ledgerWarningCount=0,ruleWarning=null,maxDiscovered=results.length,batchSaturated=results.some(r=>r.batchSaturated);const confidenceBands={high:0,medium:0,review:0,failed:0};\nfor(const r of results){maxDiscovered=Math.max(maxDiscovered,Number(r.totalDiscovered||0));const status=String(r.status||'');const done=status.startsWith('completed');if(done)completedItems.push(r);else failedItems.push(r);if(status==='completed_with_warnings')warningItems.push(r);if(done&&r.category==='needs-review')reviewItems.push(r);if(r.ledgerWarning)ledgerWarningCount++;if(!ruleWarning&&r.ruleWarning)ruleWarning=r.ruleWarning;if(['ai','ai_low_confidence','ai_failed'].includes(r.source)){const c=Number(r.confidence||0);if(r.source==='ai_failed')confidenceBands.failed++;else if(r.category==='needs-review')confidenceBands.review++;else if(c>=0.9)confidenceBands.high++;else if(c>=0.8)confidenceBands.medium++;}}\nconst inlineDigest=completedItems.slice(0,7).map(r=>({messageId:r.messageId||null,sender:r.sender||'unknown',subject:String(r.subject||'(no subject)').slice(0,80),category:r.category||null,source:r.source||null,confidence:r.confidence??0}));\nconst failedDetails=failedItems.slice(0,5).map(r=>({messageId:r.messageId||null,sender:r.sender||'unknown',subject:String(r.subject||'(no subject)').slice(0,80),category:r.category||null,source:r.source||null,failureStage:r.failureStage||null,errorMessage:r.errorMessage||null}));const reviewDetails=reviewItems.slice(0,5).map(r=>({messageId:r.messageId||null,sender:r.sender||'unknown',subject:String(r.subject||'(no subject)').slice(0,80),source:r.source||'unknown',confidence:r.confidence??0,hint:reviewHint(r),reasoning:r.reasoning||'No reasoning.'}));const reviewReasonCounts={},runReviewSenders={};for(const r of reviewItems){const hint=reviewHint(r);reviewReasonCounts[hint]=(reviewReasonCounts[hint]||0)+1;if(r.sender)runReviewSenders[r.sender]=(runReviewSenders[r.sender]||0)+1;}const ruleSuggestions=Object.entries(runReviewSenders).filter(([s,c])=>c>=2).sort((a,b)=>b[1]-a[1]).slice(0,3).map(([sender,count])=>({sender,count,hint:`${count}\u00d7 needs-review \u2192 /review ${sender}, /receipt ${sender}, or /whitelist ${sender}`}));const warningDetails=warningItems.slice(0,3).map(r=>({messageId:r.messageId||null,sender:r.sender||'unknown',subject:String(r.subject||'(no subject)').slice(0,80),errorMessage:r.errorMessage||'Completed with warning.'}));return[{json:{runId:context.runId,account:context.account,startedAt:context.startedAt,finishedAt:new Date().toISOString(),status:failedItems.length?'completed_with_failures':(warningItems.length?'completed_with_warnings':'completed'),totalDiscovered:maxDiscovered,completed:completedItems.length,failed:failedItems.length,needsReview:reviewItems.length,batchSaturated,categoryCounts:countBy(completedItems,'category'),sourceCounts:countBy(completedItems,'source'),confidenceBands,ledgerWarningCount,warningCount:warningItems.length,reviewReasonCounts,ruleSuggestions,ruleWarning,lastCompletedAt:guard.lastCompletedAt||null,lastCompletedCount:guard.lastCompletedCount??null,inlineDigest,reviewDetails,failedDetails,warningDetails}}];"
      },
      "id": "54389f25-d735-40c1-bf94-62c304661f78",
      "name": "Build Final Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2860,
        -180
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "={{\"UPDATE gmail_workflow_runs SET status='\"+String($json.status||'completed').replace(/'/g,\"''\")+\"', total_discovered=\"+Number($json.totalDiscovered||0)+\", completed_count=\"+Number($json.completed||0)+\", failed_count=\"+Number($json.failed||0)+\", review_count=\"+Number($json.needsReview||0)+\", error_message=\"+($json.errorMessage?\"'\"+String($json.errorMessage).slice(0,500).replace(/'/g,\"''\")+\"'\":\"NULL\")+\", finished_at=NOW(), updated_at=NOW() WHERE run_id='\"+String($json.runId).replace(/'/g,\"''\")+\"' RETURNING status, finished_at;\"}}",
        "options": {
          "connectionTimeout": 10,
          "delayClosingIdleConnection": 5
        }
      },
      "id": "fe1efb20-ddfc-43a7-9ac0-ac7dcc30bae8",
      "name": "Mark Run Finished",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        3080,
        -40
      ],
      "onError": "continueRegularOutput",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "function getNode(name){try{return $(name).first().json}catch{return null}}function h(s){return String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}function clampTelegram(text,max=3900){if(text.length<=max)return text;let cut=text.slice(0,max-180);const lastLt=cut.lastIndexOf('<'),lastGt=cut.lastIndexOf('>');if(lastLt>lastGt)cut=cut.slice(0,lastLt);for(const tag of ['b','i','code','pre','a','s','u']){const opens=(cut.match(new RegExp(`<${tag}[\\\\s>]`,'gi'))||[]).length;const closes=(cut.match(new RegExp(`</${tag}>`,'gi'))||[]).length;for(let i=0;i<opens-closes;i++)cut+=`</${tag}>`;}return cut+'\\n\\n\u2026truncated. Use /recent or DB for full details.';}function fmtTime(ts){try{return new Date(ts).toLocaleString('en-PH',{timeZone:'Asia/Manila',hour12:true})}catch{return String(ts||'unknown')}}function relativeTime(dateStr){const ms=Date.now()-new Date(dateStr||0).getTime();if(!dateStr||!Number.isFinite(ms))return'unknown';const min=Math.floor(ms/60000);if(min<1)return'just now';if(min<60)return`${min}m ago`;const hr=Math.floor(min/60);if(hr<24)return`${hr}h ago`;const d=Math.floor(hr/24);if(d<7)return`${d}d ago`;return`${Math.floor(d/7)}w ago`;}function duration(a,b){const ms=new Date(b).getTime()-new Date(a).getTime();if(!Number.isFinite(ms)||ms<0)return'unknown';const s=Math.round(ms/1000),m=Math.floor(s/60),r=s%60;if(m>=60){const h=Math.floor(m/60),rm=m%60;return `${h}h ${rm}m ${r}s`;}return m?`${m}m ${r}s`:`${r}s`}function emojiCat(c){const e={'priority':'\ud83d\udd34','finances':'\ud83d\udcb0','accounts-subscriptions':'\ud83d\udd14','account-security':'\ud83d\udd10','unimportant':'\ud83d\uddd1','needs-review':'\ud83d\udd0e'};return `${e[c]||''} ${c||'unknown'}`.trim();}function countsLine(obj){const order=['priority','finances','accounts-subscriptions','account-security','unimportant','needs-review'];const parts=order.filter(k=>obj?.[k]).map(k=>`${emojiCat(k)}: ${obj[k]}`);return parts.length?parts.join(' | '):'none'}function sourceLine(src){const order=['whitelist','blacklist','force_finance','force_review','thread_reuse','ledger_reuse','metadata_empty','ai','ai_low_confidence','ai_failed'];const seen=new Set(),parts=[];for(const k of order){if(src?.[k]){parts.push(`${k}: ${src[k]}`);seen.add(k);}}for(const [k,v] of Object.entries(src||{})){if(!seen.has(k))parts.push(`${k}: ${v}`);}return parts.length?parts.join(' | '):'none'}function link(id){return id?`https://mail.google.com/mail/u/0/#all/${encodeURIComponent(id)}`:null}\nconst summary=getNode('Build Final Summary')||getNode('Build No Work Summary')||$input.first().json||{};const db=$input.first().json||{};const guard=getNode('Evaluate Run Guard')||{};const dbError=!!db.error;\nif(!summary.errorMessage&&!summary.labelError&&!summary.runStartError&&!summary.gmailFetchError&&!summary.normalizationError&&(summary.totalDiscovered||0)===0&&!summary.ruleWarning&&!guard.expiredStaleRuns){let condensed=`\u2705 \ud83e\uddf9 No new emails \u00b7 ${h(duration(summary.startedAt,summary.finishedAt||new Date().toISOString()))}`;if(summary.lastCompletedAt){condensed+=` \u00b7 Last activity: ${h(relativeTime(summary.lastCompletedAt))}`;if(summary.lastCompletedCount!=null)condensed+=` (${summary.lastCompletedCount})`;const age=Date.now()-new Date(summary.lastCompletedAt).getTime();if(Number.isFinite(age)&&age>48*3600*1000)condensed+=`\\n\u26a0\ufe0f No completed run in 48+ hours \u2014 verify Gmail credentials.`;}return[{json:{chatId:$env.TELEGRAM_CHAT_ID,text:condensed}}];}\nconst icon=summary.status==='failed'||dbError?'\u26a0\ufe0f':summary.failed?'\u26a0\ufe0f':'\u2705';let verdict='\u2705 Clean run \u2014 no action needed.';if(summary.failed)verdict='\u26a0\ufe0f Processing failures need log review.';else if(summary.warningCount)verdict='\u26a0\ufe0f Processed with warnings; check linked emails.';else if(summary.needsReview)verdict=`\ud83d\udd0e Review ${summary.needsReview} uncertain email(s).`;else if(summary.ruleWarning)verdict='\ud83d\udeab Automation degraded \u2014 sender rules unavailable.';let text=`${icon} <b>\ud83e\uddf9 Shiny Gmail Daily Run</b>\\n<b>Verdict:</b> ${h(verdict)}\\n\\n`;\ntext+=`<b>Status:</b> ${h(summary.status||'completed')}\\n<b>Started:</b> ${h(fmtTime(summary.startedAt))}\\n<b>Duration:</b> ${h(duration(summary.startedAt,summary.finishedAt||new Date().toISOString()))}\\n`;if(summary.lastCompletedAt){text+=`<b>Last completed run:</b> ${h(relativeTime(summary.lastCompletedAt))}${summary.lastCompletedCount!=null?` (${summary.lastCompletedCount} email(s))`:''}\\n`;}text+=`\\n`;\ntext+=`<b>Emails found:</b> ${summary.totalDiscovered||0}\\n`;if(summary.batchSaturated)text+=`\u26a0\ufe0f Batch full (${summary.totalDiscovered}/${$('Build Context And Validate Env').first().json.batchSize}) \u2014 more emails may be waiting in inbox.\\n`;text+=`<b>Completed:</b> ${summary.completed||0}\\n<b>Failures:</b> ${summary.failed||0}\\n<b>Needs-review:</b> ${summary.needsReview||0}\\n\\n`;\ntext+=`<b>Categories:</b> ${h(countsLine(summary.categoryCounts||{}))}\\n`;const src=summary.sourceCounts||{};text+=`<b>Sources:</b> ${h(sourceLine(src))}\\n`;const totalSrc=Object.values(src).reduce((s,v)=>s+Number(v||0),0);const aiCount=Number(src.ai||0)+Number(src.ai_low_confidence||0)+Number(src.ai_failed||0);if(totalSrc>0){const rules=Number(src.whitelist||0)+Number(src.blacklist||0)+Number(src.force_finance||0)+Number(src.force_review||0);const thread=Number(src.thread_reuse||0);const ledger=Number(src.ledger_reuse||0);const explicitBypass=rules+thread+ledger;const otherNonAi=Math.max(0,totalSrc-aiCount-explicitBypass);text+=`\u26a1 AI bypass: ${Math.round((explicitBypass/totalSrc)*100)}% (${explicitBypass}/${totalSrc}) \u2014 rules: ${rules}, thread: ${thread}, ledger: ${ledger}`;if(otherNonAi)text+=` \u00b7 other non-AI: ${otherNonAi}`;text+=`\\n`;}\nif((summary.completed||0)<=7&&Array.isArray(summary.inlineDigest)&&summary.inlineDigest.length){text+=`\\n<b>Processed emails</b>\\n`+summary.inlineDigest.map((r,i)=>{const l=link(r.messageId);const subj=h(String(r.subject||'(no subject)').slice(0,60));const srcTxt=r.source==='ai'?`ai\u00b7${h(r.confidence)}`:h(r.source||'');return`${i+1}. ${emojiCat(r.category)} \u2014 ${h(r.sender||'unknown')}\\n${l?`<a href=\"${l}\">${subj}</a>`:subj} [${srcTxt}]`;}).join('\\n');}\nconst bands=summary.confidenceBands||{};if((bands.high||bands.medium||bands.review||bands.failed)||summary.ruleWarning||summary.ledgerWarningCount||summary.warningCount||guard.expiredStaleRuns||summary.needsReview||summary.batchSaturated){text+=`\\n\\n<b>Diagnostics</b>\\n`;if(summary.batchSaturated)text+=`\u26a0\ufe0f Batch saturated: increase GMAIL_BATCH_SIZE or trigger another run.\\n`;if(bands.high||bands.medium||bands.review||bands.failed)text+=`AI confidence: high ${Number(bands.high||0)} | medium ${Number(bands.medium||0)} | review ${Number(bands.review||0)} | failed ${Number(bands.failed||0)}\\n`;if(summary.needsReview&&summary.reviewReasonCounts)text+=`\ud83d\udd0e Review causes: `+Object.entries(summary.reviewReasonCounts).map(([k,v])=>`${h(k)}: ${v}`).join(' | ')+`\\n`;if(summary.ruleWarning)text+=`\u26a0\ufe0f Rules degraded: ${h(summary.ruleWarning)}\\n`;if(summary.ledgerWarningCount)text+=`\u26a0\ufe0f Ledger warnings: ${Number(summary.ledgerWarningCount)}\\n`;if(summary.warningCount)text+=`\u26a0\ufe0f Completed with warnings: ${Number(summary.warningCount)}\\n`;if(guard.expiredStaleRuns)text+=`\u26a0\ufe0f Auto-cleaned ${Number(guard.expiredStaleRuns)} stale run(s).\\n`;}\nif(Array.isArray(summary.warningDetails)&&summary.warningDetails.length)text+=`\\n<b>Warning digest</b>\\n`+summary.warningDetails.map((w,i)=>{const l=link(w.messageId);return`${i+1}. ${h(w.sender)}\\n${l?`<a href=\"${l}\">\"${h(w.subject)}\"</a>`:`\"${h(w.subject)}\"`}\\n${h(w.errorMessage)}`}).join('\\n\\n');\nif(Array.isArray(summary.failedDetails)&&summary.failedDetails.length)text+=`\\n\\n<b>Failure details</b>\\n`+summary.failedDetails.map((f,i)=>{const l=link(f.messageId);return`${i+1}. ${h(f.sender||'unknown')}\\n${l?`<a href=\"${l}\">\"${h(f.subject||'(no subject)')}\"</a>`:`\"${h(f.subject||'(no subject)')}\"`}\\n${h(f.failureStage||f.source||'failed')} \u2014 ${h(f.errorMessage||'No error message.')}`}).join('\\n\\n');\nif(Array.isArray(summary.reviewDetails)&&summary.reviewDetails.length)text+=`\\n\\n<b>Review digest</b>\\n`+summary.reviewDetails.map((r,i)=>{const l=link(r.messageId);return`${i+1}. ${h(r.sender)}\\n${l?`<a href=\"${l}\">\"${h(r.subject)}\"</a>`:`\"${h(r.subject)}\"`}\\n${h(r.source)} \u00b7 ${h(r.confidence)} \u00b7 ${h(r.hint)} \u2014 ${h(r.reasoning)}`}).join('\\n\\n');\nif(Array.isArray(summary.ruleSuggestions)&&summary.ruleSuggestions.length)text+=`\\n\\n<b>Rule suggestions this run</b>\\n`+summary.ruleSuggestions.map(s=>`\u2022 ${h(s.sender)} (${s.count}\u00d7 \u2192 /review ${h(s.sender)}, /receipt ${h(s.sender)}, or /whitelist ${h(s.sender)})`).join('\\n');const actions=[];if((summary.needsReview||0)>0)actions.push('\u2022 /recent needs-review \u2014 inspect uncertain emails');if((summary.failed||0)>0)actions.push('\u2022 /recent failed \u2014 inspect processing failures');if((summary.warningCount||0)>0)actions.push('\u2022 /recent warnings \u2014 inspect completed warnings');if(summary.batchSaturated)actions.push('\u2022 Increase GMAIL_BATCH_SIZE or run again');if((summary.completed||0)>0)actions.push('\u2022 /stats \u2014 find repeated review senders');if(actions.length)text+=`\\n\\n<b>Next actions</b>\\n`+actions.join('\\n');if(summary.errorMessage)text+=`\\n\\n<b>Note:</b> ${h(summary.errorMessage)}`;if(dbError)text+=`\\n\\n\u26a0\ufe0f Run finished logically, but DB final update failed.`;return[{json:{chatId:$env.TELEGRAM_CHAT_ID,text:clampTelegram(text)}}];"
      },
      "id": "6422c7c1-0229-4053-923d-fb6901d8e48e",
      "name": "Format Telegram Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3300,
        -40
      ]
    },
    {
      "parameters": {
        "operation": "sendMessage",
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "id": "5fbdd418-1de1-451c-9989-27d2bcd13d11",
      "name": "Send Telegram Summary",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3520,
        -40
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "operation": "sendMessage",
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.text }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "id": "68b58b3d-0b7d-4a8e-b466-a9d45b0e8720",
      "name": "Send Immediate Telegram Notice",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        880,
        160
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000
    }
  ],
  "connections": {
    "Daily Schedule 6AM Manila": {
      "main": [
        [
          {
            "node": "Build Context And Validate Env",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Context And Validate Env": {
      "main": [
        [
          {
            "node": "Env OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Env OK?": {
      "main": [
        [
          {
            "node": "Simple Run Guard Query",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Env Error Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Env Error Summary": {
      "main": [
        [
          {
            "node": "Send Immediate Telegram Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Run Guard Query": {
      "main": [
        [
          {
            "node": "Evaluate Run Guard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Run Guard": {
      "main": [
        [
          {
            "node": "Active Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Active Run?": {
      "main": [
        [
          {
            "node": "Build Skipped Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Run Started",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Skipped Summary": {
      "main": [
        [
          {
            "node": "Send Immediate Telegram Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Run Started": {
      "main": [
        [
          {
            "node": "Get Gmail Labels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Gmail Labels": {
      "main": [
        [
          {
            "node": "Get Sender Rules",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Sender Rules": {
      "main": [
        [
          {
            "node": "Fetch Inbox Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Inbox Emails": {
      "main": [
        [
          {
            "node": "Normalize Emails And Apply Rules",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Emails And Apply Rules": {
      "main": [
        [
          {
            "node": "No Work?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "No Work?": {
      "main": [
        [
          {
            "node": "Build No Work Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Split Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build No Work Summary": {
      "main": [
        [
          {
            "node": "Mark Run Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Emails": {
      "main": [
        [
          {
            "node": "Call Single Email Processor",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Final Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call Single Email Processor": {
      "main": [
        [
          {
            "node": "Normalize Child Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Child Result": {
      "main": [
        [
          {
            "node": "Used Gemini?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Used Gemini?": {
      "main": [
        [
          {
            "node": "Wait Between Emails",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Split Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Between Emails": {
      "main": [
        [
          {
            "node": "Split Emails",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Final Summary": {
      "main": [
        [
          {
            "node": "Mark Run Finished",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Run Finished": {
      "main": [
        [
          {
            "node": "Format Telegram Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Telegram Summary": {
      "main": [
        [
          {
            "node": "Send Telegram Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "timezone": "Asia/Manila",
    "saveExecutionProgress": true,
    "executionTimeout": 1800
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "versionId": "v4.0-lean-generated"
}
Pro

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

About this workflow

Shiny Gmail Daily Orchestrator V4.0 Lean. Uses postgres, gmail, telegram. Scheduled trigger; 25 nodes.

Source: https://github.com/chanrylejay/shiny-gmail-automation/blob/main/workflows/workflow_a_orchestrator.json — 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

Klaviyo List Decay Detection

HTTP Request, Postgres, Error Trigger +1
Email & Gmail

Behavioral analytics: Real-time analysis of product usage and engagement signals Churn prediction: Predictive model identifying at-risk customers 15 days before Smart upselling: Personalized recommend

Postgres, HTTP Request, Gmail
Email & Gmail

Gmail Job Alert — AI Summary to Telegram. Uses gmail, httpRequest, telegram. Scheduled trigger; 7 nodes.

Gmail, HTTP Request, Telegram
Email & Gmail

Gmail → Telegram Forwarder. Uses gmail, telegram. Scheduled trigger; 8 nodes.

Gmail, Telegram
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