{
  "name": "Lancer: External decision & proposal (sample)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lancer-job-new",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000001",
      "name": "Webhook (job.new)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "webhookId": "lancer-job-new",
      "notes": "Paste the Production URL from this node into Lancer, Campaign, Integrations, Add webhook. Subscribe to job.new. This sample is for External decision & proposal mode: Lancer sends every job that passes the campaign filters, unscored, and the 'Qualify with AI' step below decides. For External proposal mode (Lancer already screened the job) use the lancer-n8n-workflow-external-proposal.json sample instead, which has no qualifier."
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Title: {{ $json.body.data.lead.title }}\n\nDescription: {{ $json.body.data.lead.description }}",
        "messages": {
          "messageValues": [
            {
              "message": "You are a freelance-job qualifier. I'm a Zoho expert. Qualify every job that is clearly about Zoho (Zoho CRM, Zoho Creator, Zoho Books, Zoho Desk, Deluge scripting, Zoho One, etc.) as suitable. Everything else is unsuitable, even if the budget looks nice.\n\nRespond with exactly one lowercase word and nothing else: either \"suitable\" or \"unsuitable\"."
            }
          ]
        },
        "batching": {}
      },
      "id": "1f000001-0000-0000-0000-000000000005",
      "name": "Qualify with AI",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        480,
        300
      ],
      "notes": "Uses the sub-node 'OpenRouter Chat Model (fast)' to decide if the job matches your niche. Swap the system prompt for your own rules. This is the step External proposal mode keeps inside Lancer; in External decision & proposal mode it is yours."
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "google/gemini-3.1-flash-lite-preview",
          "mode": "list",
          "cachedResultName": "google/gemini-3.1-flash-lite-preview"
        },
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000006",
      "name": "OpenRouter Chat Model (fast)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        480,
        540
      ],
      "notes": "Cheap, fast model for routing (qualifier). Set up an OpenRouter credential (API key from openrouter.ai)."
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "anthropic/claude-3.5-sonnet",
          "mode": "list",
          "cachedResultName": "anthropic/claude-3.5-sonnet"
        },
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-00000000000a",
      "name": "OpenRouter Chat Model (writer)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        1070,
        540
      ],
      "notes": "Stronger model for writing the cover letter and designing the proposal page. Claude 3.5 Sonnet is a good default. Swap for any OpenRouter model slug you prefer."
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "suitable-check",
              "leftValue": "={{ $json.text.trim().toLowerCase() }}",
              "rightValue": "suitable",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000002",
      "name": "Suitable?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        720,
        300
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Title: {{ $('Webhook (job.new)').item.json.body.data.lead.title }}\n\nDescription: {{ $('Webhook (job.new)').item.json.body.data.lead.description }}\n\nSkills: {{ ($('Webhook (job.new)').item.json.body.data.lead.skills || []).map(s => s.name).join(', ') }}",
        "messages": {
          "messageValues": [
            {
              "message": "You are writing a short, specific Upwork proposal cover letter for a senior Zoho consultant.\n\nRules:\n- Open with one sentence that names something concrete from the job post (a Zoho module, a pain point, an integration they mentioned). Do NOT start with \"Hi\" or \"I hope this finds you well\".\n- 4 to 6 short paragraphs. Plain prose. No bullet lists, no headings, no emojis.\n- Mention one directly relevant thing you've actually shipped (invent a believable small example if needed).\n- End with one specific question about their current Zoho setup, not a generic \"happy to chat\".\n- Avoid buzzwords (\"synergy\", \"leverage\", \"cutting-edge\", \"perfect fit\").\n\nReturn only the cover letter body. No subject line, no signature, no greeting placeholder."
            }
          ]
        },
        "batching": {}
      },
      "id": "1f000001-0000-0000-0000-000000000009",
      "name": "Generate cover letter",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        960,
        200
      ],
      "notes": "Writes the plain-text proposal body for the /bid call. Uses the 'writer' model sub-node. Swap the system prompt for your voice."
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Job title: {{ $('Webhook (job.new)').item.json.body.data.lead.title }}\nCategory: {{ $('Webhook (job.new)').item.json.body.data.lead.category }}\nSkills: {{ ($('Webhook (job.new)').item.json.body.data.lead.skills || []).map(s => s.name).join(', ') }}\nProject duration: {{ $('Webhook (job.new)').item.json.body.data.lead.projectDuration || 'Not specified' }}\nExperience level: {{ ($('Webhook (job.new)').item.json.body.data.lead.metadata && $('Webhook (job.new)').item.json.body.data.lead.metadata.experienceLevel) || 'Not specified' }}\nClient country: {{ ($('Webhook (job.new)').item.json.body.data.lead.clientInfo && $('Webhook (job.new)').item.json.body.data.lead.clientInfo.country) || 'Unknown' }}\nClient rating: {{ ($('Webhook (job.new)').item.json.body.data.lead.clientInfo && $('Webhook (job.new)').item.json.body.data.lead.clientInfo.rating) || 'n/a' }}\n\nCover letter (render as real prose paragraphs on the page):\n{{ $('Generate cover letter').item.json.text }}",
        "messages": {
          "messageValues": [
            {
              "message": "You design a premium, editorial proposal page as a complete, self-contained HTML document. The page is the freelancer's pitch, hosted standalone; it will be linked from the Upwork proposal.\n\nDesign philosophy (37signals / Basecamp):\n- Editorial, not marketing. Reads like a thoughtful letter, not a landing page.\n- Quiet confidence over flash. No gradients, no hero banners, no big CTA buttons, no badges, no hype.\n- Serif body type (font-family stack: 'Iowan Old Style', 'Charter', Georgia, serif). Sans-serif headings and labels (system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif).\n- Single column. max-width between 640px and 720px. Centered. line-height around 1.7. Body font-size 18px.\n- Warm off-white background (#faf7f2 or similar cream). Deep charcoal body text (#1a1a1a). Muted secondary text (#6b6357).\n- One restrained accent color used sparingly (a muted brick red like #9b3d2a, an ink blue like #1f3a5f, or a deep olive like #4a5d23). The accent appears only as thin rules, small-caps kicker labels, and the signature flourish. No large colored fills.\n- Uppercase letter-spaced (letter-spacing: 0.14em) kicker labels above sections, rendered small (11-12px) in the accent color. E.g. 'PROPOSAL FOR', 'ABOUT THE JOB', 'FROM'.\n- Generous whitespace. Structural cues come from typography and rules, not cards/borders/shadows.\n- No emojis, no icons, no rounded buttons, no drop shadows, no gradients.\n- Render the cover letter (provided in the user message) as real <p> paragraphs. Preserve paragraph breaks. Do not wrap it in <pre>.\n- 'About the job' section: a quiet definition list (<dl>) or plain aligned lines listing the facts supplied. Minimal styling, no table borders.\n- End with a plain signature block: first name, role (e.g. 'Senior Zoho Consultant'), thin accent-colored rule above it. No contact info, no social links.\n\nOutput rules:\n- Output ONLY the HTML. Start with <!doctype html>. No markdown code fences, no prose explanation before or after.\n- Inline all CSS in a single <style> block. No external fonts, no images, no JS.\n- HTML-escape any user content you insert (& -> &amp;, < -> &lt;, > -> &gt;)."
            }
          ]
        },
        "batching": {}
      },
      "id": "1f000001-0000-0000-0000-00000000000b",
      "name": "Generate proposal page",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        1180,
        200
      ],
      "notes": "LLM generates the full HTML page (not a template). Outputs only the HTML document. Uses the 'writer' model sub-node. Claude 3.5 Sonnet or Gemini 3 Pro are recommended; smaller models tend to fall into generic landing-page styles."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.github.com/gists",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer REPLACE_WITH_UPWORK_ACCESS_TOKEN"
            },
            {
              "name": "Accept",
              "value": "application/vnd.github+json"
            },
            {
              "name": "X-GitHub-Api-Version",
              "value": "2022-11-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ description: 'Lancer proposal: ' + $('Webhook (job.new)').item.json.body.data.lead.title, public: false, files: { 'proposal.html': { content: String($json.text || '').replace(/^```(?:html)?\\s*/i, '').replace(/\\s*```\\s*$/,'') } } }) }}",
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000008",
      "name": "Create Gist",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1400,
        200
      ],
      "notes": "Hosts the LLM-generated HTML as a secret GitHub gist (free). Strips accidental ```html code fences if the model wraps its output. Auth token is inlined for fast setup; rotate it if you share the workflow. Rendered URL: https://gistpreview.github.io/?<gist_id>."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Webhook (job.new)').item.json.body.data.bidUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": false,
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ coverLetter: $('Generate cover letter').item.json.text + '\\n\\nMore detail on how I\\'d approach this: https://gistpreview.github.io/?' + $json.id, questionAnswerPairs: ($('Webhook (job.new)').item.json.body.data.lead.questions || []).map(q => ({ question: q, answer: 'Yes, happy to cover this in a short intro call.' })), dryRun: true }) }}",
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000003",
      "name": "Submit bid (dryRun)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1620,
        200
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "Lancer Public API key"
        }
      },
      "notes": "Set up an HTTP Header Auth credential with Name=Authorization and Value=Bearer lk_... (your Lancer API key). dryRun is true so nothing hits Upwork; flip it to false when you're ready for a real bid. The cover letter appends the gistpreview.github.io URL of the hosted HTML page."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Webhook (job.new)').item.json.body.data.rejectUrl }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={ \"reason\": \"Not a Zoho-related job\" }",
        "options": {}
      },
      "id": "1f000001-0000-0000-0000-000000000004",
      "name": "Reject lead",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        400
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "Lancer Public API key"
        }
      }
    }
  ],
  "connections": {
    "Webhook (job.new)": {
      "main": [
        [
          {
            "node": "Qualify with AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Qualify with AI": {
      "main": [
        [
          {
            "node": "Suitable?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model (fast)": {
      "ai_languageModel": [
        [
          {
            "node": "Qualify with AI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model (writer)": {
      "ai_languageModel": [
        [
          {
            "node": "Generate cover letter",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Generate proposal page",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Suitable?": {
      "main": [
        [
          {
            "node": "Generate cover letter",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reject lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate cover letter": {
      "main": [
        [
          {
            "node": "Generate proposal page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate proposal page": {
      "main": [
        [
          {
            "node": "Create Gist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Gist": {
      "main": [
        [
          {
            "node": "Submit bid (dryRun)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "pinData": {},
  "versionId": "6",
  "tags": [
    {
      "name": "lancer",
      "createdAt": "2026-04-22T00:00:00.000Z",
      "updatedAt": "2026-04-22T00:00:00.000Z"
    }
  ]
}
