{
  "name": "Send new UK and EU government tenders to Slack or email with Apify",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "daysInterval": 1,
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "8167a831-fcc5-5e17-bc6c-4c519079edad",
      "name": "Every morning at 07:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "actorId": {
          "__rl": true,
          "value": "dvIFHf7CVK9PdqdSN",
          "mode": "id"
        },
        "customBody": "{\n  \"sources\": [\n    \"uk-find-a-tender\",\n    \"uk-contracts-finder\",\n    \"eu-ted\"\n  ],\n  \"keywords\": [\n    \"software\",\n    \"cloud\",\n    \"data platform\"\n  ],\n  \"statuses\": [\n    \"open\"\n  ],\n  \"minValue\": 50000,\n  \"daysBack\": 3,\n  \"onlyNew\": true,\n  \"maxResults\": 200\n}",
        "maxTotalChargeUsd": 1
      },
      "id": "1c1bcef5-95a0-5843-9dfb-dd36460d9fc5",
      "name": "Find new tenders",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "credentials": {
        "apifyApi": {
          "id": "REPLACE_WITH_YOUR_APIFY_CREDENTIAL",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// One message per run. If the Actor found nothing new, stop here so empty days stay quiet.\nconst tenders = $input.all().map((i) => i.json).filter((t) => t.title);\nif (tenders.length === 0) return [];\n\nconst money = (v, c) => (v == null ? 'value not stated' : `${c ?? ''} ${Math.round(v).toLocaleString('en-GB')}`.trim());\nconst esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\nconst sorted = tenders.sort((a, b) => (a.closingAt ?? '9999').localeCompare(b.closingAt ?? '9999'));\nconst top = sorted.slice(0, 40);\nconst more = tenders.length - top.length;\n\nconst slack = top.map((t) =>\n  `• <${t.url}|${t.title.slice(0, 140)}>\\n   ${t.agency ?? 'Unknown buyer'} (${t.country ?? '?'}) · ${money(t.value, t.currency)} · closes ${t.closingAt ? t.closingAt.slice(0, 10) : 'n/a'}`);\nconst rows = top.map((t) =>\n  `<tr><td><a href=\"${esc(t.url)}\">${esc(t.title)}</a><br><small>${esc(t.agency ?? 'Unknown buyer')}</small></td>` +\n  `<td>${esc(t.country ?? '')}</td><td>${esc(money(t.value, t.currency))}</td><td>${esc(t.closingAt ? t.closingAt.slice(0, 10) : 'n/a')}</td></tr>`);\n\nconst title = `${tenders.length} new government tender${tenders.length === 1 ? '' : 's'}`;\nreturn [{ json: {\n  text: `*${title}*\\n\\n${slack.join('\\n')}${more > 0 ? `\\n…and ${more} more.` : ''}`,\n  subject: title,\n  html: `<h2>${title}</h2><table cellpadding=\"6\" border=\"1\" style=\"border-collapse:collapse\">` +\n        `<tr><th align=\"left\">Tender</th><th>Country</th><th>Value</th><th>Closes</th></tr>${rows.join('')}</table>` +\n        (more > 0 ? `<p>…and ${more} more.</p>` : ''),\n} }];"
      },
      "id": "831f6013-1626-59d6-87ba-5b1586a76313",
      "name": "Build the message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#tenders",
          "mode": "name"
        },
        "text": "={{ $json.text }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "2e7effc1-9995-560c-a181-e8890e9c40f5",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        740,
        -100
      ],
      "credentials": {
        "slackApi": {
          "id": "REPLACE_WITH_YOUR_SLACK_CREDENTIAL",
          "name": "Slack account"
        }
      },
      "webhookId": "dd94f237-cb76-5a73-aede-7b21e5349281"
    },
    {
      "parameters": {
        "fromEmail": "alerts@example.com",
        "toEmail": "you@example.com",
        "subject": "={{ $json.subject }}",
        "emailFormat": "html",
        "html": "={{ $json.html }}",
        "options": {}
      },
      "id": "20469268-baeb-5e98-9cc2-8005cd62d60f",
      "name": "Send by email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        740,
        100
      ],
      "credentials": {
        "smtp": {
          "id": "REPLACE_WITH_YOUR_SMTP_CREDENTIAL",
          "name": "SMTP account"
        }
      },
      "webhookId": "81b9d12f-3958-5541-9db0-885007327a7a",
      "disabled": true
    },
    {
      "parameters": {
        "content": "## Find new tenders\nEdit the filters in the Input JSON.",
        "height": 260,
        "width": 440,
        "color": 7
      },
      "id": "046eac17-9d5f-55d2-a3ab-786b52ec3ae1",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -50,
        -130
      ]
    },
    {
      "parameters": {
        "content": "## Build and send\nSlack is on. Email is off until you activate it.",
        "height": 460,
        "width": 460,
        "color": 7
      },
      "id": "1fd49f29-a572-5bb0-9e23-1908559b3eaf",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        430,
        -230
      ]
    },
    {
      "parameters": {
        "content": "## Send new UK and EU government tenders to Slack or email\n\nFor bid teams, consultancies and suppliers who sell to the public sector and want one short list of fresh tenders each morning instead of checking three portals.\n\n### How it works\n1. Every morning the schedule runs the **gov-tenders** Actor on Apify.\n2. It reads UK Find a Tender, UK Contracts Finder and EU TED, keeps open notices that match your keywords and minimum value, and with `onlyNew` returns only notices it has not sent you before.\n3. A Code node builds one Slack message (and one HTML email), soonest deadline first. Days with nothing new send nothing.\n\n### Setup\n1. Add your own Apify API token as an *Apify API* credential (Apify Console > Settings > API & Integrations).\n2. Pick your Slack credential and channel. For email, activate **Send by email** and add SMTP.\n3. Edit the Input JSON: `keywords`, `cpvCodes`, `countries`, `minValue`. Keep it the same between runs so `onlyNew` remembers what was sent.\n\n### Requirements\nApify account (the free plan returns up to 100 tenders per run) and Slack or SMTP. The Actor charges $3 per 1,000 tenders; the node caps each run at $1.\n\n**Community node:** uses the verified Apify node (`@apify/n8n-nodes-apify`). On n8n Cloud it installs from the canvas; on self-hosted n8n install it under Settings > Community nodes.\n\n### Customization tips\nAdd `ca-canadabuys` or `au-austender` to `sources`, or add `awarded` to `statuses` to see who won.\n\nMade by locaihost, a small studio that builds its tools with AI agents.",
        "height": 817,
        "width": 560
      },
      "id": "7b53134d-cb88-537b-b029-4a4ce7222fe5",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -670,
        -230
      ]
    }
  ],
  "connections": {
    "Every morning at 07:00": {
      "main": [
        [
          {
            "node": "Find new tenders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find new tenders": {
      "main": [
        [
          {
            "node": "Build the message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build the message": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send by email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
