{
  "name": "Get a Slack alert for new negative app reviews (Google Play and App Store)",
  "nodes": [
    {
      "parameters": {
        "content": "## Get a Slack alert for new negative app reviews (Google Play and App Store)\n\n### Who's it for\nMobile product managers, app developers, support and ASO teams who want to see every new 1-3 star review of their apps on both stores in one place, and answer before the rating drops.\n\n### How it works\n1. Every morning the Schedule Trigger starts the workflow.\n2. The **Settings** node holds your apps (Google Play package names, App Store IDs or store links), the country and the highest star rating to alert on.\n3. The **App Reviews Scraper** Actor on Apify (`plainrows/app-reviews-scraper`) reads the newest reviews with **Only new reviews** on: it remembers the reviews it already delivered, so each run returns only reviews posted since the last one.\n4. A Code node keeps the new reviews at or under your rating, groups them by app and store and builds one message with stars, text, app version, date and a link to the app page.\n5. The message is posted to Slack. No new low rating, no message.\n\n### Requirements\n- An Apify account (free plan available) and its API token.\n- A Slack workspace and the Slack credential in n8n.\n- The Apify node is the verified community node `@apify/n8n-nodes-apify` (available on n8n Cloud; on self-hosted n8n install it from Settings > Community nodes).\n\n### How to customize\n- Edit `apps` (one per line), `country`, `maxStars` and `monitorName` in **Settings**. Use a new `monitorName` to start a fresh history.\n- Set `maxStars` to 5 to get every new review; duplicate the Actor node to watch several countries.\n- Replace Slack with Gmail, Microsoft Teams, Jira or Google Sheets using the same fields.\n\nReviewer names are not collected. Each check pays a small fee per block of reviews requested (1 block per Google Play app, 1 per 25 App Store reviews) plus a price per new review.",
        "height": 760,
        "width": 520
      },
      "id": "c44b687f-53fb-5ea6-a22d-0a11e91d775a",
      "name": "Description",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -660,
        -340
      ]
    },
    {
      "parameters": {
        "content": "### Setup\n1. Put your apps in `apps` (one per line: Google Play package name, App Store ID or store link), set `country` and `maxStars` in **Settings**.\n2. Add your Apify credential (free account at apify.com) to *Get new reviews*.\n3. Connect Slack and pick the channel in *Post to Slack*.\n4. Run once by hand: the first run seeds the memory with the newest reviews.\n\n**Cost:** a small fee per block of reviews requested plus a price per new review (see the Actor's Pricing tab); the node caps each run with *Maximum Cost per Run*.",
        "height": 340,
        "width": 420,
        "color": 7
      },
      "id": "0908f398-845d-563e-b679-43950c5789c8",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        440,
        -340
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "e2596245-cb74-532e-b101-d8debc94d7ff",
      "name": "Every day 8:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ebd3fa0e-6fdd-5d8e-b330-899412c95739",
              "name": "apps",
              "value": "com.spotify.music\nhttps://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
              "type": "string"
            },
            {
              "id": "b3e18147-680b-519f-8b35-165889bd658d",
              "name": "country",
              "value": "US",
              "type": "string"
            },
            {
              "id": "313652c5-c65d-5d0c-a500-649d341dc0c9",
              "name": "maxStars",
              "value": 3,
              "type": "number"
            },
            {
              "id": "4a7ef553-01db-5c8d-9775-457cf9e549a0",
              "name": "monitorName",
              "value": "n8n-new-app-reviews",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "877af9bb-2f73-5380-a0d8-3dec9ee6f1a0",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        120
      ]
    },
    {
      "parameters": {
        "resource": "Actors",
        "operation": "Run actor and get dataset",
        "actorId": "plainrows~app-reviews-scraper",
        "customBody": "={{ JSON.stringify({ apps: $json.apps.split('\\n').map((a) => a.trim()).filter(Boolean), country: $json.country, maxReviewsPerApp: 25, sortBy: 'newest', onlyNewReviews: true, monitorName: $json.monitorName }) }}",
        "maxTotalChargeUsd": 1
      },
      "id": "13ccb8ef-f529-5c48-86ea-6cb4af90bca7",
      "name": "Get new reviews",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        440,
        120
      ],
      "credentials": {
        "apifyApi": {
          "id": "",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// One Slack message listing new reviews at or under maxStars, grouped by app and store. Nothing to report: no message.\nconst max = Number($('Settings').first().json.maxStars ?? 3);\nconst reviews = $input.all().map((i) => i.json).filter((r) => r.found !== false && r.rating !== null && r.rating <= max);\nif (reviews.length === 0) return [];\nconst storeName = { google_play: 'Google Play', app_store: 'App Store' };\nconst byApp = {};\nfor (const r of reviews) (byApp[`${r.appTitle ?? r.appId} (${storeName[r.store] ?? r.store}, ${r.searchCountry})`] ??= []).push(r);\nconst stars = (n) => '★'.repeat(n) + '☆'.repeat(5 - n);\nconst blocks = Object.entries(byApp).map(([app, list]) => {\n  const lines = list.slice(0, 10).map((r) => `• ${stars(r.rating)} ${r.title ? `*${r.title}* ` : ''}${(r.text ?? '').slice(0, 200)} (${(r.reviewedAt ?? '').slice(0, 10)}${r.appVersion ? `, v${r.appVersion}` : ''})`);\n  if (list.length > 10) lines.push(`…and ${list.length - 10} more`);\n  const url = list[0].appUrl ? ` <${list[0].appUrl}|open>` : '';\n  return `*${app}*${url}: ${list.length} new review${list.length > 1 ? 's' : ''} at ${max} stars or less\\n${lines.join('\\n')}`;\n});\nreturn [{ json: { text: `:iphone: *New app reviews to answer*\\n\\n${blocks.join('\\n\\n')}`, newReviews: reviews.length } }];"
      },
      "id": "9a8a1659-8e72-51a3-ba59-6f995541bb51",
      "name": "Build alert",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        120
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "",
          "mode": "list"
        },
        "text": "={{ $json.text }}",
        "otherOptions": {}
      },
      "id": "e15e706a-8944-5f83-a630-2ae9430704ad",
      "name": "Post to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        880,
        120
      ],
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack account"
        }
      }
    }
  ],
  "connections": {
    "Every day 8:00": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Get new reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get new reviews": {
      "main": [
        [
          {
            "node": "Build alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build alert": {
      "main": [
        [
          {
            "node": "Post to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
