{
  "name": "Lancer: External proposal (sample)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lancer-job-new-external-proposal",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "1f000002-0000-0000-0000-000000000001",
      "name": "Webhook (job.new)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "webhookId": "lancer-job-new-external-proposal",
      "notes": "Paste the Production URL from this node into Lancer, Campaign, Integrations, Add webhook. Subscribe to job.new. This sample is for External proposal mode: Lancer's suitability AI has already screened the job, so every job.new you receive is one to write a proposal for. The payload carries data.lead.suitabilityRating and data.lead.suitabilityReason (why Lancer thinks it fits); they can be null on Test/Trigger payloads."
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "anthropic/claude-sonnet-4.5",
          "mode": "list",
          "cachedResultName": "anthropic/claude-sonnet-4.5"
        },
        "options": {}
      },
      "id": "1f000002-0000-0000-0000-000000000002",
      "name": "OpenRouter Chat Model (writer)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        480,
        540
      ],
      "notes": "Model for writing the cover letter. Set up an OpenRouter credential (API key from openrouter.ai). Swap for any OpenRouter model slug you prefer."
    },
    {
      "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(', ') }}\n\nScreening questions (answer each one; quote the question exactly):\n{{ ($('Webhook (job.new)').item.json.body.data.lead.questions || []).map((q, i) => (i + 1) + '. ' + q).join('\\n') || 'None' }}\n\nLancer's screening note: {{ $('Webhook (job.new)').item.json.body.data.lead.suitabilityReason || 'not available' }}",
        "messages": {
          "messageValues": [
            {
              "message": "You are writing a short, specific Upwork proposal cover letter for a senior Zoho consultant. Lancer has already decided this job is a fit; the screening note in the user message explains why. Use it to pick what to emphasise, but do not quote it or mention Lancer.\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, no answers to the screening questions (those are handled separately)."
            }
          ]
        },
        "batching": {}
      },
      "id": "1f000002-0000-0000-0000-000000000003",
      "name": "Generate cover letter",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        480,
        300
      ],
      "notes": "Writes the plain-text proposal body for the /bid call. The prompt includes the job title, description, skills, screening questions and Lancer's suitabilityReason as context for what to emphasise. Uses the 'writer' model sub-node. Swap the system prompt for your voice."
    },
    {
      "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: $json.text, 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": "1f000002-0000-0000-0000-000000000004",
      "name": "Submit bid (dryRun)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        300
      ],
      "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). Each questionAnswerPairs[].question is copied verbatim from lead.questions, as the API requires; replace the placeholder answer with your own. dryRun is true so nothing hits Upwork; flip it to false when you're ready for a real bid."
    }
  ],
  "connections": {
    "Webhook (job.new)": {
      "main": [
        [
          {
            "node": "Generate cover letter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model (writer)": {
      "ai_languageModel": [
        [
          {
            "node": "Generate cover letter",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Generate cover letter": {
      "main": [
        [
          {
            "node": "Submit bid (dryRun)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false,
  "pinData": {},
  "versionId": "1",
  "tags": [
    {
      "name": "lancer",
      "createdAt": "2026-09-13T00:00:00.000Z",
      "updatedAt": "2026-09-13T00:00:00.000Z"
    }
  ]
}
