2026-09-04 · 原文发布日
自动起草Upwork投标方案并全程留痕
流程每两分钟读Slack里被转发的Upwork新职位消息并去重,从Google Drive文件夹下载候选人简历PDF提取文本建成档案,再搜出公司介绍Deck的分享链接;把新职位、候选人档案和Deck合成结构化提示发给Gemini,返回每单的定制提案JSON,校验后经Gmail发出提案包、在Slack发摘要并为每份提案建ClickUp任务,一份没产出就转人工复核。
原文要点
原文工作流每两分钟定时读取Slack中被转发的Upwork新职位消息,用n8n静态工作流数据去重;列出Google Drive文件夹中的候选人简历PDF并提取文本生成带Drive链接的档案,再搜索公司介绍Deck取分享链接;将职位、档案、Deck链接合成结构化提示发给Google Gemini,返回提案JSON数组;解析校验后用Gmail发出提案包、在Slack发摘要、为每份提案建ClickUp任务,无产出时转Slack人工复核。需配置Slack、Drive、Gemini、Gmail、ClickUp五套凭据及频道ID、文件夹ID等参数。
这次做成什么
在n8n里搭一条“Slack收职位→Gemini写提案→邮件发送并留痕”的投标流水线。以下为按原文公开描述独立设计的可复现搭建方案,不是原工作流文件本身,节点参数需在n8n界面中自行核对。
准备什么
- 一个n8n实例(云端或自托管均可,需能开启定时触发)
- Slack工作区及OAuth2凭据,知道职位接收频道和提案通知频道ID
- Google Drive OAuth2凭据,含简历PDF文件夹ID和公司Deck文件
- Google Gemini API密钥,可调用Generative Language API
- Gmail OAuth2凭据与收件人列表、ClickUp凭据与目标List ID
具体怎么做
定时读取并去重Slack职位消息
新建工作流,加Schedule Trigger设为每2分钟。接Slack节点(OAuth2),操作选读取频道历史,填入职位接收频道ID,只取最近两分钟的消息。接一个Function/Code节点,用n8n静态工作流数据(getWorkflowStaticData)保存已处理消息ID集合,新消息ID不在集合中才放行,并写入集合;再用IF节点过滤包含“Upwork”或链接特征的“职位样”文本。
从Google Drive构建候选人档案
加Google Drive节点,操作List Files,填入简历文件夹ID,筛选PDF;对每个文件接Download操作,再接Extract From File(PDF转文本)节点拿到简历正文。用Set节点把候选人姓名、简历文本摘要、文件分享链接合成档案对象,聚合为一个数组备用。
搜索公司Deck并取分享链接
再加一个Google Drive节点,操作Search,查询条件设为Deck文件名关键词(如公司名+deck),取第一条结果的webViewLink作为分享链接存入字段deckLink;查不到时把deckLink置空字符串并在后续提示中说明。
合成提示调用Gemini生成提案JSON
加HTTP Request节点,POST到Google Generative Language API的generateContent端点,Header带API密钥。Body中放结构化提示:职位列表、候选人档案数组、deckLink,要求只返回JSON数组,每项含jobId、candidateName、proposalText、skipped及skipReason字段。可粘贴下方材料中的提示词模板。
校验结果并分流输出
接Code节点用JSON.parse解析Gemini返回文本,校验每项是否含必填字段;解析失败或无提案时走IF分支,用Slack节点向通知频道发“本批需人工复核”消息并附原文。成功分支:Gmail节点把提案包发给收件人列表;Slack节点发摘要;对每个提案循环调用ClickUp节点,在目标List ID下建任务,任务名含职位标题与候选人名。
可直接复制的材料
发给Gemini的结构化提案生成提示词
本站独立编写的提示词,{{ }}为n8n表达式占位,实际节点中需替换为上游字段路径;提案语言、字数要求可按需调整。
你是一家小型IT外包工作室的投标写手。下面给你三类输入:
1)新职位列表(JSON数组,每项含jobId、title、description、budget);
2)候选人档案(JSON数组,每项含name、skills、resumeText摘要、driveLink);
3)公司介绍Deck分享链接deckLink。
请为每个职位挑选最匹配的一名候选人,起草一段150至200字的英文Upwork投标提案,要求:开头一句话点出客户痛点,中间两句引用候选人最相关的两段经历,结尾给出下一步建议并附上Deck链接。若某职位与所有候选人技能都不匹配,则跳过该职位。
只输出一个JSON数组,不要输出任何其他文字。数组元素格式:{"jobId":"字符串","candidateName":"字符串","proposalText":"字符串","skipped":false,"skipReason":""};跳过的职位输出{"jobId":"字符串","skipped":true,"skipReason":"不匹配原因"}。
输入数据:
职位={{ $json.jobs }}
候选人={{ $json.candidates }}
deckLink={{ $json.deckLink }}Gemini返回体的校验代码片段
本站独立编写的示例校验逻辑,Gemini实际返回结构以所用API版本为准,路径需在n8n中核对。
// n8n Code节点:解析并校验Gemini输出
const raw = $input.first().json.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
let items;
try {
items = JSON.parse(raw.replace(/^```json|```$/gm, "").trim());
} catch (e) {
return [{ json: { valid: false, reason: "JSON解析失败", raw } }];
}
if (!Array.isArray(items) || items.length === 0) {
return [{ json: { valid: false, reason: "空结果", raw } }];
}
const proposals = items.filter(i => i.skipped !== true);
for (const p of proposals) {
if (!p.jobId || !p.candidateName || !p.proposalText) {
return [{ json: { valid: false, reason: "字段缺失: " + (p.jobId || "未知") } }];
}
}
return [{ json: { valid: true, proposals, skipped: items.filter(i => i.skipped === true) } }];输入与输出示例
本站虚构演示,非Gemini实际调用结果;链接为占位域名。
输入
职位jobs=[{"jobId":"up-101","title":"Shopify店铺速度优化","description":"移动端首屏5秒,需压缩与懒加载","budget":"$300"}];候选人candidates=[{"name":"李萌","skills":"Shopify,Liquid,性能优化","resumeText":"三年Shopify优化经验,曾把某服饰店首屏从6秒降到1.8秒","driveLink":"https://drive.example.com/f/abc"}];deckLink="https://drive.example.com/f/deck"对应输出
[{"jobId":"up-101","candidateName":"李萌","proposalText":"Your mobile storefront taking five seconds to load is costing you conversions. Our specialist Meng Li has three years of Shopify performance work, including cutting a fashion store's first paint from six seconds to 1.8 seconds using image compression and lazy loading. We would start with a free speed audit this week. See our studio deck: https://drive.example.com/f/deck","skipped":false,"skipReason":""}]做完检查这些
- 连续两次触发同一批Slack消息时,第二次不产生新提案,说明静态数据去重生效
- Drive文件夹中放入两份PDF后,候选人档案数组长度与PDF数量一致且每个元素含driveLink
- Gemini返回合法JSON数组时,Gmail发出邮件且ClickUp任务数等于未跳过提案数
- 把Gemini提示临时改成返回空数组后,流程走人工复核分支并在Slack发出复核提醒
容易卡住的地方
- 静态工作流数据只在同一工作流激活状态下保留,停用重开可能重复处理旧消息,测试前先清空集合
- 两分钟轮询叠加多条职位会拉长Gemini提示,可能触发token上限,建议每批限制职位数
- Drive搜索Deck若同名文件多会取错链接,建议给Deck固定命名并核对查询条件
- Gemini返回常带```json包裹,校验前必须先剥离,否则解析必失败进复核分支
本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。
- 原文标题
- Send tailored Upwork job proposals with Gemini, Slack, Gmail, Drive and ClickUp
- 原作者
- Rahul Joshi
- 原始发布日期
- 2026-09-04T12:50:13.591Z
- 来源
- https://n8n.io/workflows/19203