{ "kind": "open-flow-workflow-bundle", "version": 1, "entryFlowId": "gmail-ai-labeler", "workflows": [ { "flowId": "gmail-ai-labeler", "file": { "kind": "open-flow-workflow", "version": 1, "name": "Gmail AI Labeler", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_516977b1770f278e": { "name": "New Gmail messages every minute", "kind": "poll", "config": { "labelNamesOrIds": { "kind": "value", "value": [] }, "readStatus": { "kind": "value", "value": "All" }, "search": { "kind": "value", "value": "" }, "sender": { "kind": "value", "value": "" }, "includeDrafts": { "kind": "value", "value": true }, "includeSpamAndTrash": { "kind": "value", "value": false }, "maxMessagesPerPoll": { "kind": "value", "value": 25 } }, "definition": { "configInputs": [ { "description": "Only trigger on messages carrying all of these labels, referenced by name or ID.", "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1 } }, "nullable": false, "handle": "labelNamesOrIds", "value": [] }, { "jsonSchema": { "type": "string", "enum": [ "Read", "Unread", "All" ] }, "nullable": false, "handle": "readStatus", "value": "All" }, { "description": "Gmail search-syntax query the message must match.", "jsonSchema": { "type": "string" }, "nullable": false, "handle": "search", "value": "" }, { "description": "Only trigger on messages whose sender matches this address or name.", "jsonSchema": { "type": "string" }, "nullable": false, "handle": "sender", "value": "" }, { "collapsed": true, "group": "Advanced settings" }, { "description": "Also trigger on draft messages.", "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "includeDrafts", "value": false }, { "description": "Also trigger on messages in SPAM and TRASH.", "jsonSchema": { "type": "boolean" }, "nullable": false, "handle": "includeSpamAndTrash", "value": false }, { "jsonSchema": { "type": "integer", "minimum": 1, "maximum": 100 }, "nullable": false, "handle": "maxMessagesPerPoll", "value": 25 } ], "definitionVersion": 2, "description": "Polls the Gmail mailbox and triggers when a new message is received.", "displayName": "New Message Received", "key": "gmail.on_message_received", "name": "on_message_received", "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "historyId": { "type": [ "string", "null" ] }, "labelIds": { "type": "array", "items": { "type": "string" } }, "messageId": { "type": "string" }, "messageTimestamp": { "type": "string" }, "sender": { "type": "string" }, "subject": { "type": "string" }, "threadId": { "type": "string" }, "to": { "type": "string" } }, "additionalProperties": false, "required": [ "messageId", "threadId", "historyId", "labelIds", "subject", "sender", "to", "messageTimestamp" ] } }, "nullable": false, "handle": "events" } ], "provider": "gmail", "type": "poll" }, "pollTimes": [ { "type": "every", "unit": "minute", "value": 1 } ] }, "n_3c03034bb64c734a": { "inputs": { "events": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_516977b1770f278e", "output": "events" } ] } }, "name": "Keep incoming message IDs", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object" } }, "nullable": false, "handle": "events" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1 } }, "nullable": false, "handle": "messageIds" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "count" } ], "name": "Keep incoming message IDs", "moduleId": "n_ddf0d89ed09e64f4", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_fb5ea49b8b3f49b4": { "inputs": {}, "name": "Mailbox batch is empty", "kind": "condition", "matchMode": "first", "cases": [ { "output": "no_messages", "groups": [ { "expressions": [ { "left": { "kind": "source", "source": { "kind": "node", "nodeId": "n_3c03034bb64c734a", "output": "count" } }, "operator": "==", "right": { "kind": "value", "value": 0 } } ] } ] } ] }, "n_f690fef309bdbd0c": { "inputs": { "messageIds": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_3c03034bb64c734a", "output": "messageIds" } ] } }, "name": "Read each message body", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "string", "minLength": 1 } }, "nullable": false, "handle": "messageIds" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "body": { "type": "string" } }, "required": [ "messageId", "body" ], "additionalProperties": false } }, "nullable": false, "handle": "messages" } ], "name": "Read each message body", "moduleId": "n_f482b7b0390a4f0f", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "gmail.get_message" } ] } ] } }, "n_c79e4513f5a02128": { "inputs": {}, "name": "Read existing Gmail labels", "kind": "task", "task": { "inputs": [ { "description": "Gmail user ID. Omit to use the connected mailbox.", "jsonSchema": { "type": "string", "description": "Gmail user ID. Omit to use the connected mailbox." }, "nullable": true, "handle": "userId", "value": null } ], "outputs": [ { "description": "Labels available in the mailbox.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Gmail label ID." }, "name": { "type": "string", "description": "Label display name." }, "type": { "type": "string", "description": "Gmail label type, such as system or user." }, "messageListVisibility": { "type": "string", "enum": [ "show", "hide" ], "description": "Whether messages with this label appear in the message list." }, "labelListVisibility": { "type": "string", "enum": [ "labelShow", "labelShowIfUnread", "labelHide" ], "description": "Whether the label appears in the label list." }, "messagesTotal": { "type": "integer", "description": "Total number of messages with this label." }, "messagesUnread": { "type": "integer", "description": "Unread message count for this label." }, "threadsTotal": { "type": "integer", "description": "Total number of threads with this label." }, "threadsUnread": { "type": "integer", "description": "Unread thread count for this label." }, "color": { "type": "object", "properties": { "textColor": { "type": "string", "description": "Hex color used for the label text." }, "backgroundColor": { "type": "string", "description": "Hex color used for the label background." } }, "required": [ "textColor", "backgroundColor" ], "additionalProperties": false, "description": "Configured label colors." } }, "required": [ "id", "name", "type" ], "additionalProperties": false, "description": "Schema for gmailLabelSchema." }, "description": "Labels available in the mailbox." }, "nullable": false, "handle": "labels" } ], "name": "Read existing Gmail labels", "executor": { "kind": "connector", "action": "gmail.list_labels" } } }, "n_c4f4176acccfdcfb": { "inputs": { "labels": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c79e4513f5a02128", "output": "labels" } ] }, "classifications": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "rev58amed4", "output": "classifications" } ] } }, "name": "Match labels to each message", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } } }, "required": [ "messageId", "labels" ], "additionalProperties": false } }, "nullable": false, "handle": "classifications" }, { "jsonSchema": { "type": "array", "items": { "type": "object" } }, "nullable": false, "handle": "labels" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "labelIds": { "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "messageId", "labels", "labelIds" ], "additionalProperties": false } }, "nullable": false, "handle": "plans" } ], "name": "Match labels to each message", "moduleId": "n_a786944f26bb8064", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_26b76f4d088ff30e": { "inputs": { "plans": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_c4f4176acccfdcfb", "output": "plans" } ] } }, "name": "Add each message’s label IDs", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "labelIds": { "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "messageId", "labels", "labelIds" ], "additionalProperties": false } }, "nullable": false, "handle": "plans" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "labelIds": { "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "messageId", "labels", "labelIds" ], "additionalProperties": false } }, "nullable": false, "handle": "decisions" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "updatedCount" } ], "name": "Add each message’s label IDs", "moduleId": "n_f74862ccb2800741", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [ { "action": "gmail.add_label_to_email" } ] } ] } }, "n_05e5d030fd79470c": { "inputs": {}, "name": "No incoming messages", "kind": "task", "task": { "inputs": [], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "labelIds": { "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "messageId", "labels", "labelIds" ], "additionalProperties": false } }, "nullable": false, "handle": "decisions" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "updatedCount" } ], "name": "No incoming messages", "moduleId": "n_33bd73fe3826d447", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_0e6836667f265157": { "name": "Labeling decisions", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "labelIds": { "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "messageId", "labels", "labelIds" ], "additionalProperties": false } }, "nullable": false, "handle": "decisions" }, { "jsonSchema": { "type": "integer", "minimum": 0 }, "nullable": false, "handle": "updatedCount" } ], "outputMappings": { "decisions": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_26b76f4d088ff30e", "output": "decisions" }, { "kind": "node", "nodeId": "n_05e5d030fd79470c", "output": "decisions" } ] }, "updatedCount": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_26b76f4d088ff30e", "output": "updatedCount" }, { "kind": "node", "nodeId": "n_05e5d030fd79470c", "output": "updatedCount" } ] } } }, "rev58amed4": { "inputs": { "messages": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_f690fef309bdbd0c", "output": "messages" } ] } }, "name": "Classify every incoming message", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "body": { "type": "string" } }, "required": [ "messageId", "body" ], "additionalProperties": false } }, "nullable": false, "handle": "messages" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "messageId": { "type": "string", "minLength": 1 }, "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } } }, "required": [ "messageId", "labels" ], "additionalProperties": false } }, "nullable": false, "handle": "classifications" } ], "name": "Classify every incoming message", "moduleId": "7jgz5z8vfm", "capabilities": [ { "kind": "workflow", "name": "classify-message", "flowId": "gmail-label-classifier", "revisionId": "", "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "body" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "nullable": false, "handle": "labels" } ] } ] } } }, "edges": [ { "source": "n_516977b1770f278e", "target": "n_3c03034bb64c734a" }, { "source": "n_3c03034bb64c734a", "target": "n_fb5ea49b8b3f49b4" }, { "source": "n_fb5ea49b8b3f49b4", "target": "n_f690fef309bdbd0c", "sourceHandle": "otherwise" }, { "source": "n_fb5ea49b8b3f49b4", "target": "n_05e5d030fd79470c", "sourceHandle": "no_messages" }, { "source": "n_c79e4513f5a02128", "target": "n_c4f4176acccfdcfb" }, { "source": "n_c4f4176acccfdcfb", "target": "n_26b76f4d088ff30e" }, { "source": "n_26b76f4d088ff30e", "target": "n_0e6836667f265157" }, { "source": "n_05e5d030fd79470c", "target": "n_0e6836667f265157" }, { "source": "n_f690fef309bdbd0c", "target": "rev58amed4" }, { "source": "rev58amed4", "target": "n_c79e4513f5a02128" } ] } }, "modules": { "n_ddf0d89ed09e64f4": { "name": "Keep incoming message IDs", "imports": [], "source": "export default function prepareMessages({ events }) {\n if (!Array.isArray(events) || events.length > 25) throw new Error('Expected at most 25 incoming Gmail events.');\n const messageIds = [], seen = new Set();\n for (const event of events) {\n const id = event?.messageId;\n if (typeof id !== 'string' || !id.trim() || seen.has(id)) throw new Error('Incoming Gmail message IDs must be nonempty and unique.');\n seen.add(id); messageIds.push(id);\n }\n return { messageIds, count: messageIds.length };\n}\n" }, "n_f482b7b0390a4f0f": { "name": "Read each message body", "imports": [], "source": "export function normalizeBodies(messageIds, responses) {\n if (!Array.isArray(messageIds) || !Array.isArray(responses) || messageIds.length !== responses.length) throw new Error('Gmail body count differs from the incoming message count.');\n const messages = messageIds.map((messageId, index) => {\n const response = responses[index];\n if (response?.messageId !== messageId || typeof response.body !== 'string') throw new Error('Gmail returned a different message ID or invalid body.');\n return { messageId, body: response.body };\n });\n return { messages };\n}\nexport default async function readMessageBodies({ messageIds }, context) {\n const responses = [];\n for (const messageId of messageIds) {\n context.signal?.throwIfAborted();\n responses.push(await context.actions.call('gmail.get_message', { messageId }));\n }\n return normalizeBodies(messageIds, responses);\n}\n" }, "n_a786944f26bb8064": { "name": "Match labels to each message", "imports": [], "source": "export default function prepareLabelWrites({ classifications, labels }) {\n if (!Array.isArray(classifications) || classifications.length > 25 || !Array.isArray(labels)) throw new Error('Invalid Gmail label plan.');\n const catalog = new Map(), allowed = ['Inquiry', 'Partnership', 'Notification'];\n for (const label of labels) {\n if (!allowed.includes(label?.name)) continue;\n if (typeof label.id !== 'string' || !label.id.trim() || catalog.has(label.name)) throw new Error('Configured Gmail label names must have one valid ID each.');\n catalog.set(label.name, label.id);\n }\n const seen = new Set(), plans = classifications.map(classification => {\n if (typeof classification?.messageId !== 'string' || !classification.messageId.trim() || seen.has(classification.messageId) || !Array.isArray(classification.labels)) throw new Error('Classifications must keep unique incoming Gmail message IDs.');\n seen.add(classification.messageId);\n const names = [], ids = [];\n for (const name of classification.labels) {\n if (!allowed.includes(name) || !catalog.has(name)) throw new Error('A selected label is unknown or missing from the connected Gmail account.');\n if (!names.includes(name)) { names.push(name); ids.push(catalog.get(name)); }\n }\n return { messageId: classification.messageId, labels: names, labelIds: ids };\n });\n return { plans };\n}\n" }, "n_f74862ccb2800741": { "name": "Add each message’s label IDs", "imports": [], "source": "export default async function addLabelsToMessages({ plans }, context) {\n let updatedCount = 0;\n for (const plan of plans) {\n context.signal?.throwIfAborted();\n if (!plan.labelIds.length) continue;\n const response = await context.actions.call('gmail.add_label_to_email', { messageId: plan.messageId, addLabelIds: plan.labelIds });\n if (response?.messageId !== plan.messageId) throw new Error('Gmail returned a label confirmation for a different message.');\n updatedCount++;\n }\n return { decisions: plans, updatedCount };\n}\n" }, "n_33bd73fe3826d447": { "name": "No incoming messages", "imports": [], "source": "export default function empty(){return {decisions:[],updatedCount:0};}" }, "7jgz5z8vfm": { "name": "Classify every incoming message", "imports": [], "source": "export default async function classifyMessages({ messages }, context) {\n const classifications = [];\n for (const message of messages) {\n context.signal?.throwIfAborted();\n const result = await context.workflows.call('classify-message', message);\n if (result?.messageId !== message.messageId) throw new Error('The classification belongs to a different Gmail message.');\n classifications.push(result);\n }\n return { classifications };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } } }, { "flowId": "gmail-label-classifier", "file": { "kind": "open-flow-workflow", "version": 1, "name": "Gmail Label Classifier", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_58018ae5100ad48d": { "name": "One Gmail message", "kind": "start", "inputDefs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "body" } ] }, "n_a39e22a2fcfb49c0": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_58018ae5100ad48d", "output": "body" } ] } }, "name": "Classify one message body", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "items": { "type": "object" }, "type": "array" }, "nullable": true, "handle": "messages", "value": null }, { "jsonSchema": { "items": { "type": "object" }, "minItems": 1, "type": "array" }, "nullable": false, "handle": "template", "value": [ { "role": "system", "content": "Your task is to categorize the message according to the following labels.\n\nPartnership - email about sponsored content, cooperation etc.\nInquiry - email about products, services.\nNotification - email that doesn't require response. \n\nOne email can have more than one label. Return only label names in JSON format, nothing else. Do not make things up. " }, { "role": "user", "content": "{{body}}\n\nReturn a JSON object with a required \"labels\" array. Every array item must be exactly \"Inquiry\", \"Partnership\", or \"Notification\". An empty array is allowed. Return only the JSON object." } ] }, { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "model", "value": { "model": "gpt-4o-mini-2024-07-18", "temperature": 0 } }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "body" } ], "outputs": [ { "description": "Generated response.", "jsonSchema": { "type": "object", "properties": { "labels": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } } }, "required": [ "labels" ] }, "nullable": false, "handle": "output" } ], "name": "Classify one message body", "executor": { "kind": "llm", "mode": "json" } } }, "n_516469f176775b72": { "inputs": { "messageId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_58018ae5100ad48d", "output": "messageId" } ] }, "completion": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a39e22a2fcfb49c0", "output": "output" } ] } }, "name": "Validate exact label names", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "object" }, "nullable": false, "handle": "completion" } ], "outputs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "nullable": false, "handle": "labels" } ], "name": "Validate exact label names", "moduleId": "n_e2a016cdcbeaf1e4", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_4679bd53d61cd6f0": { "name": "One classified message", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "string", "minLength": 1 }, "nullable": false, "handle": "messageId" }, { "jsonSchema": { "type": "array", "items": { "type": "string", "enum": [ "Inquiry", "Partnership", "Notification" ] } }, "nullable": false, "handle": "labels" } ], "outputMappings": { "messageId": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_516469f176775b72", "output": "messageId" } ] }, "labels": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_516469f176775b72", "output": "labels" } ] } } } }, "edges": [ { "source": "n_58018ae5100ad48d", "target": "n_a39e22a2fcfb49c0" }, { "source": "n_a39e22a2fcfb49c0", "target": "n_516469f176775b72" }, { "source": "n_516469f176775b72", "target": "n_4679bd53d61cd6f0" } ] } }, "modules": { "n_e2a016cdcbeaf1e4": { "name": "Validate exact label names", "imports": [], "source": "export default function validateLabels({ messageId, completion }) {\n const allowed = ['Inquiry', 'Partnership', 'Notification'];\n if (typeof messageId !== 'string' || !messageId.trim() || !completion || typeof completion !== 'object' || Array.isArray(completion) || !Array.isArray(completion.labels)) throw new Error('Classification must be an object containing a labels array.');\n const labels = [];\n for (const label of completion.labels) {\n if (typeof label !== 'string' || !allowed.includes(label)) throw new Error('Classification contains an unknown Gmail label.');\n if (!labels.includes(label)) labels.push(label);\n }\n return { messageId, labels };\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } } } ] }