AutomationFlowsSlack & Telegram › Send Weekly Hr Job and Contact Digests to Slack with Coresignal

Send Weekly Hr Job and Contact Digests to Slack with Coresignal

ByArtur Mulagalijev @amulagalijev on n8n.io

This workflow runs weekly, searches Coresignal job ads for recent roles matching your keywords and country, enriches each posting with the hiring company and a likely HR contact, and sends a single Slack digest with the job details and contact information. Runs every Monday at…

Cron / scheduled trigger★★★★☆ complexity26 nodesN8N Nodes Coresignal ApiSlack
Slack & Telegram Trigger: Cron / scheduled Nodes: 26 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #18126 — we link there as the canonical source.

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 →

Download .json
{
  "id": "PuYluuZv48Qz4rRX",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Track hiring signals from job ads and enrich HR contacts with Coresignal",
  "tags": [],
  "nodes": [
    {
      "id": "e55c9b4b-e050-4a83-8017-b7ca00fee581",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6144,
        2928
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Track hiring signals from job ads and enrich HR contacts with Coresignal\n\n### How it works\n\nThis workflow runs weekly to search Coresignal for recent job ads that match configured hiring keywords, then processes a limited set of job IDs one by one. For each job, it collects the job details, attempts to match the hiring company, searches for a likely HR contact, and merges the job and HR profile into a digest row. After all jobs are processed, it builds a Slack Block Kit digest and posts it to Slack.\n\n### Setup steps\n\n- Configure the schedule in the Weekly Trigger node for the desired reporting cadence.\n- Add valid Coresignal credentials to all Coresignal nodes and confirm the selected endpoints support job search, job collect, company enrichment, employee search, and employee collect operations.\n- Update the Search Config node with the target job_keywords, country, days_back, and max_jobs values.\n- Configure the Slack node with Slack credentials and the destination channel for the hiring-signal digest.\n\n### Customization\n\nAdjust the search keywords, geography, date window, and max job count in Search Config. You can also modify the code nodes that pick the top HR profile or format the Slack digest to change contact-selection rules and message layout."
      },
      "typeVersion": 1
    },
    {
      "id": "ee588e8b-3cab-449f-bb5f-744389c177f6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6704,
        3136
      ],
      "parameters": {
        "color": 7,
        "width": 912,
        "height": 304,
        "content": "## Schedule job search\n\nStarts the weekly run, applies the search parameters, queries Coresignal for matching job ads, and collects a capped list of job IDs for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "84df299c-42c5-46c1-92a6-6148489caeb7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7664,
        3136
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 480,
        "content": "## Iterate job records\n\nLoops through the selected job IDs, collects each full job record from Coresignal, flattens it for downstream use, and checks whether a company domain is available for enrichment."
      },
      "typeVersion": 1
    },
    {
      "id": "4248d883-ad2b-47a6-a073-4a191392c005",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        8560,
        3264
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Match hiring company\n\nUses the company domain to enrich and match the employer record, then branches based on whether a company match was found."
      },
      "typeVersion": 1
    },
    {
      "id": "f5fc740d-f532-40c0-a0a7-706eccc964c5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9008,
        3264
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 320,
        "content": "## Find HR candidate\n\nSearches Coresignal employee data for HR-related contacts at the matched company, keeps the highest-scoring employee ID, and verifies that a contact was found."
      },
      "typeVersion": 1
    },
    {
      "id": "6812791a-7bd8-4197-b67c-3a6976513df3",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9696,
        3248
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 320,
        "content": "## Build enriched row\n\nCollects the selected HR profile and merges it with the job record into a flat row that is returned to the loop for final aggregation."
      },
      "typeVersion": 1
    },
    {
      "id": "ba8b1100-36ce-4786-b95a-c97943835029",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9696,
        3600
      ],
      "parameters": {
        "color": 7,
        "height": 432,
        "content": "## Handle missing HR\n\nCreates a fallback row when the job lacks a company domain, the company cannot be matched, or no HR profile is found, then returns that row to the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "3820fc03-2085-4e31-8618-21f281c67fae",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7904,
        2784
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 320,
        "content": "## Send Slack digest\n\nAggregates all loop results into one Slack Block Kit digest and sends it to the configured Slack destination."
      },
      "typeVersion": 1
    },
    {
      "id": "4ce3b979-ef66-48f2-ae47-3375e78da456",
      "name": "When Monday at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        6752,
        3264
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1abe5b25-ce2f-42ef-b78e-154eb4eeb547",
      "name": "Set Search Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        6992,
        3264
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "job_keywords",
              "type": "string",
              "value": "\"software engineer\" OR \"backend engineer\" OR \"frontend engineer\" OR \"full stack engineer\" OR \"devops engineer\" OR \"platform engineer\" OR \"site reliability engineer\" OR \"data engineer\""
            },
            {
              "id": "s2",
              "name": "country",
              "type": "string",
              "value": "United States"
            },
            {
              "id": "s3",
              "name": "days_back",
              "type": "number",
              "value": 7
            },
            {
              "id": "s4",
              "name": "max_jobs",
              "type": "number",
              "value": 10
            },
            {
              "id": "s5",
              "name": "slack_channel",
              "type": "string",
              "value": "C0AST3Z3SBC"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a75005b6-7aae-4ee3-8034-b9ce0a811835",
      "name": "Search Jobs via Coresignal",
      "type": "n8n-nodes-coresignal-api.coresignal",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        7232,
        3264
      ],
      "parameters": {
        "esQuery": "={{ JSON.stringify({ query: { bool: { must: [ { query_string: { query: $json.job_keywords, default_field: \"title\" } }, { match: { country: $json.country } } ], filter: [ { term: { status: 1 } }, { range: { created_at: { gte: $now.minus({ days: $json.days_back }).toFormat(\"yyyy-MM-dd HH:mm:ss\") } } } ] } } }) }}",
        "resource": "job",
        "operation": "search_es_dsl"
      },
      "credentials": {
        "coresignalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "90c0164d-a840-4605-86f2-8f08a9a4c945",
      "name": "Extract Job IDs",
      "type": "n8n-nodes-base.code",
      "position": [
        7472,
        3264
      ],
      "parameters": {
        "jsCode": "// Coresignal search endpoints return a bare array of IDs, which n8n splits into one item per ID.\n// Flatten whatever shape arrives, dedupe, cap at max_jobs, emit one item per job ID.\nconst cfg = $('Set Search Parameters').first().json;\nconst max = Number(cfg.max_jobs) || 10;\n\nlet ids = [];\nfor (const it of $input.all()) {\n  const j = it.json;\n  if (typeof j === 'number' || (typeof j === 'string' && j)) {\n    ids.push(j);\n  } else if (Array.isArray(j)) {\n    ids = ids.concat(j);\n  } else if (j && typeof j === 'object') {\n    if (Array.isArray(j.body)) ids = ids.concat(j.body);\n    else if (Array.isArray(j.data)) ids = ids.concat(j.data);\n    else ids = ids.concat(Object.values(j));\n  }\n}\n\nids = ids\n  .filter(v => typeof v === 'number' || (typeof v === 'string' && String(v).trim() !== ''))\n  .map(v => String(v));\nids = [...new Set(ids)].slice(0, max);\n\nif (!ids.length) return [];\nreturn ids.map(id => ({ json: { job_id: id } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "a5534190-0ac3-4cec-90c6-76a6ad6e9569",
      "name": "Loop Over Job Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        7712,
        3264
      ],
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "typeVersion": 3
    },
    {
      "id": "38d84965-a2dd-4e98-9aae-63111836b8b1",
      "name": "Collect Job Details",
      "type": "n8n-nodes-coresignal-api.coresignal",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        7952,
        3440
      ],
      "parameters": {
        "jobId": "={{ $json.job_id }}",
        "resource": "job"
      },
      "credentials": {
        "coresignalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "a95cdae9-dba2-4db3-ac06-faf77f1a44f7",
      "name": "Prepare Job Record",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        8176,
        3440
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Flatten the Coresignal job record into the fields the digest needs.\nconst j = $json || {};\n\nconst val = (o, keys) => {\n  for (const k of keys) {\n    if (o && o[k] !== undefined && o[k] !== null && o[k] !== '') return o[k];\n  }\n  return '';\n};\n\nconst sources = Array.isArray(j.job_sources) ? j.job_sources : [];\nconst srcUrl = sources.length ? val(sources[0], ['url']) : '';\nconst locParts = [val(j, ['city']), val(j, ['state']), val(j, ['country'])].filter(Boolean);\n\nreturn {\n  job_id: String(val(j, ['id', 'job_id'])),\n  job_title: val(j, ['title']),\n  job_url: srcUrl || val(j, ['external_url']),\n  job_seniority: val(j, ['seniority']),\n  job_department: val(j, ['department']),\n  employment_type: val(j, ['employment_type']),\n  job_location: val(j, ['location']) || locParts.join(', '),\n  posted_at: String(val(j, ['date_posted', 'created_at', 'updated_at'])).slice(0, 10),\n  company_name: val(j, ['company_name']),\n  company_domain: val(j, ['company_domain']),\n  company_industry: val(j, ['company_industry']),\n  company_employees_count: val(j, ['company_employees_count'])\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "5ca234f7-f8ac-413e-b2d3-5c9612020820",
      "name": "Check Company Domain",
      "type": "n8n-nodes-base.if",
      "position": [
        8384,
        3440
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.company_domain }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "858fcabd-9c5f-483e-8f66-08a3a0bae334",
      "name": "Enrich Company Details",
      "type": "n8n-nodes-coresignal-api.coresignal",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        8608,
        3424
      ],
      "parameters": {
        "resource": "company",
        "operation": "enrich",
        "websiteUrl": "={{ $json.company_domain }}"
      },
      "credentials": {
        "coresignalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "78b67e35-8de7-4ea6-8972-1d50f6f4dbe8",
      "name": "Check Company Match",
      "type": "n8n-nodes-base.if",
      "position": [
        8832,
        3424
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c2",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.company_id || $json.id }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "f4c9c939-b842-479a-98c2-1efa5eea47cd",
      "name": "Search HR Contacts",
      "type": "n8n-nodes-coresignal-api.coresignal",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        9056,
        3424
      ],
      "parameters": {
        "esQuery": "={{ JSON.stringify({ query: { bool: { filter: [ { term: { active_experience_company_id: Number($json.company_id || $json.id) } }, { term: { is_working: 1 } } ], must: [ { bool: { minimum_should_match: 1, should: [ { term: { \"active_experience_department.exact\": \"Human Resources\" } }, { match_phrase: { active_experience_title: \"Talent Acquisition\" } }, { match_phrase: { active_experience_title: \"Recruiter\" } }, { match_phrase: { active_experience_title: \"Recruiting\" } }, { match_phrase: { active_experience_title: \"Head of People\" } }, { match_phrase: { active_experience_title: \"People Operations\" } }, { match_phrase: { active_experience_title: \"HR Manager\" } } ] } } ], should: [ { term: { \"active_experience_management_level.exact\": \"Head\" } }, { term: { \"active_experience_management_level.exact\": \"Manager\" } }, { term: { \"active_experience_management_level.exact\": \"Director\" } }, { term: { is_decision_maker: 1 } } ] } } }) }}",
        "operation": "search_es_dsl"
      },
      "credentials": {
        "coresignalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "c666fe73-fb53-4ada-a05c-5a467ca18799",
      "name": "Select Top HR ID",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        9280,
        3424
      ],
      "parameters": {
        "jsCode": "// Employee search returns a bare array of IDs ordered by _score \u2014 keep the top one.\nlet ids = [];\nfor (const it of $input.all()) {\n  const j = it.json;\n  if (typeof j === 'number' || (typeof j === 'string' && j)) {\n    ids.push(j);\n  } else if (Array.isArray(j)) {\n    ids = ids.concat(j);\n  } else if (j && typeof j === 'object') {\n    if (Array.isArray(j.body)) ids = ids.concat(j.body);\n    else if (Array.isArray(j.data)) ids = ids.concat(j.data);\n    else ids = ids.concat(Object.values(j));\n  }\n}\n\nids = ids.filter(v => typeof v === 'number' || (typeof v === 'string' && String(v).trim() !== ''));\n\nreturn [{ json: { hr_employee_id: ids.length ? String(ids[0]) : '' } }];"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "dad4d15c-cfc9-49ae-a926-849f08907aef",
      "name": "Check HR Profile",
      "type": "n8n-nodes-base.if",
      "position": [
        9504,
        3424
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c3",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.hr_employee_id }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "877765a1-94b6-4bf6-850e-175eb158427d",
      "name": "Collect HR Details",
      "type": "n8n-nodes-coresignal-api.coresignal",
      "onError": "continueRegularOutput",
      "maxTries": 3,
      "position": [
        9744,
        3408
      ],
      "parameters": {
        "employeeId": "={{ $json.hr_employee_id }}"
      },
      "credentials": {
        "coresignalApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "alwaysOutputData": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "a46488e5-f880-4d3f-a0f9-a14f25c6e910",
      "name": "Merge Job and HR Data",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        9968,
        3408
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge the job record with the HR profile into one flat digest row.\nconst job = $('Prepare Job Record').first().json;\nconst c = $json || {};\n\nconst val = (o, keys) => {\n  for (const k of keys) {\n    if (o && o[k] !== undefined && o[k] !== null && o[k] !== '') return o[k];\n  }\n  return '';\n};\n\nconst pickEmail = (p, key) => {\n  const coll = p && p.professional_emails_collection;\n  if (!Array.isArray(coll) || !coll.length) return '';\n  const sorted = coll.slice().sort((a, b) => (a.order_of_priority ?? 99) - (b.order_of_priority ?? 99));\n  return sorted[0][key] || '';\n};\n\nconst email = val(c, ['primary_professional_email', 'professional_email']) || pickEmail(c, 'professional_email');\nconst emailStatus = val(c, ['primary_professional_email_status']) || pickEmail(c, 'professional_email_status');\nconst name = val(c, ['full_name', 'name']);\n\nreturn {\n  ...job,\n  hr_found: !!name,\n  hr_name: name,\n  hr_title: val(c, ['active_experience_title', 'headline', 'job_title']),\n  hr_department: val(c, ['active_experience_department']),\n  hr_management_level: val(c, ['active_experience_management_level']),\n  hr_location: val(c, ['location_full', 'location_country']),\n  hr_linkedin: val(c, ['linkedin_url', 'professional_network_url', 'url']),\n  hr_email: email,\n  hr_email_status: emailStatus,\n  hr_coresignal_id: String(val(c, ['id']))\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "c8ee80ad-e32b-42d9-9c9c-7d33f65ad814",
      "name": "Handle Missing HR Info",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        9744,
        3856
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Reached when there is no company domain, no company match, or no HR profile.\n// The job still goes into the digest, just without a contact.\nconst job = $('Prepare Job Record').first().json;\n\nreturn {\n  ...job,\n  hr_found: false,\n  hr_name: '',\n  hr_title: '',\n  hr_department: '',\n  hr_management_level: '',\n  hr_location: '',\n  hr_linkedin: '',\n  hr_email: '',\n  hr_email_status: '',\n  hr_coresignal_id: ''\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "36aacb80-d030-42a1-a4ea-f86e8624153d",
      "name": "Create Slack Digest",
      "type": "n8n-nodes-base.code",
      "position": [
        7952,
        2944
      ],
      "parameters": {
        "jsCode": "// Build one Slack Block Kit digest from every job row collected by the loop.\nconst cfg = $('Set Search Parameters').first().json;\nconst rows = $input.all().map(i => i.json).filter(r => r && r.job_title);\n\nconst esc = (s) => String(s == null ? '' : s)\n  .replace(/&/g, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\n\nconst link = (url, label) => (url ? `<${url}|${esc(label)}>` : esc(label));\n\nconst periodStart = $now.minus({ days: Number(cfg.days_back) || 7 }).toFormat('d LLL');\nconst periodEnd = $now.toFormat('d LLL yyyy');\nconst withHr = rows.filter(r => r.hr_found).length;\n\nconst blocks = [\n  {\n    type: 'header',\n    text: { type: 'plain_text', text: `Weekly job digest \u2014 ${rows.length} new roles`, emoji: false }\n  },\n  {\n    type: 'context',\n    elements: [\n      {\n        type: 'mrkdwn',\n        text: `*${periodStart} \u2013 ${periodEnd}*  \u00b7  ${esc(cfg.country)}  \u00b7  HR contact found for ${withHr}/${rows.length}`\n      }\n    ]\n  },\n  { type: 'divider' }\n];\n\nconst textLines = [`Weekly job digest \u2014 ${rows.length} new roles (${periodStart} \u2013 ${periodEnd})`, ''];\n\nrows.forEach((r, i) => {\n  const meta = [r.job_location, r.job_seniority, r.employment_type, r.posted_at ? `posted ${r.posted_at}` : '']\n    .filter(Boolean)\n    .map(esc)\n    .join('  \u00b7  ');\n\n  let hrLine;\n  if (r.hr_found) {\n    const parts = [`*${esc(r.hr_name)}*`];\n    if (r.hr_title) parts.push(esc(r.hr_title));\n    if (r.hr_linkedin) parts.push(link(r.hr_linkedin, 'profile'));\n    if (r.hr_email) parts.push(esc(r.hr_email));\n    hrLine = `HR contact: ${parts.join('  \u00b7  ')}`;\n  } else {\n    hrLine = '_No HR contact found for this company_';\n  }\n\n  blocks.push({\n    type: 'section',\n    text: {\n      type: 'mrkdwn',\n      text: `*${i + 1}. ${link(r.job_url, r.job_title || 'Untitled role')}*  \u2014  ${esc(r.company_name)}\\n${meta}\\n${hrLine}`\n    }\n  });\n\n  textLines.push(`${i + 1}. ${r.job_title} \u2014 ${r.company_name} (${r.job_location || 'n/a'})`);\n  textLines.push(`   ${r.job_url || 'no url'}`);\n  textLines.push(`   HR: ${r.hr_found ? `${r.hr_name}, ${r.hr_title} ${r.hr_email || ''}` : 'not found'}`);\n});\n\nif (!rows.length) {\n  blocks.push({\n    type: 'section',\n    text: { type: 'mrkdwn', text: '_No matching job ads were posted in this period._' }\n  });\n  textLines.push('No matching job ads were posted in this period.');\n}\n\nblocks.push({ type: 'divider' });\nblocks.push({\n  type: 'context',\n  elements: [{ type: 'mrkdwn', text: 'Source: Coresignal multi-source jobs & employees' }]\n});\n\nreturn [{\n  json: {\n    blocks,\n    digest_text: textLines.join('\\n'),\n    notification_text: `Weekly job digest \u2014 ${rows.length} new roles, ${withHr} with an HR contact`,\n    job_count: rows.length,\n    hr_found_count: withHr,\n    slack_channel: cfg.slack_channel,\n    rows\n  }\n}];"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "1e856448-92b5-40b1-b69a-4e34a9bb448f",
      "name": "Send Digest to Slack",
      "type": "n8n-nodes-base.slack",
      "maxTries": 3,
      "position": [
        8192,
        2944
      ],
      "parameters": {
        "text": "={{ $json.notification_text }}",
        "select": "channel",
        "blocksUi": "={{ JSON.stringify({ blocks: $json.blocks }) }}",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.slack_channel }}"
        },
        "messageType": "block",
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.2,
      "waitBetweenTries": 2000
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "e3c0e487-452d-46a8-8009-f53ef8b3d216",
  "nodeGroups": [],
  "connections": {
    "Extract Job IDs": {
      "main": [
        [
          {
            "node": "Loop Over Job Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check HR Profile": {
      "main": [
        [
          {
            "node": "Collect HR Details",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Missing HR Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select Top HR ID": {
      "main": [
        [
          {
            "node": "Check HR Profile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect HR Details": {
      "main": [
        [
          {
            "node": "Merge Job and HR Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Job Record": {
      "main": [
        [
          {
            "node": "Check Company Domain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search HR Contacts": {
      "main": [
        [
          {
            "node": "Select Top HR ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Monday at 8am": {
      "main": [
        [
          {
            "node": "Set Search Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Company Match": {
      "main": [
        [
          {
            "node": "Search HR Contacts",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Missing HR Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect Job Details": {
      "main": [
        [
          {
            "node": "Prepare Job Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Slack Digest": {
      "main": [
        [
          {
            "node": "Send Digest to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Company Domain": {
      "main": [
        [
          {
            "node": "Enrich Company Details",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle Missing HR Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Job Batches": {
      "main": [
        [
          {
            "node": "Create Slack Digest",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Collect Job Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Job and HR Data": {
      "main": [
        [
          {
            "node": "Loop Over Job Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Search Parameters": {
      "main": [
        [
          {
            "node": "Search Jobs via Coresignal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich Company Details": {
      "main": [
        [
          {
            "node": "Check Company Match",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle Missing HR Info": {
      "main": [
        [
          {
            "node": "Loop Over Job Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Jobs via Coresignal": {
      "main": [
        [
          {
            "node": "Extract Job IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs weekly, searches Coresignal job ads for recent roles matching your keywords and country, enriches each posting with the hiring company and a likely HR contact, and sends a single Slack digest with the job details and contact information. Runs every Monday at…

Source: https://n8n.io/workflows/18126/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

This workflow runs weekly, reads alumni profile URLs from Google Sheets, looks up matching employee profiles in Coresignal, detects job changes based on the current active employer, posts alerts to Sl

Google Sheets, N8N Nodes Coresignal Api, Slack
Slack & Telegram

This workflow fully automates your team's daily standup process using Slack for communication, Notion for structured data storage, and Redis for real-time session management.

Notion, Slack, Redis
Slack & Telegram

debug. Uses httpRequest, slack, redis, mailgun. Scheduled trigger; 60 nodes.

HTTP Request, Slack, Redis +2
Slack & Telegram

Seller Follow-Up Engine (Enhanced). Uses httpRequest, slack. Scheduled trigger; 44 nodes.

HTTP Request, Slack
Slack & Telegram

This workflow is an automated employee time tracking and reporting system that monitors weekly work hours via TMetric, then delivers personalized summaries directly to each team member on Slack. It co

HTTP Request, Item Lists, Data Table +1