AutomationFlowsSlack & Telegram › Sync Hubspot and Linear Customers Daily Using Snowflake Data

Sync Hubspot and Linear Customers Daily Using Snowflake Data

ByRomain Jouhannet @rjouhann on n8n.io

Keeps your Linear Customers list automatically in sync with HubSpot CRM data, using Snowflake as the data warehouse source.

Cron / scheduled trigger★★★★☆ complexity21 nodesHTTP RequestSlackSnowflake
Slack & Telegram Trigger: Cron / scheduled Nodes: 21 Complexity: ★★★★☆ Added:
Sync Hubspot and Linear Customers Daily Using Snowflake Data — n8n workflow card showing HTTP Request, Slack, Snowflake integration

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

This workflow follows the HTTP Request → Slack 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
{
  "id": "HkG9aYueINUTHZ05",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "HubSpot \u2192 Linear Customers Sync (via Snowflake)",
  "tags": [
    {
      "id": "17",
      "name": "snowflake",
      "createdAt": "2023-09-18T17:05:02.756Z",
      "updatedAt": "2023-09-18T17:05:02.756Z"
    },
    {
      "id": "6Ek7V8f4xbM9vWLj",
      "name": "linear",
      "createdAt": "2024-11-08T12:12:15.330Z",
      "updatedAt": "2024-11-08T12:12:15.330Z"
    }
  ],
  "nodes": [
    {
      "id": "522c2916-0b5c-4445-992a-aa50d34365c4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2096,
        1184
      ],
      "parameters": {
        "width": 480,
        "height": 848,
        "content": "## HubSpot \u2192 Linear Customers Sync (via Snowflake)\n\n### How it works\n\n1. Scheduled trigger initiates data retrieval from HubSpot via Snowflake.\n2. Data is processed and paginated results are fetched.\n3. Final output is formatted and customers are matched.\n4. Based on the match, the workflow routes to either create or update customers in Linear.\n5. Slack update is sent with the results.\n\n### Setup steps\n\n- [ ] Configure Snowflake credentials for data retrieval.\n- [ ] Set up Linear API credentials for customer management.\n- [ ] Connect Slack account for updates.\n- [ ] Schedule the trigger according to desired frequency.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "6c668fcf-392b-44e5-82c1-c8c99eb931c4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1536,
        1280
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "## Trigger and initial data fetch\n\nInitial trigger and data retrieval from HubSpot via Snowflake."
      },
      "typeVersion": 1
    },
    {
      "id": "f4f8a7ac-10ea-48fc-a447-9f49623a68a9",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        1200
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 352,
        "content": "## Pagination setup and iteration\n\nSet up pagination and iterate through pages of customer data."
      },
      "typeVersion": 1
    },
    {
      "id": "7b07d5a6-584e-4f32-a51d-579c4338aaf1",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        1328
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 336,
        "content": "## Data processing and formatting\n\nProcess fetched data and format the final output."
      },
      "typeVersion": 1
    },
    {
      "id": "f6c6d0e6-6345-4392-8676-5ab0f314de0b",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        544,
        1376
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Customer matching and routing\n\nMatch customers for actions and route to create or update functions."
      },
      "typeVersion": 1
    },
    {
      "id": "769cecd7-923f-4091-8c9d-de3faa8b6f71",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        992,
        1184
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 592,
        "content": "## Customer management in Linear\n\nCreate or update customers in Linear based on the routing decision."
      },
      "typeVersion": 1
    },
    {
      "id": "d99ddc31-d233-4bc8-9527-107ce1e11a1d",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        1712
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 320,
        "content": "## Notify via Slack\n\nSend a Slack update with the result of the customer management process."
      },
      "typeVersion": 1
    },
    {
      "id": "a53566ce-2e8f-4041-9f48-f1ca1ef6c843",
      "name": "Post Update to Linear",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1040,
        1616
      ],
      "parameters": {
        "url": "https://api.linear.app/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"mutation CustomerUpdate($id: String!, $input: CustomerUpdateInput!) { customerUpdate(id: $id, input: $input) { success customer { id name revenue size status { id name } } } }\",\n  \"variables\": {\n    \"id\": \"{{ $json.linearId }}\",\n    \"input\": {\n      \"revenue\": {{ $json.snowflakeData.arr }},\n      \"size\": {{ $json.snowflakeData.seats }}\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linearApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "99aed38a-fd51-4909-8c35-55e8bf13e9c0",
      "name": "Post Create to Linear",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1040,
        1376
      ],
      "parameters": {
        "url": "https://api.linear.app/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"mutation CustomerCreate($input: CustomerCreateInput!) { customerCreate(input: $input) { success customer { id name domains externalIds revenue size status { id name } } } }\",\n  \"variables\": {\n    \"input\": {\n      \"name\": \"{{ $json.snowflakeData.name }}\",\n      \"domains\": [\"{{ $json.snowflakeData.domain }}\"],\n      \"externalIds\": [\"{{ $json.snowflakeData.companyId }}\"],\n      \"revenue\": {{ $json.snowflakeData.arr }},\n      \"size\": {{ $json.snowflakeData.seats }}\n    }\n  }\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linearApi"
      },
      "typeVersion": 4.2
    },
    {
      "id": "2ba32a25-8b4d-4e17-929d-a06a9a9456ef",
      "name": "Route by Customer Action",
      "type": "n8n-nodes-base.switch",
      "position": [
        816,
        1504
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "create",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "is-create",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "create"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "update",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "is-update",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "update"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "a5dad935-c564-41c6-8e24-42ecaafdaf6b",
      "name": "Format Output Data",
      "type": "n8n-nodes-base.code",
      "position": [
        320,
        1504
      ],
      "parameters": {
        "jsCode": "// Format final output to match expected structure\nconst data = $input.all()[0].json;\nconst allCustomers = data.allCustomers;\n\nconsole.log(`\u2705 Pagination complete: ${allCustomers.length} total customers`);\n\nreturn [{\n  json: {\n    data: {\n      customers: {\n        nodes: allCustomers\n      }\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c3b9602a-dc66-4c39-a30a-6242e76308fb",
      "name": "Set Next Page Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        96,
        1312
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cursor-next",
              "name": "cursor",
              "type": "string",
              "value": "={{ $json.cursor }}"
            },
            {
              "id": "page-next",
              "name": "pageNumber",
              "type": "number",
              "value": "={{ $json.pageNumber }}"
            },
            {
              "id": "all-customers-next",
              "name": "allCustomers",
              "type": "array",
              "value": "={{ $json.allCustomers }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "91fe3a62-f8cb-4307-9070-84f5da8a8bf9",
      "name": "If More Pages Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        -144,
        1312
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-next",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.hasNextPage }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e67f6802-7ac9-41ac-bf83-ee37c80a338c",
      "name": "Process Customer Page",
      "type": "n8n-nodes-base.code",
      "position": [
        -368,
        1312
      ],
      "parameters": {
        "jsCode": "// Process page results and prepare for next iteration\nconst response = $input.all()[0].json;\nconst customers = response.data.customers.nodes;\nconst pageInfo = response.data.customers.pageInfo;\n\n// Get accumulated data \u2014 use Prepare Next Iteration if available (pages 2+),\n// otherwise fall back to Initialize Pagination (first page)\nlet previousData;\ntry {\n  previousData = $('Set Next Page Parameters').all()[0].json;\n} catch (e) {\n  previousData = $('Set Initial Pagination').all()[0].json;\n}\n\nconst allCustomers = [...(previousData.allCustomers || [])];\nconst pageNumber = (previousData.pageNumber || 0) + 1;\n\n// Add new customers to collection\nallCustomers.push(...customers);\n\nconsole.log(`\ud83d\udcc4 Page ${pageNumber}: Fetched ${customers.length} customers (Total: ${allCustomers.length})`);\n\nreturn [{\n  json: {\n    cursor: pageInfo.endCursor,\n    hasNextPage: pageInfo.hasNextPage,\n    pageNumber: pageNumber,\n    allCustomers: allCustomers,\n    currentPageCustomers: customers\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f4c8955b-57fe-4473-b2e2-a225a0e1a453",
      "name": "Fetch Customer Data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -592,
        1392
      ],
      "parameters": {
        "url": "https://api.linear.app/graphql",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"query\": \"query GetCustomers($after: String) { customers(first: 250, after: $after) { nodes { id name domains externalIds revenue size tier { id name } status { id name } owner { id name email } logoUrl createdAt updatedAt } pageInfo { hasNextPage endCursor } } }\",\n  \"variables\": {{ $json.cursor ? '{\"after\": \"' + $json.cursor + '\"}' : '{}' }}\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "linearApi"
      },
      "credentials": {
        "linearApi": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.2
    },
    {
      "id": "de50b0ee-62be-41bd-bda5-356b3741a408",
      "name": "Set Initial Pagination",
      "type": "n8n-nodes-base.set",
      "position": [
        -816,
        1392
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cursor-init",
              "name": "cursor",
              "type": "string",
              "value": ""
            },
            {
              "id": "page-init",
              "name": "pageNumber",
              "type": "number",
              "value": "=0"
            },
            {
              "id": "all-customers-init",
              "name": "allCustomers",
              "type": "array",
              "value": "=[]"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "da68e303-94ba-407d-9d8d-0bf5f06b04cc",
      "name": "Limit HubSpot Records",
      "type": "n8n-nodes-base.limit",
      "disabled": true,
      "position": [
        -1040,
        1392
      ],
      "parameters": {
        "maxItems": 10
      },
      "typeVersion": 1
    },
    {
      "id": "e903a180-efdf-440b-b37c-9b63e94efe90",
      "name": "When Scheduled at 2 PM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1488,
        1392
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 14
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "8e56c48c-f9b0-4009-a96d-952a469162ff",
      "name": "Match and Update Customers",
      "type": "n8n-nodes-base.code",
      "position": [
        592,
        1504
      ],
      "parameters": {
        "jsCode": "// Customer matching: create and update only\n\n// Get Snowflake customers\nconst snowflakeCustomers = $('Limit HubSpot Records').all().map(item => ({\n  companyId: item.json.COMPANY_ID.toString(),\n  name: item.json.COMPANY_NAME,\n  domain: item.json.DOMAIN,\n  arr: Math.round(item.json.ARR || 0),\n  seats: item.json.SEATS || 0\n}));\n\n// Get Linear customers\nconst linearData = $('Format Output Data').all()[0].json.data.customers.nodes;\n\n// Build lookup maps\n// 1. Index ALL externalIds (not just [0]) \u2014 handles multi-ID customers\n//    and cases where Snowflake ID matches a non-primary externalId\nconst linearByExternalId = new Map();\nlinearData.forEach(c => {\n  (c.externalIds || []).forEach(id => {\n    if (!linearByExternalId.has(id)) linearByExternalId.set(id, c);\n  });\n});\n\n// 2. Domain-based fallback \u2014 handles cases where HubSpot re-keyed\n//    the company (new COMPANY_ID) but the domain is unchanged\nconst linearByDomain = new Map();\nlinearData.forEach(c => {\n  (c.domains || []).forEach(d => {\n    if (d && !linearByDomain.has(d)) linearByDomain.set(d, c);\n  });\n});\n\n// Resolve a Snowflake customer to its Linear counterpart\nfunction findLinearCustomer(sfCustomer) {\n  return linearByExternalId.get(sfCustomer.companyId)\n    || linearByDomain.get(sfCustomer.domain)\n    || null;\n}\n\nconst toCreate = [];\nconst toUpdate = [];\nconst matchLog = [];\n\nsnowflakeCustomers.forEach(sfCustomer => {\n  const linearCustomer = findLinearCustomer(sfCustomer);\n\n  if (!linearCustomer) {\n    toCreate.push({ action: 'create', snowflakeData: sfCustomer });\n    return;\n  }\n\n  const matchedVia = linearByExternalId.has(sfCustomer.companyId) ? 'externalId' : 'domain';\n  matchLog.push(`  \u2713 [${matchedVia}] ${sfCustomer.name}`);\n\n  const revenueChanged = linearCustomer.revenue !== sfCustomer.arr;\n  const sizeChanged = linearCustomer.size !== sfCustomer.seats;\n\n  if (revenueChanged || sizeChanged) {\n    toUpdate.push({\n      action: 'update',\n      linearId: linearCustomer.id,\n      linearData: linearCustomer,\n      snowflakeData: sfCustomer,\n      changes: { revenue: revenueChanged, size: sizeChanged }\n    });\n  }\n});\n\nconsole.log('\\n\ud83d\udcc8 Action Summary:');\nconsole.log('  \ud83c\udd95 Create: ' + toCreate.length);\nconsole.log('  \u270f\ufe0f Update: ' + toUpdate.length);\nconsole.log('  \ud83d\udcca Total: ' + (toCreate.length + toUpdate.length));\nif (matchLog.length) {\n  console.log('\\n\ud83d\udd17 Domain fallback matches:');\n  matchLog.filter(l => l.includes('domain')).forEach(l => console.log(l));\n}\n\nreturn [\n  ...toCreate.map(item => ({ json: item })),\n  ...toUpdate.map(item => ({ json: item }))\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "98ecdb91-fdbe-46b0-a6d9-57e9afe57dff",
      "name": "Post Slack Notification",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        752,
        1872
      ],
      "parameters": {
        "text": "=Linear Customer Sync \u2014 Run Summary",
        "select": "channel",
        "blocksUi": "={\n  \"blocks\": [\n    {\n      \"type\": \"header\",\n      \"text\": { \"type\": \"plain_text\", \"text\": \"\ud83d\udd04 Linear Customer Sync\", \"emoji\": true }\n    },\n    {\n      \"type\": \"section\",\n      \"fields\": [\n        { \"type\": \"mrkdwn\", \"text\": \"*{{ $json.action.toTitleCase() }}*: `{{ $json.snowflakeData.name }}` (`${{ $json.snowflakeData.arr }}`, `{{ $json.snowflakeData.seats }} seats`)\" }\n      ]\n    }\n  ]\n}",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#your-slack-channel"
        },
        "messageType": "block",
        "otherOptions": {}
      },
      "executeOnce": false,
      "retryOnFail": false,
      "typeVersion": 2.1
    },
    {
      "id": "cb703602-d451-4ffd-8168-e968fb2f0a79",
      "name": "Retrieve HubSpot Data from Snowflake",
      "type": "n8n-nodes-base.snowflake",
      "position": [
        -1264,
        1392
      ],
      "parameters": {
        "query": "SELECT\n  comp.COMPANY_ID,\n  comp.NAME AS COMPANY_NAME,\n  comp.DOMAIN,\n  SUM(d.AMOUNT) AS ARR,\n  MAX(d.NUMBER_OF_SEATS) AS SEATS\nFROM\n  \"HUBSPOT\".\"COMPANIES\" comp\nJOIN\n  \"HUBSPOT\".\"ASSOCIATIONS_DEALS_TO_COMPANIES\" assoc\n  ON comp.COMPANY_ID = assoc.COMPANY_ID\nJOIN\n  \"HUBSPOT\".\"DEALS\" d\n  ON assoc.DEAL_ID = d.DEAL_ID\nWHERE\n  d.DEALSTAGE = 'closedwon'\nGROUP BY\n  comp.COMPANY_ID,\n  comp.NAME,\n  comp.DOMAIN\nORDER BY\n  ARR DESC",
        "operation": "executeQuery"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "WRas2GJ7w02TmUYO",
    "timeSavedMode": "fixed",
    "availableInMCP": false,
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveExecutionProgress": true,
    "timeSavedPerExecution": 60,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "versionId": "371569d3-b69a-4bef-a213-e5d2fb9d110d",
  "connections": {
    "Format Output Data": {
      "main": [
        [
          {
            "node": "Match and Update Customers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Customer Data": {
      "main": [
        [
          {
            "node": "Process Customer Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If More Pages Exist": {
      "main": [
        [
          {
            "node": "Set Next Page Parameters",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format Output Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Limit HubSpot Records": {
      "main": [
        [
          {
            "node": "Set Initial Pagination",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Customer Page": {
      "main": [
        [
          {
            "node": "If More Pages Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Initial Pagination": {
      "main": [
        [
          {
            "node": "Fetch Customer Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Scheduled at 2 PM": {
      "main": [
        [
          {
            "node": "Retrieve HubSpot Data from Snowflake",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Customer Action": {
      "main": [
        [
          {
            "node": "Post Create to Linear",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Post Update to Linear",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Next Page Parameters": {
      "main": [
        [
          {
            "node": "Fetch Customer Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match and Update Customers": {
      "main": [
        [
          {
            "node": "Route by Customer Action",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post Slack Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve HubSpot Data from Snowflake": {
      "main": [
        [
          {
            "node": "Limit HubSpot Records",
            "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

Keeps your Linear Customers list automatically in sync with HubSpot CRM data, using Snowflake as the data warehouse source.

Source: https://n8n.io/workflows/14518/ — 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

Import Productboard Notes Companies And Features Into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.

HTTP Request, Snowflake, Slack
Slack & Telegram

Import Productboard Notes, Companies and Features into Snowflake. Uses stickyNote, httpRequest, splitOut, snowflake. Scheduled trigger; 35 nodes.

HTTP Request, Snowflake, Slack
Slack & Telegram

This workflow imports Productboard data into Snowflake, automating data extraction, mapping, and updates for features, companies, and notes. It supports scheduled weekly updates, data cleansing, and S

HTTP Request, Snowflake, 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
Slack & Telegram

This workflow streamlines the entire inventory replenishment process by leveraging AI for demand forecasting and intelligent logic for supplier selection. It aggregates data from multiple sources—POS

HTTP Request, MySQL, Slack