{
  "name": "Send new Spitogatos.gr property listings with agent phones to Google Sheets and Telegram with Apify",
  "nodes": [
    {
      "parameters": {
        "content": "## Send new Spitogatos.gr property listings with agent phones to Google Sheets and Telegram\n\nEvery morning this workflow checks Spitogatos.gr, Greece's largest property portal, for listings published in the last 24 hours in the areas you pick. Each new listing is saved to Google Sheets with its price per m², floor, year built and the agent's name and phone number, and a summary is sent to Telegram.\n\nΝέες αγγελίες ακινήτων από το Spitogatos.gr με τηλέφωνο μεσίτη, κάθε πρωί στο Telegram.\n\n### Who it's for\nProperty investors, buyers relocating to Greece, and agents who watch a neighbourhood.\n\n### How it works\n1. **Schedule** runs the workflow every morning at 8:00.\n2. **Set your search** holds your areas, budget, size and deal settings.\n3. **Apify** runs the [Spitogatos.gr Scraper](https://apify.com/fayoussef/spitogatos-scraper?fpr=youssef) for listings first posted in the last 24 hours.\n4. **Remove Duplicates** drops listings you already received.\n5. Every new listing is saved to your Google Sheet.\n6. Matching listings go to Telegram in one message.\n\n### How to set up\n1. Create a free [Apify account](https://apify.com/?fpr=youssef) and connect it in the **Apify** node (OAuth or API token).\n2. Create an empty Google Sheet and paste its URL in **Set your search**.\n3. Create a Telegram bot with @BotFather, connect it, and paste your chat id in **Set your search**.\n4. Edit the areas and filters, run once, then activate.\n\n### Requirements\n- Apify account (the free plan works)\n- Google Sheets and a Telegram bot\n\n### How to customize\n- Set `maxPricePerM2` (for example 2500) to alert only on listings under that price per m².\n- Use `rent` in `listingType` to track rentals.\n- Add more areas, in English or Greek, separated by commas.",
        "height": 1100,
        "width": 520,
        "color": 1
      },
      "id": "5b170a01-0000-4000-8000-000000000001",
      "name": "Sticky Note - About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 1. Set your search\n`locations`: area names in English or Greek, separated by commas (for example `Kolonaki, Pangrati`).\n`listingType`: `sale` or `rent`.\n`propertyType`: `apartment`, `studio`, `maisonette`, `detached`, `villa` or empty for all homes.\n`maxPricePerM2`: 0 sends every new listing. Set a number to get only listings under it, plus price cuts when `alertOnPriceCuts` is true.",
        "height": 640,
        "width": 520,
        "color": 7
      },
      "id": "5b170a01-0000-4000-8000-000000000002",
      "name": "Sticky Note - Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 2. Find new listings\nRuns the [Spitogatos.gr Scraper](https://apify.com/fayoussef/spitogatos-scraper?fpr=youssef) on your Apify account for listings first posted within `postedWithin`. `maxPages` keeps each run small.\n\n**Skip listings already sent** remembers every listing id across runs, so you never see the same home twice.\n\nUsage is billed per listing on your Apify account.",
        "height": 640,
        "width": 520,
        "color": 7
      },
      "id": "5b170a01-0000-4000-8000-000000000003",
      "name": "Sticky Note - Step 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        360,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 3. Save and alert\nEvery new listing becomes one row in your Google Sheet with the agent's phone number (headers are created on the first run).\n\nMatching listings reach Telegram in one message with up to 20 homes. The rest are in the sheet.",
        "height": 640,
        "width": 920,
        "color": 7
      },
      "id": "5b170a01-0000-4000-8000-000000000004",
      "name": "Sticky Note - Step 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        920,
        -240
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "5b170a01-0000-4000-8000-000000000010",
      "name": "Every morning at 8",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -120,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "5b170a01f-0000-4000-8000-000000000001",
              "name": "locations",
              "value": "Glyfada, Voula",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000002",
              "name": "listingType",
              "value": "sale",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000003",
              "name": "propertyType",
              "value": "apartment",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000004",
              "name": "priceMax",
              "value": 350000,
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000005",
              "name": "areaMin",
              "value": 50,
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000006",
              "name": "roomsMin",
              "value": 2,
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000007",
              "name": "postedWithin",
              "value": "24hours",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000008",
              "name": "maxPages",
              "value": 3,
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000009",
              "name": "maxPricePerM2",
              "value": 0,
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000010",
              "name": "alertOnPriceCuts",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000011",
              "name": "googleSheetUrl",
              "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000012",
              "name": "telegramChatId",
              "value": "PASTE_YOUR_TELEGRAM_CHAT_ID_HERE",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "5b170a01-0000-4000-8000-000000000011",
      "name": "Set your search",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        120,
        120
      ]
    },
    {
      "parameters": {
        "operation": "Run actor and get dataset",
        "actorSource": "store",
        "actorId": {
          "__rl": true,
          "value": "aAohRNwnunh3U1L6d",
          "mode": "id"
        },
        "customBody": "={{ JSON.stringify(Object.fromEntries(Object.entries({\n  start_urls: [],\n  locations: $json.locations.split(',').map(s => s.trim()).filter(Boolean),\n  listing_type: $json.listingType || 'sale',\n  category: 'residential',\n  property_types: $json.propertyType.trim() || null,\n  price_max: $json.priceMax || null,\n  area_min: $json.areaMin || null,\n  rooms_min: $json.roomsMin || null,\n  posted_within: $json.postedWithin || '24hours',\n  max_depth: $json.maxPages || 3\n}).filter(([k, v]) => v !== null))) }}"
      },
      "id": "5b170a01-0000-4000-8000-000000000012",
      "name": "Find new listings on Spitogatos.gr",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        460,
        120
      ]
    },
    {
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.id }}",
        "options": {}
      },
      "id": "5b170a01-0000-4000-8000-000000000013",
      "name": "Skip listings already sent",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        720,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "5b170a01f-0000-4000-8000-000000000001",
              "name": "Price (EUR)",
              "value": "={{ Number($json.price) }}",
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000002",
              "name": "Area (m2)",
              "value": "={{ Number($json.sq_meters) }}",
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000003",
              "name": "EUR per m2",
              "value": "={{ Math.round(Number($json.pricePerSqMeters)) }}",
              "type": "number"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000004",
              "name": "Rooms",
              "value": "={{ $json.rooms ?? '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000005",
              "name": "Floor",
              "value": "={{ (f => f === '' ? '' : (isNaN(f) ? f : 'Floor ' + f))(String($json['Floor Level'] ?? $json.floorNumber ?? '')) }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000006",
              "name": "Built",
              "value": "={{ $json.year_of_construction || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000007",
              "name": "Neighbourhood",
              "value": "={{ $json.geographiesByLevel_4_fullName || $json.geographiesByLevel_2_fullName || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000008",
              "name": "Region",
              "value": "={{ $json.geographiesByLevel_2_fullName || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000009",
              "name": "Price cut (%)",
              "value": "={{ String($json.priceReduced) === 'True' || $json.priceReduced === true ? Number($json.priceChangePercentage) || '' : '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000010",
              "name": "Agency",
              "value": "={{ $json.agency_name || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000011",
              "name": "Contact",
              "value": "={{ $json.contact_name || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000012",
              "name": "Phone",
              "value": "={{ $json.contact_telephone || $json.agency_telephone || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000013",
              "name": "First listed",
              "value": "={{ $json.firstPublishDate || '' }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000014",
              "name": "Photo",
              "value": "={{ Array.isArray($json.images) ? ($json.images[0] || '') : ((String($json.images || '').match(/https?:[^'\",\\]\\s]+/) || [''])[0]) }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000015",
              "name": "Found on",
              "value": "={{ $now.toISODate() }}",
              "type": "string"
            },
            {
              "id": "5b170a01f-0000-4000-8000-000000000016",
              "name": "Link",
              "value": "={{ $json.url }}",
              "type": "string"
            }
          ]
        },
        "options": {
          "ignoreConversionErrors": true
        }
      },
      "id": "5b170a01-0000-4000-8000-000000000014",
      "name": "Format property row",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        980,
        120
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set your search').first().json.googleSheetUrl }}",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "5b170a01-0000-4000-8000-000000000015",
      "name": "Save every listing to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1240,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "5b170a01c-0000-4000-8000-000000000001",
              "leftValue": "={{ (() => { const s = $('Set your search').first().json; const cap = Number(s.maxPricePerM2) || 0; if (!cap) return true; return $json['EUR per m2'] <= cap || (s.alertOnPriceCuts && $json['Price cut (%)'] !== ''); })() }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "5b170a01-0000-4000-8000-000000000016",
      "name": "Keep matching deals",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1240,
        240
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "items",
        "options": {}
      },
      "id": "5b170a01-0000-4000-8000-000000000017",
      "name": "Combine into one message",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1460,
        240
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Set your search').first().json.telegramChatId }}",
        "text": "={{ (() => { const esc = s => String(s ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');\nconst homes = $json.items;\nconst lines = homes.slice(0, 20).map(h => {\n  const cut = h['Price cut (%)'] !== '' ? ' · ↓' + h['Price cut (%)'] + '%' : '';\n  return '🏠 <b><a href=\"' + h.Link + '\">€' + Number(h['Price (EUR)']).toLocaleString('en-US') + ' · ' + h['Area (m2)'] + ' m²</a></b> (€'\n    + Number(h['EUR per m2']).toLocaleString('en-US') + '/m²)' + cut + '\\n'\n    + esc([h.Rooms ? h.Rooms + ' rooms' : '', h.Floor, h.Built ? 'built ' + h.Built : ''].filter(Boolean).join(' · ')) + '\\n'\n    + esc(h.Neighbourhood) + '\\n'\n    + esc(h.Agency || h.Contact) + (h.Phone ? ' · ☎ ' + esc(h.Phone) : '');\n});\nreturn '<b>' + homes.length + ' new Spitogatos listing' + (homes.length === 1 ? '' : 's') + '</b>\\n\\n' + lines.join('\\n\\n')\n  + (homes.length > 20 ? '\\n\\n+ ' + (homes.length - 20) + ' more in your Google Sheet' : '');\n})() }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "id": "5b170a01-0000-4000-8000-000000000018",
      "name": "Send listings to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1660,
        240
      ],
      "webhookId": "5b170a01w-0000-4000-8000-000000000001"
    }
  ],
  "connections": {
    "Every morning at 8": {
      "main": [
        [
          {
            "node": "Set your search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set your search": {
      "main": [
        [
          {
            "node": "Find new listings on Spitogatos.gr",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find new listings on Spitogatos.gr": {
      "main": [
        [
          {
            "node": "Skip listings already sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip listings already sent": {
      "main": [
        [
          {
            "node": "Format property row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format property row": {
      "main": [
        [
          {
            "node": "Save every listing to Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Keep matching deals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep matching deals": {
      "main": [
        [
          {
            "node": "Combine into one message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine into one message": {
      "main": [
        [
          {
            "node": "Send listings to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}