{"id":"ZiIoKEClTk83g1Jt","meta":{"instanceId":"workflow-8bfd2626","versionId":"1.0.0","createdAt":"2025-09-29T07:07:54.451560","updatedAt":"2025-09-29T07:07:54.451597","owner":"n8n-user","license":"MIT","category":"automation","status":"active","priority":"high","environment":"production"},"name":"Gmail to Vector Embeddings with PGVector and Ollama","nodes":[{"id":"162b1a8b-2471-4880-9fcb-7f2dcfe175a8","name":"Embeddings Ollama","type":"n8n-nodes-base.noOp","position":[1920,-100],"parameters":{"model":"nomic-embed-text:latest"},"credentials":{},"typeVersion":1,"notes":"This embeddingsOllama node performs automated tasks as part of the workflow."},{"id":"49eb04b0-3b54-499c-ba46-3251102a4017","name":"Default Data Loader","type":"n8n-nodes-base.noOp","position":[2040,-97.5],"parameters":{"options":{"metadata":{"metadataValues":[{"name":"emails_metadata.id","value":"={{ $('Extract email fields').item.json.email_id }}"},{"name":"emails_metadata.thread_id","value":"={{ $('Extract email fields').item.json.thread_id }}"}]}},"jsonData":"={{ $('Extract email fields').item.json.email_text }}","jsonMode":"expressionData"},"typeVersion":1,"notes":"This documentDefaultDataLoader node performs automated tasks as part of the workflow."},{"id":"b4853472-6ac7-4da5-97b3-b22950ddff06","name":"Recursive Character Text Splitter","type":"n8n-nodes-base.noOp","position":[2128,100],"parameters":{"options":{},"chunkSize":2000,"chunkOverlap":50},"typeVersion":1,"notes":"This textSplitterRecursiveCharacterTextSplitter node performs automated tasks as part of the workflow."},{"id":"b189f134-f78e-438f-9189-2f2b276b487d","name":"Gmail Trigger","type":"n8n-nodes-base.gmailTrigger","position":[1260,280],"parameters":{"simple":false,"filters":{"labelIds":["INBOX"]},"options":{"downloadAttachments":true},"pollTimes":{"item":[{"mode":"everyMinute"}]}},"credentials":{},"typeVersion":1.2,"notes":"This gmailTrigger node performs automated tasks as part of the workflow."},{"id":"81cba4c5-7762-483d-a076-3fa8799f70ce","name":"Loop Over Items","type":"n8n-nodes-base.splitInBatches","position":[840,40],"parameters":{"options":{}},"typeVersion":3,"notes":"This splitInBatches node performs automated tasks as part of the workflow."},{"id":"f82243ad-6efd-4be2-bf4e-5001870ae854","name":"Split Out","type":"n8n-nodes-base.splitOut","position":[640,40],"parameters":{"options":{"destinationFieldName":"after"},"fieldToSplitOut":"weeks"},"typeVersion":1,"notes":"This splitOut node performs automated tasks as part of the workflow."},{"id":"2163d5ec-416f-4299-8a9d-10c26eaef32f","name":"Was manually triggered?","type":"n8n-nodes-base.if","position":[2416,-145],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"3cbc77e7-1796-4e1b-bbff-6391dd131336","operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $('Manual Trigger').isExecuted }}","rightValue":""}]}},"typeVersion":2.2,"notes":"This if node performs automated tasks as part of the workflow."},{"id":"76557325-c94e-47a9-9384-e6cbea94f67e","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","position":[0,40],"parameters":{},"typeVersion":1,"notes":"This manualTrigger node performs automated tasks as part of the workflow."},{"id":"b9400906-a458-4305-8805-bb6bea17396b","name":"No Operation, do nothing","type":"n8n-nodes-base.noOp","position":[2636,-145],"parameters":{},"typeVersion":1,"notes":"This noOp node performs automated tasks as part of the workflow."},{"id":"5f0aa7c2-85b3-4585-8c8c-727af27de61c","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-60,-540],"parameters":{"color":6,"width":1440,"height":780,"content":"## Bulk e-mail import\n\nPress the `Test workflow` button to run this once, and bulk import of all your e-mail\n\n### IMPORTANT\nSpecify your Gmail account creation date by editing the code node"},"typeVersion":1,"notes":"This stickyNote node performs automated tasks as part of the workflow."},{"id":"2b85d362-d40d-49c0-b3a7-27fdbee8e90b","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[360,-100],"parameters":{"width":220,"height":300,"content":"## Edit this ⬇️\nAnd specify your Gmail account creation date"},"typeVersion":1,"notes":"This stickyNote node performs automated tasks as part of the workflow."},{"id":"05a9dd25-ae36-4e3c-a249-787ee1047bff","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[740,260],"parameters":{"color":4,"width":640,"height":180,"content":"## Activate the workflow\nAnd this trigger will check for new mail, every minute"},"typeVersion":1,"notes":"This stickyNote node performs automated tasks as part of the workflow."},{"id":"3f19abc5-a165-49e8-b97e-233c47949e68","name":"Set before and after dates","type":"n8n-nodes-base.set","position":[1040,-320],"parameters":{"options":{},"assignments":{"assignments":[{"id":"48e8d703-e52a-46cc-bd72-9b0d3352091b","name":"after","type":"string","value":"={{ $json.after }}"},{"id":"a515cf56-9bc6-4724-a0ef-01a6159606f7","name":"before","type":"string","value":"={{ DateTime.fromISO($json.after).plus(1, 'week').toISODate() }}"}]}},"typeVersion":3.4,"notes":"This set node performs automated tasks as part of the workflow."},{"id":"af742b17-2086-4698-af3a-32cb7260f380","name":"Extract email fields","type":"n8n-nodes-base.set","position":[1480,-320],"parameters":{"options":{},"assignments":{"assignments":[{"id":"f818bad8-b000-499c-b137-de22dff4a343","name":"email_text","type":"string","value":"={{ $json.text }}"},{"id":"68c16520-4a26-4ea9-95f7-ee89b9f53c4f","name":"email_from","type":"string","value":"={{ $json.from?.text ?? '' }}"},{"id":"981f1f5b-ba2f-4153-966c-45bb6b535794","name":"email_to","type":"string","value":"={{ $json.to?.text ?? '' }}"},{"id":"b528dd23-a743-4a55-98df-e1ae823b29b3","name":"date","type":"string","value":"={{ DateTime.fromISO($json.date).toISO() }}"},{"id":"39081032-e503-470b-8d83-b5064238d037","name":"email_id","type":"string","value":"={{ $json.id }}"},{"id":"146e8e72-3c2c-4320-b93a-b109d2e46139","name":"thread_id","type":"string","value":"={{ $json.threadId }}"},{"id":"a49333a5-c565-4d46-8398-d423072b1e4d","name":"email_subject","type":"string","value":"={{ $json.subject }}"},{"id":"806cf930-450e-4221-8061-a71ec8bf9bbe","name":"attachments","type":"array","value":"={{ Object.keys($binary).map(item => $binary[item].fileName).filter(item => !!item) }}"},{"id":"30a38aaf-04c2-4286-99c9-8bb60ae8b317","name":"email_cc","type":"string","value":"={{ $json.cc?.text ?? ''}}"}]}},"typeVersion":3.4,"notes":"This set node performs automated tasks as part of the workflow."},{"id":"a51f5d5f-69c7-4153-be7f-492a8694629a","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[1640,-540],"parameters":{"width":720,"height":780,"content":"## Magic here 🪄\n#### (not really, just statistics)\nE-mail is stored in a `emails_metadata` structured table, and also fed to the [`nomic-embed-text`]({{ $env.WEBHOOK_URL }} model to be stored in a `emails_embeddings` table as [vector embeddings]({{ $env.WEBHOOK_URL }} so similarity searches are possible.\n\nThe `email_id` field can be used to make the relation between the structured records and the vector embeddings, as it's stored in their metadata as `emails_metadata.id`.\nThis is also the case for `thread_id`."},"typeVersion":1,"notes":"This stickyNote node performs automated tasks as part of the workflow."},{"id":"809e9269-1275-4c87-8c7f-1840c76f5b22","name":"Store structured","type":"n8n-nodes-base.postgres","onError":"continueErrorOutput","position":[1700,-320],"parameters":{"table":{"__rl":true,"mode":"name","value":"emails_metadata"},"schema":{"__rl":true,"mode":"list","value":"public"},"columns":{"value":{},"schema":[{"id":"email_id","type":"string","display":true,"removed":false,"required":true,"displayName":"email_id","defaultMatch":false,"canBeUsedToMatch":true},{"id":"thread_id","type":"string","display":true,"removed":false,"required":false,"displayName":"thread_id","defaultMatch":false,"canBeUsedToMatch":false},{"id":"email_from","type":"string","display":true,"removed":false,"required":false,"displayName":"email_from","defaultMatch":false,"canBeUsedToMatch":false},{"id":"email_to","type":"string","display":true,"removed":false,"required":false,"displayName":"email_to","defaultMatch":false,"canBeUsedToMatch":false},{"id":"email_cc","type":"string","display":true,"removed":false,"required":false,"displayName":"email_cc","defaultMatch":false,"canBeUsedToMatch":false},{"id":"date","type":"dateTime","display":true,"removed":false,"required":true,"displayName":"date","defaultMatch":false,"canBeUsedToMatch":false},{"id":"email_subject","type":"string","display":true,"removed":false,"required":false,"displayName":"email_subject","defaultMatch":false,"canBeUsedToMatch":false},{"id":"attachments","type":"array","display":true,"removed":false,"required":false,"displayName":"attachments","defaultMatch":false,"canBeUsedToMatch":false},{"id":"email_text","type":"string","display":true,"removed":false,"required":false,"displayName":"email_text","defaultMatch":false,"canBeUsedToMatch":false}],"mappingMode":"autoMapInputData","matchingColumns":["email_id"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{"outputColumns":["*"]},"operation":"upsert"},"credentials":{},"typeVersion":2.6,"notes":"This postgres node performs automated tasks as part of the workflow."},{"id":"1c3dca79-381c-411b-8727-baa297e1ceda","name":"Store vectorized","type":"n8n-nodes-base.noOp","onError":"continueRegularOutput","position":[1936,-320],"parameters":{"mode":"insert","options":{},"tableName":"emails_embeddings"},"credentials":{},"typeVersion":1,"notes":"This vectorStorePGVector node performs automated tasks as part of the workflow."},{"id":"3b7e13b2-73e9-42e7-900c-59611fe5af32","name":"Create the table","type":"n8n-nodes-base.postgres","position":[200,40],"parameters":{"query":"CREATE TABLE IF NOT EXISTS public.emails_metadata (\n    email_id character varying(64) NOT NULL,\n    thread_id character varying(64),\n    email_from text,\n    email_to text,\n    email_cc text,\n    date timestamp with time zone NOT NULL,\n    email_subject text,\n    email_text text,\n    attachments text[]\n);\n","options":{},"operation":"executeQuery"},"credentials":{},"typeVersion":2.6,"notes":"This postgres node performs automated tasks as part of the workflow."},{"id":"19c55312-d1da-4d1e-8637-c5b08a9c1a2d","name":"Explode interval into weeks","type":"n8n-nodes-base.code","position":[420,40],"parameters":{"mode":"runOnceForEachItem","jsCode":"// Edit this\nlet whenDidICreateMyGmailAccount = DateTime.fromISO('2013-11-01')\n\n// (don't edit further down)\nwhenDidICreateMyGmailAccount = whenDidICreateMyGmailAccount.set({day: 1})\nlet now = $now.set({day: 1})\nconst weeks = []\nwhile (Math.floor(Interval.fromDateTimes(whenDidICreateMyGmailAccount, now).length('weeks')) > -1) {\n  weeks.push(now.toISODate())\n  now = now.minus({weeks: 1})\n}\n\nreturn {json: { weeks }};"},"typeVersion":2,"notes":"This code node performs automated tasks as part of the workflow."},{"id":"aed43a77-6d58-41ba-b0b0-fdd3e9fe777a","name":"Get a batch of messages","type":"n8n-nodes-base.gmail","position":[1260,-320],"webhookId":"bace3678-df5b-4a9c-a1ef-1c219e3fd07b","parameters":{"simple":false,"filters":{"receivedAfter":"={{ $json.after }}","receivedBefore":"={{ $json.before }}"},"options":{"downloadAttachments":true},"operation":"getAll","returnAll":true},"credentials":{},"typeVersion":2.1,"notes":"This gmail node performs automated tasks as part of the workflow."},{"id":"error-6587d4d5","name":"Error Handler","type":"n8n-nodes-base.stopAndError","typeVersion":1,"position":[1000,400],"parameters":{"message":"Workflow execution error","options":{}}}],"active":false,"settings":{"executionOrder":"v1","saveManualExecutions":true,"callerPolicy":"workflowsFromSameOwner","errorWorkflow":null,"timezone":"UTC","executionTimeout":3600,"maxExecutions":1000,"retryOnFail":true,"retryCount":3,"retryDelay":1000},"versionId":"3c337d42-a3bb-4b71-ac36-deaf0cdf6019","connections":{},"description":"Automated workflow: Gmail to Vector Embeddings with PGVector and Ollama. This workflow integrates 15 different services: stickyNote, textSplitterRecursiveCharacterTextSplitter, code, gmailTrigger, splitOut. It contains 20 nodes and follows best practices for error handling and security.","notes":"Excellent quality workflow: Gmail to Vector Embeddings with PGVector and Ollama. This workflow has been optimized for production use with comprehensive error handling, security, and documentation."}