{
  "name": "Post new jobs from company career pages to Slack with Apify",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 9
            }
          ]
        }
      },
      "id": "2580eeb2-f48f-5555-a38c-c660deca984e",
      "name": "Every day at 09:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "30bd26dc-2d90-5fba-a6a6-36c89efb37a0",
              "name": "companies",
              "value": "airbnb\nfigma\nnotion\nhttps://jobs.lever.co/spotify\nashby:ramp",
              "type": "string"
            },
            {
              "id": "b5cb4f58-11dd-5ad1-9de5-07342b117ec6",
              "name": "titleKeywords",
              "value": "engineer, data, designer",
              "type": "string"
            },
            {
              "id": "a046d8e6-e28f-5e76-8f89-89be3ff8f251",
              "name": "locations",
              "value": "",
              "type": "string"
            },
            {
              "id": "25d2c646-4e11-5db6-bf11-4b01b07d7a2a",
              "name": "remoteOnly",
              "value": false,
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "id": "8fad3071-7c78-5c4d-8976-5d4188e57b7a",
      "name": "Companies and filters",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "actorId": {
          "__rl": true,
          "value": "ooXv5feBjdefzRfdC",
          "mode": "id"
        },
        "customBody": "={{ JSON.stringify({\n  companies: String($json.companies).split('\\n').map((s) => s.trim()).filter(Boolean),\n  titleKeywords: String($json.titleKeywords ?? '').split(',').map((s) => s.trim()).filter(Boolean),\n  locations: String($json.locations ?? '').split(',').map((s) => s.trim()).filter(Boolean),\n  remoteOnly: $json.remoteOnly === true,\n  includeDescription: false,\n  onlyNew: true,\n  maxResults: 500\n}) }}",
        "maxTotalChargeUsd": 1
      },
      "id": "e14b2746-6214-5802-9aec-49e68d3d56fa",
      "name": "Find new jobs",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 2,
      "position": [
        480,
        0
      ],
      "credentials": {
        "apifyApi": {
          "id": "REPLACE_WITH_YOUR_APIFY_CREDENTIAL",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// One Slack message per run with the new jobs. Quiet days send nothing.\nconst jobs = $input.all().map((i) => i.json).filter((j) => j.title && j.url);\nif (jobs.length === 0) return [];\nconst esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst salary = (j) => {\n  if (j.salaryMin == null && j.salaryMax == null) return '';\n  const f = (n) => (n == null ? '?' : Math.round(n).toLocaleString('en-US'));\n  return ` · ${j.salaryCurrency ?? ''} ${f(j.salaryMin)}-${f(j.salaryMax)}`;\n};\nconst byCompany = new Map();\nfor (const j of jobs) {\n  const k = j.company ?? 'Other';\n  if (!byCompany.has(k)) byCompany.set(k, []);\n  byCompany.get(k).push(j);\n}\nconst lines = [];\nlet shown = 0;\nfor (const [company, list] of byCompany) {\n  if (shown >= 40) break;\n  lines.push(`*${esc(company)}*`);\n  for (const j of list.slice(0, Math.max(1, 40 - shown))) {\n    lines.push(`• <${j.url}|${esc(j.title.slice(0, 120))}> · ${esc(j.location ?? 'location not stated')}${salary(j)}`);\n    shown += 1;\n  }\n}\nconst more = jobs.length - shown;\nconst title = `${jobs.length} new job${jobs.length === 1 ? '' : 's'} at the companies you follow`;\nreturn [{ json: { text: `*${title}*\\n\\n${lines.join('\\n')}${more > 0 ? `\\n…and ${more} more.` : ''}` } }];"
      },
      "id": "a01e9cfd-153c-5c4d-9ff7-e7546ae09b23",
      "name": "Build the Slack message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#jobs",
          "mode": "name"
        },
        "text": "={{ $json.text }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "5c3d5ae9-2793-5fb7-a718-b7fe8410ace8",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        960,
        0
      ],
      "credentials": {
        "slackApi": {
          "id": "REPLACE_WITH_YOUR_SLACK_CREDENTIAL",
          "name": "Slack account"
        }
      }
    },
    {
      "parameters": {
        "content": "## Companies and filters\nOne company per line. Filters are comma-separated; leave empty for all.",
        "height": 280,
        "width": 660,
        "color": 7
      },
      "id": "3e9f7370-2ad5-592e-91ec-2a85627c6902",
      "name": "Companies",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -130
      ]
    },
    {
      "parameters": {
        "content": "## Build and send\nOne message per day, grouped by company.",
        "height": 290,
        "width": 480,
        "color": 7
      },
      "id": "c0f6b724-025d-5e76-a7bc-5b0e3a3234bf",
      "name": "Send",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        670,
        -130
      ]
    },
    {
      "parameters": {
        "content": "## Post new jobs from company career pages to Slack\n\nFor recruiters and sourcers tracking hiring at target accounts, sales teams using new roles as buying signals, and job seekers watching a shortlist of employers.\n\n### How it works\n1. Every morning **Companies and filters** sends your list to the **careers-jobs** Actor on Apify.\n2. It reads each company's own careers board (Greenhouse, Lever, Ashby, Personio, Teamtailor), keeps jobs that match your title keywords and locations, and with `onlyNew` returns only jobs it has not sent before.\n3. A Code node groups the new jobs by company into one Slack message, with location and pay range when the posting has one. Quiet days send nothing.\n\n### Setup\n1. Add your Apify API token as an *Apify API* credential.\n2. Pick your Slack credential and channel in **Post to Slack**.\n3. Edit **Companies and filters**. A company can be a board name (`figma`), a careers-board URL, or `ats:token` (`ashby:ramp`).\n\nThe first run returns every open job that matches (and posts up to 40 of them); after that you only see new ones. Changing the list or filters starts a fresh memory.\n\n### Cost per run\n$2 per 1,000 jobs. A first run over five companies is usually 100 to 400 jobs ($0.20 to $0.80); daily runs after that cost cents. The node caps each run at $1.\n\n**Apify node:** this uses the verified Apify node (`@apify/n8n-nodes-apify`). n8n Cloud installs it from the canvas; on self-hosted n8n add it under Settings > Community nodes.\n\n### Customization tips\nAdd `withSalaryOnly: true` to the Input JSON for jobs with a published pay range only.\n\nMade by locaihost data (locaihost.org).",
        "height": 820,
        "width": 580
      },
      "id": "6feecd3d-44ee-5ad3-bfbb-8cd127d8ef02",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -680,
        -240
      ]
    }
  ],
  "connections": {
    "Every day at 09:00": {
      "main": [
        [
          {
            "node": "Companies and filters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Companies and filters": {
      "main": [
        [
          {
            "node": "Find new jobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find new jobs": {
      "main": [
        [
          {
            "node": "Build the Slack message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build the Slack message": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
