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 →
{
"name": "MSFrog Gmail Inbox Triage Sample",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 1
}
]
}
},
"id": "1f2ddab6-0bce-4dd7-b7ec-65ddf3ea0465",
"name": "Every Minute",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-2040,
300
]
},
{
"parameters": {
"resource": "message",
"operation": "getAll",
"returnAll": false,
"limit": 1,
"filters": {
"q": "in:inbox is:unread",
"includeSpamTrash": false
}
},
"id": "a9a068e7-a16e-40d4-89c7-929ebeb5f4aa",
"name": "Gmail Get Newest Unread",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-1810,
300
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const source = $json;\n\n// Support both Gmail API (payload.headers array) and IMAP (headers object/array)\nconst getHeader = (name) => {\n // Gmail API format: payload.headers is array of {name, value}\n const gmailHeaders = source.payload?.headers;\n if (Array.isArray(gmailHeaders)) {\n return gmailHeaders.find((h) => String(h.name || '').toLowerCase() === String(name).toLowerCase())?.value || '';\n }\n // IMAP format: headers is object or plain source fields\n const headers = source.headers || {};\n return headers[name] || headers[name.toLowerCase()] || '';\n};\n\nconst parseAddress = (value) => {\n const text = String(value || '').trim();\n const match = text.match(/<([^>]+)>/);\n return (match ? match[1] : text).trim();\n};\n\nconst fromRaw = source.from || getHeader('From') || '';\nconst toRaw = source.to || getHeader('To') || '';\nconst subject = source.subject || getHeader('Subject') || '(No subject)';\n\n// Extract attachments - handle both Gmail API (nested parts) and IMAP (attachments array)\nconst attachments = [];\nconst walkParts = (parts) => {\n if (!Array.isArray(parts)) return;\n for (const part of parts) {\n if (part?.filename) {\n attachments.push({\n fileName: part.filename,\n mimeType: part.mimeType || '',\n extractedText: ''\n });\n }\n if (Array.isArray(part?.parts)) walkParts(part.parts);\n }\n};\nwalkParts(source.payload?.parts);\n\n// IMAP may have attachments directly\nif (Array.isArray(source.attachments)) {\n source.attachments.forEach((a) => {\n if (a.filename && !attachments.find((att) => att.fileName === a.filename)) {\n attachments.push({\n fileName: a.filename || '',\n mimeType: a.contentType || '',\n extractedText: ''\n });\n }\n });\n}\n\nconst from = parseAddress(fromRaw);\n// IMAP uses text/textPlain; Gmail uses textPlain/snippet/textHtml\nconst text = source.text || source.textPlain || source.snippet || source.textHtml || '';\n\n// IMAP doesn't provide messageId reliably, use uid or generate\nconst messageId = source.id || source.messageId || getHeader('message-id') || source.uid || `imap-${Date.now()}`;\n// IMAP doesn't have threadId like Gmail; use inReplyTo or fallback to messageId\nconst threadId = source.threadId || getHeader('in-reply-to') || messageId;\n\nreturn [{\n json: {\n mailbox: 'lgauci@airosoftware.com',\n to: toRaw ? [toRaw] : ['lgauci@airosoftware.com'],\n from,\n subject,\n text,\n messageId,\n threadId,\n inReplyTo: getHeader('In-Reply-To') || getHeader('in-reply-to') || '',\n references: (getHeader('References') || getHeader('references') || '').split(' ').filter(Boolean),\n routingHints: [\n {\n mailboxPattern: 'lgauci@airosoftware.com',\n companyNameHint: 'Acme',\n workflowNameHint: 'IT Support'\n },\n {\n mailboxPattern: 'supplier-onboarding@example.com',\n companyNameHint: 'Supplier',\n workflowNameHint: 'Supplier Onboarding'\n }\n ],\n attachments\n }\n}];"
},
"id": "f61261ea-f7f4-4e09-bcf0-86a08394cd2e",
"name": "Gmail To Triage Shape",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1580,
300
]
},
{
"parameters": {
"jsCode": "const source = $json;\nconst attachmentText = (source.attachments || [])\n .map((attachment) => `${attachment.fileName}: ${attachment.extractedText || ''}`.trim())\n .join('\\n');\n\nconst matchedRoute = (source.routingHints || []).find((route) => route.mailboxPattern === source.mailbox) || null;\n\nreturn [{\n json: {\n mailbox: source.mailbox,\n to: source.to || [],\n from: source.from,\n senderDomain: String(source.from || '').split('@')[1] || '',\n inboundEmail: {\n subject: source.subject,\n text: source.text,\n messageId: source.messageId,\n threadId: source.threadId,\n inReplyTo: source.inReplyTo,\n references: source.references || []\n },\n attachmentSummary: attachmentText,\n combinedText: [source.subject, source.text, attachmentText].filter(Boolean).join('\\n\\n'),\n routeHint: matchedRoute,\n linkLookup: {\n matched: false,\n workflowEntryUuid: '',\n reason: 'No persisted email-thread mapping in sample workflow'\n }\n }\n}];"
},
"id": "63aefde0-57e4-41dd-a8db-fbd489d355c0",
"name": "Normalize Email Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1350,
300
]
},
{
"parameters": {
"jsCode": "const source = $('Normalize Email Payload').first().json;\nconst routeHint = source.routeHint || {};\nconst companyUuid = String(routeHint.companyUuidHint || '').trim() || 'token-company';\nconst companyName = String(routeHint.companyNameHint || '').trim() || 'Current Company';\n\nreturn [{\n json: {\n ...source,\n company: {\n uuid: companyUuid,\n name: companyName,\n active: true\n }\n }\n}];"
},
"id": "d25022c6-f1ca-48dc-bb27-c9fc6acee9fb",
"name": "Set Token Company Context",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-890,
300
]
},
{
"parameters": {
"resource": "workflow",
"operation": "getTypes",
"returnAll": true
},
"id": "565a7cc8-9dd3-4915-a52e-53e1afce74be",
"name": "MSFrog Get Workflow Types",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
-660,
300
]
},
{
"parameters": {
"jsCode": "const source = $('Set Token Company Context').first().json;\nconst workflows = $input.all().map((item) => item.json);\nconst body = String(source.combinedText || '').toLowerCase();\nconst isReply = Boolean(source.inboundEmail?.inReplyTo) || Boolean(source.linkLookup?.matched);\nconst workflowHint = String(source.routeHint?.workflowNameHint || '').toLowerCase();\n\nlet action = 'create_workflow';\nlet canSelfResolve = false;\nlet replyDraft = 'We have logged your issue and will keep you updated.';\n\nif (body.includes('resolved') || body.includes('fixed') || body.includes('working now')) {\n action = 'self_resolve';\n canSelfResolve = true;\n replyDraft = 'Thanks for the update. Based on your email this looks resolved. Please let us know if the issue returns.';\n} else if (isReply || body.includes('existing ticket') || body.includes('update case') || body.includes('follow up')) {\n action = 'update_workflow';\n replyDraft = 'Thanks for the additional information. We have updated your existing case.';\n}\n\nconst workflowCandidates = workflows\n .map((workflow) => {\n const name = String(workflow.name || '').toLowerCase();\n let score = 0;\n if (workflowHint && name.includes(workflowHint)) score += 10;\n if (body.includes(name)) score += 6;\n if (name.includes('support') && (body.includes('issue') || body.includes('error') || body.includes('help'))) score += 3;\n return { workflow, score };\n })\n .sort((a, b) => b.score - a.score);\n\nconst selectedWorkflow = workflowCandidates[0]?.workflow || null;\n\nreturn [{\n json: {\n ...source,\n availableWorkflowTypes: workflows.map((workflow) => ({\n uuid: workflow.uuid,\n name: workflow.name,\n status: workflow.status\n })),\n aiDecision: {\n summary: source.inboundEmail?.subject || 'Client support request',\n canSelfResolve,\n action,\n replyNeeded: true,\n replyDraft,\n workflowNameHint: selectedWorkflow?.name || source.routeHint?.workflowNameHint || '',\n workflowUuidCandidate: selectedWorkflow?.uuid || '',\n stepTitleHint: body.includes('printer') ? 'investigate' : '',\n existingEntryReferenceHint: '',\n entryName: source.inboundEmail?.subject || 'New support case',\n commentText: `Inbound email from ${source.from}:\\n\\n${source.combinedText}`\n }\n }\n}];"
},
"id": "c61e8de2-0d0c-4b4f-a7a2-d45b35caafc9",
"name": "AI Decision Placeholder",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-430,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5192a519-4b8a-4e03-984e-b6bb66824ef7",
"leftValue": "={{$json.aiDecision.action}}",
"rightValue": "create_workflow",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "620987fe-b0c4-4604-b6d0-6a3bf7622f05",
"name": "If Create Workflow",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-190,
140
]
},
{
"parameters": {
"jsCode": "const source = $('AI Decision Placeholder').first().json;\nconst workflows = $('MSFrog Get Workflow Types').all().map((item) => item.json);\nconst hint = String(source.aiDecision.workflowNameHint || '').toLowerCase();\n\nconst activeWorkflows = workflows.filter((workflow) => {\n const status = String(workflow.status || '').toLowerCase();\n return status === '' || status === 'active';\n});\n\nconst ranked = activeWorkflows\n .map((workflow) => {\n const name = String(workflow.name || '').toLowerCase();\n let score = 0;\n if (hint && name.includes(hint)) score += 10;\n if (hint && hint.includes(name)) score += 6;\n return { workflow, score };\n })\n .sort((a, b) => b.score - a.score);\n\nconst selectedWorkflow = ranked[0]?.workflow;\nif (!selectedWorkflow) {\n throw new Error('Could not resolve an active workflow type from available definitions.');\n}\n\nconst steps = Array.isArray(selectedWorkflow.steps) ? selectedWorkflow.steps : [];\nconst stepHint = String(source.aiDecision.stepTitleHint || '').toLowerCase();\nconst selectedStep = steps.find((step) => String(step.title || '').toLowerCase().includes(stepHint)) || steps[0];\n\nif (!selectedStep) {\n throw new Error('Selected workflow has no steps, so entry creation cannot continue.');\n}\n\nreturn [{\n json: {\n ...source,\n selectedWorkflow: {\n uuid: selectedWorkflow.uuid,\n name: selectedWorkflow.name,\n stepUuid: selectedStep.uuid,\n stepTitle: selectedStep.title\n },\n msfrogCreate: {\n workflowUuid: selectedWorkflow.uuid,\n name: source.aiDecision.entryName,\n description: `Auto-created from email ${source.inboundEmail.messageId}`,\n stepAssignments: [\n {\n workflow_step_uuid: selectedStep.uuid,\n assigned_user_uuid: null,\n due_date: null\n }\n ]\n }\n }\n}];"
},
"id": "07225159-b53e-413b-a7bb-d7a8376caf0d",
"name": "Prepare Create Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
50,
40
]
},
{
"parameters": {
"resource": "workflowEntry",
"operation": "create",
"workflowUuid": "={{$json.msfrogCreate.workflowUuid}}",
"name": "={{$json.msfrogCreate.name}}",
"description": "={{$json.msfrogCreate.description}}",
"stepAssignments": "={{ JSON.stringify($json.msfrogCreate.stepAssignments) }}"
},
"id": "588fc0d1-51dc-420e-917d-1c4e53c9124b",
"name": "MSFrog Create Workflow Entry",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
300,
40
]
},
{
"parameters": {
"resource": "workflowEntry",
"operation": "createComment",
"workflowEntryUuid": "={{$json.uuid}}",
"comment": "={{$('AI Decision Placeholder').first().json.aiDecision.commentText}}"
},
"id": "e73f278f-d0c0-4095-a3cb-161b841932ad",
"name": "MSFrog Comment New Entry",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
560,
40
]
},
{
"parameters": {
"jsCode": "const source = $('AI Decision Placeholder').first().json;\nconst createdEntry = $('MSFrog Create Workflow Entry').first().json;\n\nreturn [{\n json: {\n outcome: 'created',\n workflowEntryUuid: createdEntry.uuid,\n replyTo: source.from,\n replySubject: `Re: ${source.inboundEmail.subject}`,\n replyBody: `${source.aiDecision.replyDraft}\\n\\nReference: ${createdEntry.entry_number || createdEntry.uuid}`,\n mappingRecord: {\n mailbox: source.mailbox,\n messageId: source.inboundEmail.messageId,\n threadId: source.inboundEmail.threadId,\n workflowEntryUuid: createdEntry.uuid,\n companyUuid: source.company.uuid,\n workflowUuid: source.selectedWorkflow?.uuid || '',\n lastAction: 'create_workflow'\n }\n }\n}];"
},
"id": "d219ca04-cbaa-43fb-8c1a-314ed374a1c1",
"name": "Draft Create Reply",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
820,
40
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "4f2d17cd-3917-40d6-af5e-69f9063776ef",
"leftValue": "={{$json.aiDecision.action}}",
"rightValue": "update_workflow",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "4fb3454a-802b-40dc-8332-d93c27258460",
"name": "If Update Workflow",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
40,
520
]
},
{
"parameters": {
"resource": "workflowEntry",
"operation": "getAll",
"returnAll": true
},
"id": "c2a71b7a-3d6e-4418-a5e5-efc3b3d09ea8",
"name": "MSFrog Get Existing Entries",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
290,
420
]
},
{
"parameters": {
"jsCode": "const source = $('AI Decision Placeholder').first().json;\nconst entries = $('MSFrog Get Existing Entries').all().map((item) => item.json);\nconst text = String(source.combinedText || '').toLowerCase();\nconst subject = String(source.inboundEmail.subject || '').toLowerCase();\nconst referenceHint = String(source.aiDecision.existingEntryReferenceHint || '').toLowerCase();\n\nconst ranked = entries\n .map((entry) => {\n const workflowName = String(entry.workflow?.name || entry.workflow_name || '').toLowerCase();\n const entryNumber = String(entry.entry_number || '').toLowerCase();\n const entryName = String(entry.name || entry.entry_name || '').toLowerCase();\n let score = 0;\n\n if (referenceHint && entryNumber.includes(referenceHint)) score += 12;\n if (subject && entryName.includes(subject)) score += 8;\n if (workflowName && text.includes(workflowName)) score += 5;\n if (source.linkLookup?.workflowEntryUuid && entry.uuid === source.linkLookup.workflowEntryUuid) score += 20;\n\n return { entry, score };\n })\n .sort((a, b) => b.score - a.score);\n\nconst selectedEntry = ranked[0]?.entry;\nif (!selectedEntry) {\n throw new Error('Could not resolve an existing workflow entry to update.');\n}\n\nconst steps = Array.isArray(selectedEntry.steps) ? selectedEntry.steps : [];\nconst stepHint = String(source.aiDecision.stepTitleHint || '').toLowerCase();\nconst selectedStep = steps.find((step) => String(step.title || '').toLowerCase().includes(stepHint))\n || steps.find((step) => String(step.status || '').toLowerCase() !== 'completed')\n || steps[0];\n\nif (!selectedStep) {\n throw new Error('Resolved workflow entry has no steps to update.');\n}\n\nreturn [{\n json: {\n ...source,\n selectedEntry: {\n uuid: selectedEntry.uuid,\n entryNumber: selectedEntry.entry_number || '',\n workflowName: selectedEntry.workflow?.name || selectedEntry.workflow_name || '',\n stepUuid: selectedStep.uuid,\n stepTitle: selectedStep.title\n },\n msfrogUpdate: {\n workflowEntryUuid: selectedEntry.uuid,\n name: source.aiDecision.entryName,\n description: `Updated from reply email ${source.inboundEmail.messageId}`,\n stepAssignments: [\n {\n workflow_entry_step_uuid: selectedStep.uuid,\n assigned_user_uuid: selectedStep.assigned_user_uuid || null,\n due_date: selectedStep.due_date || null\n }\n ]\n }\n }\n}];"
},
"id": "dc0ca407-c769-4388-86c3-0b72e8715c08",
"name": "Prepare Update Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
540,
420
]
},
{
"parameters": {
"resource": "workflowEntry",
"operation": "update",
"workflowEntryUuid": "={{$json.msfrogUpdate.workflowEntryUuid}}",
"name": "={{$json.msfrogUpdate.name}}",
"description": "={{$json.msfrogUpdate.description}}",
"stepAssignments": "={{ JSON.stringify($json.msfrogUpdate.stepAssignments) }}"
},
"id": "53f65f16-592b-4279-8ca4-30ba6bd281ef",
"name": "MSFrog Update Workflow Entry",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
790,
420
]
},
{
"parameters": {
"resource": "workflowEntry",
"operation": "createComment",
"workflowEntryUuid": "={{$json.uuid}}",
"comment": "={{$('AI Decision Placeholder').first().json.aiDecision.commentText}}"
},
"id": "7ebd6ffc-9d55-45c5-ba3b-1949e7c67cb0",
"name": "MSFrog Comment Existing Entry",
"type": "CUSTOM.msfrog",
"typeVersion": 1,
"credentials": {
"msfrogApi": {
"name": "<your credential>"
}
},
"position": [
1040,
420
]
},
{
"parameters": {
"jsCode": "const source = $('AI Decision Placeholder').first().json;\nconst updatedEntry = $('MSFrog Update Workflow Entry').first().json;\n\nreturn [{\n json: {\n outcome: 'updated',\n workflowEntryUuid: updatedEntry.uuid,\n replyTo: source.from,\n replySubject: `Re: ${source.inboundEmail.subject}`,\n replyBody: `${source.aiDecision.replyDraft}\\n\\nReference: ${updatedEntry.entry_number || updatedEntry.uuid}`,\n mappingRecord: {\n mailbox: source.mailbox,\n messageId: source.inboundEmail.messageId,\n threadId: source.inboundEmail.threadId,\n workflowEntryUuid: updatedEntry.uuid,\n companyUuid: source.company.uuid,\n lastAction: 'update_workflow'\n }\n }\n}];"
},
"id": "d54034a8-ddd4-4b61-baf3-e13bcf9280ae",
"name": "Draft Update Reply",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1290,
420
]
},
{
"parameters": {
"jsCode": "const source = $('AI Decision Placeholder').first().json;\n\nreturn [{\n json: {\n outcome: 'self_resolve',\n replyTo: source.from,\n replySubject: `Re: ${source.inboundEmail.subject}`,\n replyBody: source.aiDecision.replyDraft,\n mappingRecord: {\n mailbox: source.mailbox,\n messageId: source.inboundEmail.messageId,\n threadId: source.inboundEmail.threadId,\n workflowEntryUuid: source.linkLookup?.workflowEntryUuid || '',\n companyUuid: source.company.uuid,\n lastAction: 'self_resolve'\n }\n }\n}];"
},
"id": "77e45dab-65d9-4c47-b64e-67d6fa2cc7f2",
"name": "Draft Self Resolve Reply",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
300,
700
]
},
{
"parameters": {
"jsCode": "const result = $json;\nconst ai = $('AI Decision Placeholder').first()?.json?.aiDecision || {};\nconst company = $('Set Token Company Context').first()?.json?.company || {};\n\nconst selectedWorkflow = $('Prepare Create Payload').first()?.json?.selectedWorkflow\n || $('Prepare Update Payload').first()?.json?.selectedEntry\n || null;\n\nconst action = String(ai.action || result.mappingRecord?.lastAction || 'unknown');\nconst outcome = String(result.outcome || 'unknown');\nconst createdOrUpdatedEntryUuid = result.workflowEntryUuid || result.mappingRecord?.workflowEntryUuid || '';\n\nconst summaryLines = [\n `Outcome: ${outcome}`,\n `Action decided: ${action}`,\n `Company: ${company.name || 'unknown'} (${company.uuid || 'n/a'})`,\n `Workflow hint: ${ai.workflowNameHint || 'n/a'}`,\n `Selected workflow/entry: ${selectedWorkflow?.name || selectedWorkflow?.workflowName || 'n/a'}`,\n `Selected step: ${selectedWorkflow?.stepTitle || 'n/a'} (${selectedWorkflow?.stepUuid || 'n/a'})`,\n `Workflow entry UUID: ${createdOrUpdatedEntryUuid || 'none'}`,\n `Mailbox: ${result.mappingRecord?.mailbox || 'n/a'}`,\n `Thread: ${result.mappingRecord?.threadId || 'n/a'}`,\n `Message ID: ${result.mappingRecord?.messageId || 'n/a'}`,\n `Reply target: ${result.replyTo || 'n/a'}`,\n `Reply subject: ${result.replySubject || 'n/a'}`\n];\n\nreturn [{\n json: {\n ...result,\n executionSummary: {\n outcome,\n action,\n companyName: company.name || null,\n companyUuid: company.uuid || null,\n workflowHint: ai.workflowNameHint || null,\n selectedWorkflowName: selectedWorkflow?.name || selectedWorkflow?.workflowName || null,\n selectedStepTitle: selectedWorkflow?.stepTitle || null,\n selectedStepUuid: selectedWorkflow?.stepUuid || null,\n workflowEntryUuid: createdOrUpdatedEntryUuid || null,\n mailbox: result.mappingRecord?.mailbox || null,\n threadId: result.mappingRecord?.threadId || null,\n messageId: result.mappingRecord?.messageId || null\n },\n summaryText: summaryLines.join('\\n')\n }\n}];"
},
"id": "9a7f3c4d-f3ec-4f6c-9c4e-0f66fd8aab4f",
"name": "Execution Summary",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1540,
360
]
},
{
"parameters": {
"jsCode": "const summary = $json.executionSummary || {};\nconst outcome = String(summary.outcome || '').trim();\nconst action = String(summary.action || '').trim();\n\nconst errors = [];\n\nif (!summary.companyUuid) errors.push('companyUuid is missing');\nif (!summary.mailbox) errors.push('mailbox is missing');\n// threadId and messageId are optional for IMAP email sources\nif (!outcome) errors.push('outcome is missing');\nif (!action) errors.push('action is missing');\n\nif (action === 'create_workflow' || outcome === 'created') {\n if (!summary.workflowEntryUuid) errors.push('workflowEntryUuid is missing for create flow');\n if (!summary.selectedWorkflowName) errors.push('selectedWorkflowName is missing for create flow');\n}\n\nif (action === 'update_workflow' || outcome === 'updated') {\n if (!summary.workflowEntryUuid) errors.push('workflowEntryUuid is missing for update flow');\n if (!summary.selectedStepUuid) errors.push('selectedStepUuid is missing for update flow');\n}\n\nif (action === 'self_resolve' || outcome === 'self_resolve') {\n if (!$json.replyBody) errors.push('replyBody is missing for self-resolve flow');\n}\n\nif (errors.length > 0) {\n throw new Error(`Execution assertions failed: ${errors.join('; ')}`);\n}\n\nreturn [{\n json: {\n ...$json,\n assertions: {\n passed: true,\n checkedAt: new Date().toISOString(),\n rulesChecked: [\n 'base summary fields (IMAP compatible)',\n 'create/update/self_resolve-specific fields'\n ]\n }\n }\n}];"
},
"id": "2b2313f2-53e8-4949-b4f2-9f5f5eb2ed8f",
"name": "Assert End Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1780,
360
]
},
{
"parameters": {
"resource": "message",
"operation": "send",
"to": "={{$json.replyTo}}",
"subject": "={{$json.replySubject}}",
"message": "={{$json.replyBody}}",
"additionalFields": {
"threadId": "={{$json.mappingRecord.threadId}}"
}
},
"id": "4af639fe-7ad4-4b78-ad0e-3e2def4c779c",
"name": "Gmail Send Reply",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
2020,
360
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Normalize Email Payload": {
"main": [
[
{
"node": "Set Token Company Context",
"type": "main",
"index": 0
}
]
]
},
"Set Token Company Context": {
"main": [
[
{
"node": "MSFrog Get Workflow Types",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Get Workflow Types": {
"main": [
[
{
"node": "AI Decision Placeholder",
"type": "main",
"index": 0
}
]
]
},
"AI Decision Placeholder": {
"main": [
[
{
"node": "If Create Workflow",
"type": "main",
"index": 0
}
]
]
},
"If Create Workflow": {
"main": [
[
{
"node": "Prepare Create Payload",
"type": "main",
"index": 0
}
],
[
{
"node": "If Update Workflow",
"type": "main",
"index": 0
}
]
]
},
"Prepare Create Payload": {
"main": [
[
{
"node": "MSFrog Create Workflow Entry",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Create Workflow Entry": {
"main": [
[
{
"node": "MSFrog Comment New Entry",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Comment New Entry": {
"main": [
[
{
"node": "Draft Create Reply",
"type": "main",
"index": 0
}
]
]
},
"Draft Create Reply": {
"main": [
[
{
"node": "Execution Summary",
"type": "main",
"index": 0
}
]
]
},
"If Update Workflow": {
"main": [
[
{
"node": "MSFrog Get Existing Entries",
"type": "main",
"index": 0
}
],
[
{
"node": "Draft Self Resolve Reply",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Get Existing Entries": {
"main": [
[
{
"node": "Prepare Update Payload",
"type": "main",
"index": 0
}
]
]
},
"Prepare Update Payload": {
"main": [
[
{
"node": "MSFrog Update Workflow Entry",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Update Workflow Entry": {
"main": [
[
{
"node": "MSFrog Comment Existing Entry",
"type": "main",
"index": 0
}
]
]
},
"MSFrog Comment Existing Entry": {
"main": [
[
{
"node": "Draft Update Reply",
"type": "main",
"index": 0
}
]
]
},
"Draft Update Reply": {
"main": [
[
{
"node": "Execution Summary",
"type": "main",
"index": 0
}
]
]
},
"Draft Self Resolve Reply": {
"main": [
[
{
"node": "Execution Summary",
"type": "main",
"index": 0
}
]
]
},
"Execution Summary": {
"main": [
[
{
"node": "Assert End Result",
"type": "main",
"index": 0
}
]
]
},
"Every Minute": {
"main": [
[
{
"node": "Gmail Get Newest Unread",
"type": "main",
"index": 0
}
]
]
},
"Gmail Get Newest Unread": {
"main": [
[
{
"node": "Gmail To Triage Shape",
"type": "main",
"index": 0
}
]
]
},
"Gmail To Triage Shape": {
"main": [
[
{
"node": "Normalize Email Payload",
"type": "main",
"index": 0
}
]
]
},
"Assert End Result": {
"main": [
[
{
"node": "Gmail Send Reply",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "b70ffde0-09d5-4c7c-ad68-536edda794aa",
"meta": {
"templateCredsSetupCompleted": false
},
"id": "b4ae5aa9-87d1-4316-9fd4-f54d26050a2f",
"tags": []
}
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.
gmailOAuth2msfrogApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
MSFrog Gmail Inbox Triage Sample. Uses gmail, CUSTOM. Scheduled trigger; 22 nodes.
Source: https://github.com/lgauciAiro/msfrog-n8n-community-node/blob/bf787342d6002ee8bdeb9057ded0f026d0b2ca95/examples/client-email-triage-gmail-inbox.workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
Automatically extract structured information from emails using AI-powered document analysis. This workflow processes emails from specified domains, classifies them by type, and extracts structured dat
This weekly workflow helps you stay on top of SEO visibility losses by automatically detecting when your previously strong keywords fall out of Google’s top 10 results.
What This Flow Does
This n8n workflow sends personalized outreach emails automatically while enforcing strict safety rules such as email validation, spam checks, daily limits, and human-like delays.