AutomationFlowsMarketing & Ads › Venuedesk — Lead Discovery (daily)

Venuedesk — Lead Discovery (daily)

VenueDesk — Lead Discovery (Daily). Uses postgres, httpRequest. Scheduled trigger; 7 nodes.

Cron / scheduled trigger★★★★☆ complexity7 nodesPostgresHTTP Request
Marketing & Ads Trigger: Cron / scheduled Nodes: 7 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.

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
{
  "updatedAt": "2026-03-15T20:42:04.929Z",
  "createdAt": "2026-03-09T21:09:54.399Z",
  "id": "Wl4HBw8sGpfvTzta",
  "name": "VenueDesk \u2014 Lead Discovery (Daily)",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      },
      "id": "sched1",
      "name": "Daily at 6am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "path": "run-lead-discovery",
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "wh_disc",
      "name": "Manual Discovery Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        200,
        480
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "DO $$ BEGIN\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n    ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n    ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n    ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n  END IF;\nEND $$; SELECT 1;",
        "options": {}
      },
      "id": "ensure1",
      "name": "Ensure DB Columns",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        420,
        380
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const counties = [\n  'Greater London','Kent','Surrey','Hertfordshire','Essex',\n  'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n  'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n  'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n  'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n  'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n  {q:'village hall hire',             t:'Village Hall'},\n  {q:'community centre room hire',    t:'Community Centre'},\n  {q:'sports club function room',     t:'Sports Club'},\n  {q:'event venue hire',              t:'Event Venue'},\n  {q:'church hall hire',              t:'Community Centre'},\n  {q:'social club venue hire',        t:'Sports Club'},\n  {q:'hotel function room hire',      t:'Hotel'},\n  {q:'working mens club hire',        t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n  json: {\n    query:            v.q + ' ' + county,\n    county:           county,\n    venue_type_label: v.t\n  }\n}));"
      },
      "id": "gen1",
      "name": "Generate Searches",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        380
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://places.googleapis.com/v1/places:searchText",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Goog-Api-Key",
              "value": "={{$env.GOOGLE_PLACES_API_KEY}}"
            },
            {
              "name": "X-Goog-FieldMask",
              "value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "bodyParameters": {
          "parameters": [
            {
              "name": "textQuery",
              "value": "={{ $json.query }}"
            },
            {
              "name": "regionCode",
              "value": "GB"
            },
            {
              "name": "languageCode",
              "value": "en-GB"
            },
            {
              "name": "maxResultCount",
              "value": "=20"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 20000
        }
      },
      "id": "ddg_fetch",
      "name": "Search Google Maps",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        860,
        380
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype  = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n  console.log('No places returned for query:', genItem.query || 'unknown');\n  return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n  const name    = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n  const address = p.formattedAddress || '';\n  const website = p.websiteUri || '';\n  const phone   = p.nationalPhoneNumber || '';\n  const placeId = p.id || '';\n\n  if (!name || name.length < 3) continue;\n  if (SKIP.test(name)) continue;\n\n  results.push({\n    json: {\n      venue_name:  name.substring(0, 120),\n      website_url: website,\n      phone:       phone,\n      venue_type:  vtype,\n      county:      county,\n      notes:       ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n      place_id:    placeId,\n      source:      'google_maps'\n    }\n  });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
      },
      "id": "parse_ddg",
      "name": "Parse Google Maps Results",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        380
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n  $${{ $json.venue_name }}$$::text,\n  $$$$::text,\n  $${{ $json.website_url }}$$::text,\n  $${{ $json.phone }}$$::text,\n  $${{ $json.venue_type }}$$::text,\n  $$new$$::text,\n  $${{ $json.county }}$$::text,\n  $$google_maps$$::text,\n  $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n  SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n  SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
        "options": {}
      },
      "id": "insert1",
      "name": "Insert Lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1300,
        380
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Daily at 6am": {
      "main": [
        [
          {
            "node": "Ensure DB Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Discovery Trigger": {
      "main": [
        [
          {
            "node": "Ensure DB Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ensure DB Columns": {
      "main": [
        [
          {
            "node": "Generate Searches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Searches": {
      "main": [
        [
          {
            "node": "Search Google Maps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Google Maps Results": {
      "main": [
        [
          {
            "node": "Insert Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google Maps": {
      "main": [
        [
          {
            "node": "Parse Google Maps Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": true
  },
  "staticData": {
    "node:Daily at 6am": {
      "recurrenceRules": []
    }
  },
  "meta": null,
  "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
  "activeVersionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
  "versionCounter": 70,
  "triggerCount": 2,
  "shared": [
    {
      "updatedAt": "2026-03-09T21:09:54.399Z",
      "createdAt": "2026-03-09T21:09:54.399Z",
      "role": "workflow:owner",
      "workflowId": "Wl4HBw8sGpfvTzta",
      "projectId": "xsdyFVsct988qLUy",
      "project": {
        "updatedAt": "2025-12-04T20:24:17.537Z",
        "createdAt": "2025-12-04T19:47:48.685Z",
        "id": "xsdyFVsct988qLUy",
        "name": "andrew johnson <andrew.ralston.johnson@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "e2485274-7097-4eb5-8502-e39b2308096c"
      }
    }
  ],
  "tags": [],
  "activeVersion": {
    "updatedAt": "2026-03-10T17:44:36.839Z",
    "createdAt": "2026-03-10T17:44:36.839Z",
    "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
    "workflowId": "Wl4HBw8sGpfvTzta",
    "nodes": [
      {
        "parameters": {
          "rule": {
            "interval": [
              {
                "field": "cronExpression",
                "expression": "0 6 * * *"
              }
            ]
          }
        },
        "id": "sched1",
        "name": "Daily at 6am",
        "type": "n8n-nodes-base.scheduleTrigger",
        "typeVersion": 1.2,
        "position": [
          200,
          300
        ]
      },
      {
        "parameters": {
          "path": "run-lead-discovery",
          "httpMethod": "POST",
          "responseMode": "onReceived",
          "options": {}
        },
        "id": "wh_disc",
        "name": "Manual Discovery Trigger",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          200,
          480
        ],
        "webhookId": "run-lead-discovery"
      },
      {
        "parameters": {
          "operation": "executeQuery",
          "query": "DO $$ BEGIN\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='county') THEN\n    ALTER TABLE leads ADD COLUMN county text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='phone') THEN\n    ALTER TABLE leads ADD COLUMN phone text DEFAULT '';\n  END IF;\n  IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='leads' AND column_name='source') THEN\n    ALTER TABLE leads ADD COLUMN source text DEFAULT 'manual';\n  END IF;\nEND $$; SELECT 1;",
          "options": {}
        },
        "id": "ensure1",
        "name": "Ensure DB Columns",
        "type": "n8n-nodes-base.postgres",
        "typeVersion": 2.5,
        "position": [
          420,
          380
        ],
        "credentials": {
          "postgres": {
            "id": "iEsRYyB7vjr5G7i8",
            "name": "Postgres account 3"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "const counties = [\n  'Greater London','Kent','Surrey','Hertfordshire','Essex',\n  'Suffolk','Norfolk','Leicestershire','Derbyshire','West Midlands',\n  'Worcestershire','West Yorkshire','North Yorkshire','Lancashire',\n  'Greater Manchester','Cheshire','County Durham','Northumberland','Dorset','Hampshire',\n  'Oxfordshire','Cambridgeshire','Buckinghamshire','Berkshire','Devon',\n  'Cornwall','Somerset','Gloucestershire','Wiltshire','Shropshire'\n];\nconst venueTypes = [\n  {q:'village hall hire',             t:'Village Hall'},\n  {q:'community centre room hire',    t:'Community Centre'},\n  {q:'sports club function room',     t:'Sports Club'},\n  {q:'event venue hire',              t:'Event Venue'},\n  {q:'church hall hire',              t:'Community Centre'},\n  {q:'social club venue hire',        t:'Sports Club'},\n  {q:'hotel function room hire',      t:'Hotel'},\n  {q:'working mens club hire',        t:'Sports Club'}\n];\n\n// Rotate county by day-of-month so each day targets a different area\nconst county = counties[new Date().getDate() % counties.length];\n\nreturn venueTypes.map(v => ({\n  json: {\n    query:            v.q + ' ' + county,\n    county:           county,\n    venue_type_label: v.t\n  }\n}));"
        },
        "id": "gen1",
        "name": "Generate Searches",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          640,
          380
        ]
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://places.googleapis.com/v1/places:searchText",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "X-Goog-Api-Key",
                "value": "={{$env.GOOGLE_PLACES_API_KEY}}"
              },
              {
                "name": "X-Goog-FieldMask",
                "value": "places.displayName,places.formattedAddress,places.websiteUri,places.nationalPhoneNumber,places.id"
              }
            ]
          },
          "sendBody": true,
          "contentType": "json",
          "bodyParameters": {
            "parameters": [
              {
                "name": "textQuery",
                "value": "={{ $json.query }}"
              },
              {
                "name": "regionCode",
                "value": "GB"
              },
              {
                "name": "languageCode",
                "value": "en-GB"
              },
              {
                "name": "maxResultCount",
                "value": "=20"
              }
            ]
          },
          "options": {
            "response": {
              "response": {
                "neverError": true
              }
            },
            "timeout": 20000
          }
        },
        "id": "ddg_fetch",
        "name": "Search Google Maps",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          860,
          380
        ]
      },
      {
        "parameters": {
          "jsCode": "const item = $input.item.json;\n\n// Get county and venue_type from upstream Generate Searches node\nlet genItem = {};\ntry { genItem = $('Generate Searches').item.json; } catch(e) {}\nconst county = String(genItem.county || item.county || 'England');\nconst vtype  = String(genItem.venue_type_label || item.venue_type_label || 'Community Venue');\n\n// Google Places API returns: { places: [ { displayName, formattedAddress, websiteUri, nationalPhoneNumber, id } ] }\nconst places = Array.isArray(item.places) ? item.places : [];\n\nif (places.length === 0) {\n  console.log('No places returned for query:', genItem.query || 'unknown');\n  return [];\n}\n\n// Chains/irrelevant venues to skip\nconst SKIP = /tesco|asda|sainsbury|morrisons|costa|starbucks|mcdonalds|pizza|kfc|nando|greggs|boots|lidl|aldi|amazon/i;\n\nconst results = [];\nfor (const p of places) {\n  const name    = (p.displayName && p.displayName.text) ? p.displayName.text.trim() : '';\n  const address = p.formattedAddress || '';\n  const website = p.websiteUri || '';\n  const phone   = p.nationalPhoneNumber || '';\n  const placeId = p.id || '';\n\n  if (!name || name.length < 3) continue;\n  if (SKIP.test(name)) continue;\n\n  results.push({\n    json: {\n      venue_name:  name.substring(0, 120),\n      website_url: website,\n      phone:       phone,\n      venue_type:  vtype,\n      county:      county,\n      notes:       ('Google Maps \u00b7 ' + county + (address ? ' \u00b7 ' + address : '')).substring(0, 255),\n      place_id:    placeId,\n      source:      'google_maps'\n    }\n  });\n}\n\nconsole.log('Parsed', results.length, 'venues from Google Maps for', vtype, 'in', county);\nreturn results;"
        },
        "id": "parse_ddg",
        "name": "Parse Google Maps Results",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1080,
          380
        ]
      },
      {
        "parameters": {
          "operation": "executeQuery",
          "query": "INSERT INTO leads (venue_name, email, website_url, phone, venue_type, status, county, source, notes)\nSELECT\n  $${{ $json.venue_name }}$$::text,\n  $$$$::text,\n  $${{ $json.website_url }}$$::text,\n  $${{ $json.phone }}$$::text,\n  $${{ $json.venue_type }}$$::text,\n  $$new$$::text,\n  $${{ $json.county }}$$::text,\n  $$google_maps$$::text,\n  $${{ $json.notes }}$$::text\nWHERE NOT EXISTS (\n  SELECT 1 FROM leads WHERE website_url = $${{ $json.website_url }}$$ AND $${{ $json.website_url }}$$ <> $$$$\n)\nAND NOT EXISTS (\n  SELECT 1 FROM leads WHERE lower(venue_name) = lower($${{ $json.venue_name.substring(0,50) }}$$)\n);",
          "options": {}
        },
        "id": "insert1",
        "name": "Insert Lead",
        "type": "n8n-nodes-base.postgres",
        "typeVersion": 2.5,
        "position": [
          1300,
          380
        ],
        "credentials": {
          "postgres": {
            "id": "iEsRYyB7vjr5G7i8",
            "name": "Postgres account 3"
          }
        }
      }
    ],
    "connections": {
      "Daily at 6am": {
        "main": [
          [
            {
              "node": "Ensure DB Columns",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Manual Discovery Trigger": {
        "main": [
          [
            {
              "node": "Ensure DB Columns",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Ensure DB Columns": {
        "main": [
          [
            {
              "node": "Generate Searches",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Generate Searches": {
        "main": [
          [
            {
              "node": "Search Google Maps",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse Google Maps Results": {
        "main": [
          [
            {
              "node": "Insert Lead",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search Google Maps": {
        "main": [
          [
            {
              "node": "Parse Google Maps Results",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "andrew johnson",
    "name": null,
    "description": null,
    "autosaved": false,
    "workflowPublishHistory": [
      {
        "createdAt": "2026-03-15T20:42:04.955Z",
        "id": 1396,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "deactivated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-15T20:42:04.971Z",
        "id": 1397,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "activated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-10T17:44:36.868Z",
        "id": 919,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "deactivated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      },
      {
        "createdAt": "2026-03-10T17:44:36.882Z",
        "id": 920,
        "workflowId": "Wl4HBw8sGpfvTzta",
        "versionId": "e80c5b06-9ee0-4608-9e23-b17f6634bd9d",
        "event": "activated",
        "userId": "e2485274-7097-4eb5-8502-e39b2308096c"
      }
    ]
  }
}

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

VenueDesk — Lead Discovery (Daily). Uses postgres, httpRequest. Scheduled trigger; 7 nodes.

Source: https://github.com/AndyJay72/VenueDesk/blob/2b877fc3d0dba284a404cc64fcc153b92c612ae3/n8n-workflows/Wl4HBw8sGpfvTzta.json — original creator credit. Request a take-down →

More Marketing & Ads workflows → · Browse all categories →

Related workflows

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

Marketing & Ads

Workflow A — WhatsApp Lead Intake & Qualification. Uses postgres, httpRequest, errorTrigger. Scheduled trigger; 67 nodes.

Postgres, HTTP Request, Error Trigger
Marketing & Ads

This workflow fetches unqualified leads from Postgres at defined retry intervals, sends personalized WhatsApp template messages via Gallabox API, and logs message activity while updating lead status i

Postgres, HTTP Request
Marketing & Ads

Build authentic Reddit presence and generate qualified leads through AI-powered community engagement that provides genuine value without spam or promotion.

HTTP Request, Reddit
Marketing & Ads

This workflow automates bulk email campaigns with built-in validation, deliverability protection, and smart send-time optimization.

HTTP Request, Postgres, Gmail
Marketing & Ads

This workflow runs on scheduled weekly and monthly triggers to generate unified marketing performance reports. It processes multiple websites by collecting analytics data, paid ads performance, and CR

Gmail, Google Sheets, Google Analytics +3