email-gmail-minio-文档化-英文_092525
高级
这是一个Content Creation, Multimodal AI领域的自动化工作流,包含 30 个节点。主要使用 S3, Set, Code, Gmail, Merge 等节点。 将 Gmail 邮件同步至 PostgreSQL,并使用 S3 存储附件
前置要求
- •Google 账号和 Gmail API 凭证
- •PostgreSQL 数据库连接信息
使用的节点 (30)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "FmREphiGbmZ0G1c2",
"meta": {
"instanceId": "899c18ce39f97e3899bbcfee71176bdf66f586807337797df5057e21f373d373",
"templateCredsSetupCompleted": true
},
"name": "email-gmail-minio-_documented-INGLES_092525",
"tags": [
{
"id": "yFL69KEFrYRFofXs",
"name": "Email Processing",
"createdAt": "2025-09-23T20:24:57.942Z",
"updatedAt": "2025-09-23T20:24:57.942Z"
}
],
"nodes": [
{
"id": "0a33ff73-f79e-4078-a0aa-5abaa9f4fa84",
"name": "系统概览",
"type": "n8n-nodes-base.stickyNote",
"position": [
896,
0
],
"parameters": {
"color": 4,
"width": 728,
"height": 512,
"content": "## 系统概览"
},
"typeVersion": 1
},
{
"id": "aec18c14-63c7-45b2-9ad1-913460c7e12d",
"name": "设置要求",
"type": "n8n-nodes-base.stickyNote",
"position": [
896,
528
],
"parameters": {
"width": 728,
"height": 900,
"content": "## 设置要求"
},
"typeVersion": 1
},
{
"id": "2480af25-7d24-4319-a0a2-fbfee28356b9",
"name": "系统触发器",
"type": "n8n-nodes-base.stickyNote",
"position": [
1648,
352
],
"parameters": {
"color": 4,
"width": 336,
"height": 1018,
"content": "## 系统触发器"
},
"typeVersion": 1
},
{
"id": "2f399efb-9293-4d0b-ae12-d6f50527b92e",
"name": "邮件检索",
"type": "n8n-nodes-base.stickyNote",
"position": [
1984,
48
],
"parameters": {
"color": 4,
"width": 500,
"height": 1348,
"content": "## 邮件检索"
},
"typeVersion": 1
},
{
"id": "b186aa06-a61b-4de2-8b17-2de984ff9db4",
"name": "附件处理",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
0
],
"parameters": {
"color": 4,
"width": 370,
"height": 600,
"content": "## 附件处理"
},
"typeVersion": 1
},
{
"id": "2317a48d-be68-45de-bf66-544a2d1269e1",
"name": "数据映射",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
400
],
"parameters": {
"color": 4,
"width": 800,
"height": 676,
"content": "## 数据映射"
},
"typeVersion": 1
},
{
"id": "ca7df595-4f73-45ef-953b-221bcde715af",
"name": "数据转换",
"type": "n8n-nodes-base.stickyNote",
"position": [
4416,
224
],
"parameters": {
"color": 4,
"width": 450,
"height": 832,
"content": "## 数据转换"
},
"typeVersion": 1
},
{
"id": "92881631-dcd7-48a2-ab18-bb91f340f314",
"name": "数据库存储",
"type": "n8n-nodes-base.stickyNote",
"position": [
4896,
224
],
"parameters": {
"color": 4,
"width": 640,
"height": 980,
"content": "## 数据库存储"
},
"typeVersion": 1
},
{
"id": "803691ca-35b2-41f5-b18d-4cdc9a4332ce",
"name": "脚本:提取二进制",
"type": "n8n-nodes-base.stickyNote",
"position": [
2864,
0
],
"parameters": {
"color": 4,
"width": 320,
"height": 778,
"content": "## 脚本:提取二进制元数据"
},
"typeVersion": 1
},
{
"id": "cca7c12b-a517-4e24-a4a4-bdfa56c28ec8",
"name": "脚本:邮件转换",
"type": "n8n-nodes-base.stickyNote",
"position": [
2752,
832
],
"parameters": {
"color": 4,
"width": 578,
"height": 600,
"content": "## 脚本:邮件转换"
},
"typeVersion": 1
},
{
"id": "dfb06420-2df7-4358-ae18-1af7a6ddcb5c",
"name": "处理项目循环",
"type": "n8n-nodes-base.splitInBatches",
"position": [
4992,
928
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "780d103a-f2b6-4458-ae38-0c4c842cb309",
"name": "提取二进制元数据",
"type": "n8n-nodes-base.code",
"position": [
2960,
672
],
"parameters": {
"jsCode": "const results = [];\n\nfor (const item of $input.all()) {\n const binaryData = item.binary;\n const jsonData = item.json;\n \n if (!binaryData || typeof binaryData !== 'object') {\n results.push({\n json: {\n error: \"No binary data found in item\",\n originalJson: jsonData,\n itemIndex: results.length\n }\n });\n continue;\n }\n \n const attachmentMetadata = {};\n const attachmentList = [];\n let totalSize = 0;\n \n for (const [key, binaryItem] of Object.entries(binaryData)) {\n if (binaryItem && binaryItem.data) {\n const metadata = {\n key: key,\n mimeType: binaryItem.mimeType || 'unknown',\n fileName: binaryItem.fileName || key,\n fileExtension: binaryItem.fileName ? \n binaryItem.fileName.split('.').pop().toLowerCase() : 'unknown',\n sizeBytes: binaryItem.data ? Buffer.byteLength(binaryItem.data) : 0,\n sizeMB: binaryItem.data ? \n (Buffer.byteLength(binaryItem.data) / 1024 / 1024).toFixed(2) : 0,\n hasData: !!binaryItem.data,\n encoding: 'binary',\n fileType: 'unknown'\n };\n \n attachmentMetadata[key] = metadata;\n attachmentList.push(metadata);\n totalSize += metadata.sizeBytes;\n }\n }\n \n const summary = {\n totalAttachments: attachmentList.length,\n totalSizeBytes: totalSize,\n totalSizeMB: (totalSize / 1024 / 1024).toFixed(2)\n };\n \n results.push({\n json: {\n summary: summary,\n attachments: attachmentMetadata,\n attachmentList: attachmentList,\n originalJson: jsonData,\n extractedAt: new Date().toISOString(),\n itemIndex: results.length\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "fd1530bf-7504-4020-bc53-76367009329c",
"name": "链接邮件附件",
"type": "n8n-nodes-base.code",
"position": [
4480,
928
],
"parameters": {
"jsCode": "\n\nfunction processAttachments(attachmentsArray, messageId) {\n if (!attachmentsArray || !Array.isArray(attachmentsArray)) {\n return null;\n }\n \n const emailAttachments = attachmentsArray.filter(item => \n item.messageId === messageId\n );\n \n if (emailAttachments.length === 0) {\n return null;\n }\n \n const processedAttachments = emailAttachments.map(item => {\n const att = item.attachments || {};\n return {\n key: att.key || '',\n mimeType: att.mimeType || '',\n fileName: att.fileName || '',\n fileExtension: att.fileExtension || '',\n sizeBytes: att.sizeBytes || 0,\n sizeMB: att.sizeMB || '0',\n hasData: att.hasData || false,\n encoding: att.encoding || '',\n fileType: att.fileType || '',\n originalFileSize: att.originalFileSize || ''\n };\n });\n \n return JSON.stringify(processedAttachments);\n}\n\nconst transformedItems = [];\n\ntry {\n const inputData = $input.all().map(item => item.json).flat();\n \n let attachmentsData = [];\n const emailsData = [];\n \n inputData.forEach((item, index) => {\n if (item.adjunto && Array.isArray(item.adjunto)) {\n attachmentsData = attachmentsData.concat(item.adjunto);\n } else if (item.message_id) {\n emailsData.push(item);\n }\n });\n \n emailsData.forEach(emailData => {\n const emailAttachments = processAttachments(attachmentsData, emailData.message_id);\n \n const transformedMessage = {\n message_id: emailData.message_id,\n thread_id: emailData.thread_id,\n sender: JSON.stringify(emailData.sender),\n recipients: JSON.stringify(emailData.recipients),\n labels: JSON.stringify(emailData.labels),\n subject: emailData.subject,\n body: emailData.body,\n attachments: emailAttachments,\n size: emailData.size,\n timestamp: emailData.timestamp,\n is_read: emailData.is_read,\n is_outgoing: emailData.is_outgoing,\n is_deleted: emailData.is_deleted,\n last_indexed: emailData.last_indexed\n };\n \n transformedItems.push({ json: transformedMessage });\n });\n \n} catch (error) {\n transformedItems.push({\n json: {\n error: true,\n message: error.message,\n timestamp: new Date().toISOString()\n }\n });\n}\n\nreturn transformedItems;"
},
"typeVersion": 2
},
{
"id": "95720209-c7b9-4f42-ba46-8c34e67f4212",
"name": "构建字段",
"type": "n8n-nodes-base.set",
"position": [
3760,
768
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n \"attachments\": {{ $('Merge Attachments').item.json.attachments }},\n \"messageId\": \"{{ $('Merge Attachments').item.json['originalJson.id'] }}\"\n}"
},
"typeVersion": 3.4
},
{
"id": "36c166f5-b689-44fb-ba7c-97ea037aa414",
"name": "最终合并",
"type": "n8n-nodes-base.merge",
"position": [
4208,
928
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "281c76ce-eaed-427c-88a2-5bced43cdc0b",
"name": "聚合附件",
"type": "n8n-nodes-base.aggregate",
"position": [
3920,
768
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "adjunto"
},
"typeVersion": 1
},
{
"id": "feee0836-e419-43a0-a2e2-e61c8601fa17",
"name": "插入或更新数据库",
"type": "n8n-nodes-base.postgres",
"position": [
5280,
944
],
"parameters": {
"table": {
"__rl": true,
"mode": "list",
"value": "messages",
"cachedResultName": "messages"
},
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"columns": {
"value": {},
"schema": [
{
"id": "message_id",
"type": "string",
"display": true,
"removed": false,
"required": true,
"displayName": "message_id",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"message_id"
]
},
"options": {},
"operation": "upsert"
},
"credentials": {
"postgres": {
"id": "ND7MmENs7bvU1tdS",
"name": "gmail-postgresql"
}
},
"typeVersion": 2.6
},
{
"id": "42737d4f-7e8a-40c0-9a40-05fbfe75a4b4",
"name": "二进制数据过滤器",
"type": "n8n-nodes-base.filter",
"position": [
2720,
672
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "binary-filter-condition",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ ($('Get Individual Message').isExecuted ? $('Get Individual Message').item.binary : null) || ($('Get Many Sent').isExecuted ? $('Get Many Sent').item.binary : null) || ($('Get Many Inbox').isExecuted ? $('Get Many Inbox').item.binary : null) }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "c8686357-9600-4317-95c5-dbbd6b462378",
"name": "上传到存储",
"type": "n8n-nodes-base.s3",
"position": [
3600,
768
],
"parameters": {
"fileName": "=/emailuser/{{ $json[\"originalJson.id\"] }}/{{ $json.attachments.fileName }}",
"operation": "upload",
"bucketName": "=gmail-attachments",
"additionalFields": {},
"binaryPropertyName": "={{ $json.attachments.key }}"
},
"credentials": {
"s3": {
"id": "3k864gx3V3O1OCaQ",
"name": "S3 account"
}
},
"typeVersion": 1
},
{
"id": "e73cf96d-064f-4718-a110-7d5e0b2fb4ae",
"name": "流汇聚",
"type": "n8n-nodes-base.noOp",
"position": [
2368,
928
],
"parameters": {},
"typeVersion": 1
},
{
"id": "4ebdb948-2ddb-4902-b6fc-6e285865e29d",
"name": "结束循环",
"type": "n8n-nodes-base.noOp",
"position": [
5264,
704
],
"parameters": {},
"typeVersion": 1
},
{
"id": "15ec8e2f-b136-426d-b997-dbab3917f7db",
"name": "获取单个邮件",
"type": "n8n-nodes-base.gmail",
"position": [
2048,
784
],
"webhookId": "2db16144-ecf5-4b0e-8292-c0cd1e1c9cef",
"parameters": {
"simple": false,
"options": {
"downloadAttachments": true
},
"messageId": "={{ $json.id }}",
"operation": "get"
},
"credentials": {
"gmailOAuth2": {
"id": "O0hOO1rdssY1E4qM",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "9032c8cf-b8d1-4537-b939-d2010ebaf188",
"name": "获取多个已发送邮件",
"type": "n8n-nodes-base.gmail",
"position": [
2048,
928
],
"webhookId": "06120048-cc84-485d-bdcb-f3581714c885",
"parameters": {
"simple": false,
"filters": {
"q": "in:sent newer_than:1h"
},
"options": {
"downloadAttachments": true
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"id": "O0hOO1rdssY1E4qM",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "e41c9979-10db-46e8-b62c-ba194182a52e",
"name": "获取多个收件箱邮件",
"type": "n8n-nodes-base.gmail",
"position": [
2048,
1136
],
"webhookId": "841c056d-b5f8-426d-8b08-5a04be5fa4c1",
"parameters": {
"simple": false,
"filters": {
"q": "in:inbox newer_than:1h"
},
"options": {
"downloadAttachments": true
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"gmailOAuth2": {
"id": "O0hOO1rdssY1E4qM",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "bdf38440-c107-4237-b5f1-542f1355e7d5",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1792,
1024
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "0534de91-4193-4ce3-b22c-dcd8e5f2449b",
"name": "Gmail 触发器",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
1792,
784
],
"parameters": {
"simple": false,
"filters": {},
"options": {
"downloadAttachments": true
},
"pollTimes": {
"item": [
{
"mode": "everyX",
"unit": "minutes",
"value": 30
}
]
}
},
"credentials": {
"gmailOAuth2": {
"id": "O0hOO1rdssY1E4qM",
"name": "Gmail account"
}
},
"typeVersion": 1.3
},
{
"id": "effb5527-f309-46f0-9fef-ac98ad9ecc17",
"name": "邮件到 PostgreSQL 转换",
"type": "n8n-nodes-base.code",
"position": [
3152,
944
],
"parameters": {
"jsCode": "// Gmail to PostgreSQL transformation\nconst authenticatedUserEmail = 'your.email@gmail.com';\n\nfunction extractTextFromHtml(htmlContent) {\n if (!htmlContent) return '';\n return htmlContent.replace(/<[^>]+>/g, '').replace(/\\s+/g, ' ').trim().substring(0, 500) || '';\n}\n\nfunction normalizeEmailAddress(emailObj) {\n if (!emailObj) return { email: '', name: '' };\n return {\n email: emailObj.address || emailObj.email || '',\n name: emailObj.name || ''\n };\n}\n\nconst transformedItems = [];\n\nfor (const item of $input.all()) {\n const gmailData = item.json;\n \n const fromInfo = gmailData.from?.value?.[0] || {};\n const sender = normalizeEmailAddress(fromInfo);\n \n const recipients = {\n to: (gmailData.to?.value || []).map(normalizeEmailAddress),\n cc: (gmailData.cc?.value || []).map(normalizeEmailAddress),\n bcc: (gmailData.bcc?.value || []).map(normalizeEmailAddress)\n };\n \n const labels = gmailData.labelIds || [];\n \n let body = '';\n if (gmailData.text && gmailData.text.trim().length > 0) {\n body = gmailData.text.trim().substring(0, 1000);\n } else if (gmailData.textAsHtml && gmailData.textAsHtml.trim().length > 0) {\n body = gmailData.textAsHtml.replace(/<[^>]+>/g, '').trim().substring(0, 1000);\n } else if (gmailData.html) {\n body = extractTextFromHtml(gmailData.html);\n }\n \n if (!body || body.length === 0) {\n body = gmailData.subject || 'No content available';\n }\n \n const isRead = !labels.includes('UNREAD');\n const isOutgoing = sender.email.toLowerCase() === authenticatedUserEmail.toLowerCase();\n \n let timestamp = new Date().toISOString();\n if (gmailData.date) {\n try {\n timestamp = new Date(gmailData.date).toISOString();\n } catch (error) {\n timestamp = new Date().toISOString();\n }\n }\n \n const transformedMessage = {\n message_id: gmailData.id,\n thread_id: gmailData.threadId || gmailData.id,\n sender: sender,\n recipients: recipients,\n labels: labels,\n subject: gmailData.subject || '',\n body: body,\n attachments: null,\n size: gmailData.sizeEstimate || 0,\n timestamp: timestamp,\n is_read: isRead,\n is_outgoing: isOutgoing,\n is_deleted: false,\n last_indexed: new Date().toISOString()\n };\n \n transformedItems.push({ json: transformedMessage });\n}\n\nreturn transformedItems;"
},
"typeVersion": 2
},
{
"id": "2f4846f0-475d-4fb5-8999-f82e006df9b4",
"name": "拆分附件",
"type": "n8n-nodes-base.splitOut",
"position": [
3152,
672
],
"parameters": {
"include": "selectedOtherFields",
"options": {},
"fieldToSplitOut": "attachments",
"fieldsToInclude": "originalJson.id"
},
"typeVersion": 1
},
{
"id": "7fa3c757-7f23-417a-a818-6f0b819334e1",
"name": "合并附件",
"type": "n8n-nodes-base.merge",
"position": [
3440,
768
],
"parameters": {
"mode": "combine",
"options": {},
"advanced": true,
"mergeByFields": {
"values": [
{
"field1": "originalJson.id",
"field2": "id"
}
]
}
},
"typeVersion": 3.2
},
{
"id": "831155c0-bacf-4dd1-92a8-e7e8d5af2d7b",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 880,
"height": 1504,
"content": "## 将 Gmail 邮件同步到 PostgreSQL 并附带 S3 附件存储"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "38ad63c6-aa46-4c77-9fe1-d287f26daec2",
"connections": {
"Final Merge": {
"main": [
[
{
"node": "Link Email Attachments",
"type": "main",
"index": 0
}
]
]
},
"Get Many Sent": {
"main": [
[
{
"node": "Flow Convergence",
"type": "main",
"index": 0
}
]
]
},
"Gmail Trigger": {
"main": [
[
{
"node": "Get Individual Message",
"type": "main",
"index": 0
}
]
]
},
"Get Many Inbox": {
"main": [
[
{
"node": "Flow Convergence",
"type": "main",
"index": 0
}
]
]
},
"Flow Convergence": {
"main": [
[
{
"node": "Binary Data Filter",
"type": "main",
"index": 0
},
{
"node": "Merge Attachments",
"type": "main",
"index": 1
},
{
"node": "Email to PostgreSQL Transform",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Get Many Sent",
"type": "main",
"index": 0
},
{
"node": "Get Many Inbox",
"type": "main",
"index": 0
}
]
]
},
"Structure Fields": {
"main": [
[
{
"node": "Aggregate Attachments",
"type": "main",
"index": 0
}
]
]
},
"Merge Attachments": {
"main": [
[
{
"node": "Upload to Storage",
"type": "main",
"index": 0
}
]
]
},
"Split Attachments": {
"main": [
[
{
"node": "Merge Attachments",
"type": "main",
"index": 0
}
]
]
},
"Upload to Storage": {
"main": [
[
{
"node": "Structure Fields",
"type": "main",
"index": 0
}
]
]
},
"Binary Data Filter": {
"main": [
[
{
"node": "Extract Binary Metadata",
"type": "main",
"index": 0
}
]
]
},
"Process Items Loop": {
"main": [
[
{
"node": "End Loop",
"type": "main",
"index": 0
}
],
[
{
"node": "Insert or Update Database",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Attachments": {
"main": [
[
{
"node": "Final Merge",
"type": "main",
"index": 0
}
]
]
},
"Get Individual Message": {
"main": [
[
{
"node": "Flow Convergence",
"type": "main",
"index": 0
}
]
]
},
"Link Email Attachments": {
"main": [
[
{
"node": "Process Items Loop",
"type": "main",
"index": 0
}
]
]
},
"Extract Binary Metadata": {
"main": [
[
{
"node": "Split Attachments",
"type": "main",
"index": 0
}
]
]
},
"Insert or Update Database": {
"main": [
[
{
"node": "Process Items Loop",
"type": "main",
"index": 0
}
]
]
},
"Email to PostgreSQL Transform": {
"main": [
[
{
"node": "Final Merge",
"type": "main",
"index": 1
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 内容创作, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
完整的 B2B 销售流程:Apollo 潜在客户生成、Mailgun 外展和 AI 回复管理
If
Set
Code
+26
116 节点Paul
内容创作
使用Perplexity和GPT为WordPress创建SEO优化博客,包含关键词和媒体
使用Perplexity和GPT为WordPress创建SEO优化博客,包含关键词和媒体
Set
Code
Limit
+22
124 节点Paul
内容创作
使用特定工具为WordPress创建SEO优化博客
使用特定工具为WordPress创建SEO优化博客
Set
Code
Limit
+22
124 节点Paul
内容创作
使用GPT-5和fal.ai图像从关键词到WordPress自动化SEO博客流程
使用GPT-5和fal.ai图像从关键词到WordPress自动化SEO博客流程
Set
Code
Wait
+20
96 节点Paul
内容创作
WordPress博客自动化专业版(深度研究)v2.1市场
使用GPT-4o、Perplexity AI和多语言支持自动化SEO优化的博客创建
If
Set
Xml
+27
125 节点Daniel Ng
内容创作
使用AI和Freepik将Reddit商业痛点转换为病毒式LinkedIn内容
使用AI和Freepik将Reddit商业痛点转换为病毒式LinkedIn内容
If
Set
Code
+16
48 节点Daniel Lianes
内容创作
工作流信息
难度等级
高级
节点数量30
分类2
节点类型14
作者
Jose Cuartas
@josecuartasconsultant in automations focused on using artificial intelligence helping to give solutions to teams that lead electronic commerce in their companies
外部链接
在 n8n.io 查看 →
分享此工作流