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": "Upwork Proposal Follow-up \u00b7 Connects Reclaim",
"nodes": [
{
"id": "bb000000-0000-4000-8000-000000000001",
"name": "\u25b6 Run now",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-360,
540
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000002",
"name": "\ud83d\udd52 Daily",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-360,
760
],
"parameters": {
"rule": {
"interval": [
{
"field": "days",
"daysInterval": 1
}
]
}
},
"typeVersion": 1.2
},
{
"id": "bb000000-0000-4000-8000-000000000003",
"name": "\u2460 Fetch proposals",
"type": "n8n-nodes-base.httpRequest",
"position": [
40,
620
],
"parameters": {
"url": "http://localhost:4178/api/proposals",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "bb000000-0000-4000-8000-000000000004",
"name": "\u2461 Filter active + age",
"type": "n8n-nodes-base.code",
"position": [
420,
620
],
"parameters": {
"jsCode": "// keep submitted/pending proposals, bucket by age\nconst props = ($input.first().json.proposals) || [];\nconst active = props.filter(p => /submitted|accepted|pending|viewed/i.test((p.status_label||p.status||'')));\nreturn active.map(p => ({ json: { ...p, bucket: p.age_days>=14 ? 'withdraw' : p.age_days>=5 ? 'followup' : 'fresh' } }));"
},
"typeVersion": 2
},
{
"id": "bb000000-0000-4000-8000-000000000005",
"name": "\u2462 Route by age",
"type": "n8n-nodes-base.switch",
"position": [
800,
620
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "\ud83d\udd14 Follow up (5-13d)",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.bucket }}",
"rightValue": "followup"
}
]
}
},
{
"outputKey": "\ud83d\uddd1 Stale (14d+)",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.bucket }}",
"rightValue": "withdraw"
}
]
}
},
{
"outputKey": "\ud83d\udc40 Fresh (<5d)",
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.bucket }}",
"rightValue": "fresh"
}
]
}
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "bb000000-0000-4000-8000-000000000006",
"name": "\u270d Compose nudge",
"type": "n8n-nodes-base.code",
"position": [
1180,
400
],
"parameters": {
"jsCode": "// a polite, ready-to-send follow-up (edit tone as you like)\nconst p = $json;\nconst msg = `Hi \u2014 just following up on my proposal for \"${p.title}\". I'm still very interested and have availability to start this week. Happy to hop on a quick call or answer any questions. Thanks!`;\nreturn [{ json: { id: p.id, title: p.title, age_days: p.age_days, followup_message: msg } }];"
},
"typeVersion": 2
},
{
"id": "bb000000-0000-4000-8000-000000000007",
"name": "\ud83d\uddd1 Withdraw candidate",
"type": "n8n-nodes-base.noOp",
"position": [
1180,
620
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000008",
"name": "\ud83d\udc40 Fresh (hold)",
"type": "n8n-nodes-base.noOp",
"position": [
1180,
840
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000009",
"name": "\ud83d\udcca Build digest",
"type": "n8n-nodes-base.code",
"position": [
420,
1040
],
"parameters": {
"jsCode": "// summary across all active proposals\nconst all = $('\u2461 Filter active + age').all().map(i=>i.json);\nconst f = all.filter(p=>p.bucket==='followup'), w = all.filter(p=>p.bucket==='withdraw');\nconst L = ['# Proposal follow-up','',`Active: ${all.length} \u00b7 ${f.length} due a nudge \u00b7 ${w.length} stale (reclaim connects)`];\nif (f.length){ L.push('','## Nudge these'); f.forEach(p=>L.push(`- ${p.title} \u2014 ${p.age_days}d old`)); }\nif (w.length){ L.push('','## Consider withdrawing (free up connects)'); w.forEach(p=>L.push(`- ${p.title} \u2014 ${p.age_days}d, no reply`)); }\nreturn [{ json: { digest: L.join('\\n'), active: all.length, followup: f.length, stale: w.length } }];"
},
"typeVersion": 2
},
{
"id": "bb000000-0000-4000-8000-000000000010",
"name": "\ud83d\udce4 Report",
"type": "n8n-nodes-base.noOp",
"position": [
780,
1040
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000011",
"name": "note-in",
"type": "n8n-nodes-base.stickyNote",
"position": [
20,
420
],
"parameters": {
"color": 4,
"width": 720,
"height": 140,
"content": "## \u2460 YOUR PROPOSALS\nDaily, pull your submitted proposals with an age (days since sent)."
},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000012",
"name": "note-route",
"type": "n8n-nodes-base.stickyNote",
"position": [
780,
420
],
"parameters": {
"color": 5,
"width": 340,
"height": 140,
"content": "## \u2461 AGE & ROUTE\nBucket each: fresh (<5d) \u00b7 nudge (5\u201313d) \u00b7 stale (14d+)."
},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000013",
"name": "note-act",
"type": "n8n-nodes-base.stickyNote",
"position": [
1160,
200
],
"parameters": {
"color": 2,
"width": 760,
"height": 140,
"content": "## \u2462 ACT\nDraft a follow-up for aging proposals; flag stale ones to withdraw and reclaim connects."
},
"typeVersion": 1
},
{
"id": "bb000000-0000-4000-8000-000000000014",
"name": "note-digest",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
840
],
"parameters": {
"color": 6,
"width": 700,
"height": 140,
"content": "## \u2463 DIGEST\nOne summary of what to nudge vs withdraw. Add Slack / email here."
},
"typeVersion": 1
}
],
"connections": {
"\u25b6 Run now": {
"main": [
[
{
"node": "\u2460 Fetch proposals",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd52 Daily": {
"main": [
[
{
"node": "\u2460 Fetch proposals",
"type": "main",
"index": 0
}
]
]
},
"\u2460 Fetch proposals": {
"main": [
[
{
"node": "\u2461 Filter active + age",
"type": "main",
"index": 0
}
]
]
},
"\u2461 Filter active + age": {
"main": [
[
{
"node": "\u2462 Route by age",
"type": "main",
"index": 0
},
{
"node": "\ud83d\udcca Build digest",
"type": "main",
"index": 0
}
]
]
},
"\u2462 Route by age": {
"main": [
[
{
"node": "\u270d Compose nudge",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\uddd1 Withdraw candidate",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\udc40 Fresh (hold)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Build digest": {
"main": [
[
{
"node": "\ud83d\udce4 Report",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Upwork Proposal Follow-up · Connects Reclaim. Uses httpRequest. Event-driven trigger; 14 nodes.
Source: https://github.com/Majboor/upwork-mcp-cli/blob/main/examples/n8n/upwork-proposal-followup.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 listens for an “Approved” label on a Trello card, reads the AI draft bookkeeping JSON from card comments, and posts the corresponding transaction to Xero. It then adds a Xero deep link b
02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.
This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t
[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.
[](https://youtu.be/c7yCZhmMjtI)