DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Supply Chain & Procurement Tech SaaS: 5 Automations That Scale Platform Ops and Meet Compliance (Free Workflow JSON)

If you're building TMS, WMS, procurement software, or supplier management SaaS — you're sitting on one of the most integration-heavy stacks in enterprise software.

Your platform talks to ERPs, carrier APIs, customs brokers, payment rails, and a dozen supplier portals. Every new customer means a new integration project. Every contract renewal is a manual chase. Every carrier outage is a support spike.

n8n is the automation layer that keeps platform ops running without headcount. Self-hosted, git-versioned, 400+ native integrations — and your customers' supplier pricing, spend data, and logistics contracts never touch a third-party cloud.

Here are 5 automations supply chain and procurement SaaS companies use to scale platform ops and maintain compliance.


1. New Customer Onboarding Drip

Manual onboarding sequences leak customers. A structured 3-touch drip — triggered automatically when a new account appears in your CRM — closes the gap between sign-up and first value.

{
  "nodes": [
    {"id":"1","name":"New Account Trigger","type":"n8n-nodes-base.googleSheetsTrigger","parameters":{"sheetId":"YOUR_SHEET_ID","range":"Accounts!A:F","event":"rowAdded"},"position":[250,300]},
    {"id":"2","name":"Day 0 — Welcome + API Credentials","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{$json.contact_email}}","subject":"Welcome to [Platform] — your API credentials","message":"Hi {{$json.contact_name}},\n\nYour API key: {{$json.api_key}}\nDashboard: https://app.yourplatform.com\n\nFirst steps: connect your ERP and run your first test shipment. Takes ~20 minutes.\n\nQuestions? Reply here or ping us in Slack.\n\n— The Team"},"position":[450,200]},
    {"id":"3","name":"Notify CSM","type":"n8n-nodes-base.slack","parameters":{"channel":"#customer-success","text":"New account: {{$json.company_name}} ({{$json.contact_email}}) — CSM: {{$json.csm_owner}}. Day 0 welcome sent. CRM: {{$json.crm_link}}"},"position":[450,400]},
    {"id":"4","name":"Wait 3 Days","type":"n8n-nodes-base.wait","parameters":{"amount":3,"unit":"days"},"position":[650,300]},
    {"id":"5","name":"Day 3 — Integration Tips","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{$json.contact_email}}","subject":"Day 3 check-in: how's the ERP connection going?","message":"Hi {{$json.contact_name}},\n\n3 most common integration wins our customers get in week 1:\n\n1. Automated PO sync from ERP → platform (saves 4h/week)\n2. Real-time shipment status to your procurement dashboard\n3. Supplier invoice reconciliation against POs\n\nDocs: https://docs.yourplatform.com/quickstart\n\nStuck? Book a 20-min setup call: https://cal.yourplatform.com"},"position":[850,200]},
    {"id":"6","name":"Wait 4 Days","type":"n8n-nodes-base.wait","parameters":{"amount":4,"unit":"days"},"position":[1050,300]},
    {"id":"7","name":"Day 7 — Success Stories","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{$json.contact_email}}","subject":"Week 1 done — here's what customers achieve by month 1","message":"Hi {{$json.contact_name}},\n\nWhat customers typically achieve in month 1:\n\n- 60% reduction in manual PO entry\n- 3-day faster invoice approval cycle\n- Real-time supplier compliance monitoring\n\nIf you haven't run your first end-to-end test yet, reply and we'll pair you with an implementation engineer.\n\n— The Team"},"position":[1250,200]},
    {"id":"8","name":"Mark Onboarding Complete","type":"n8n-nodes-base.googleSheets","parameters":{"operation":"update","sheetId":"YOUR_SHEET_ID","range":"Accounts!G:G","values":[["onboarding_complete"]],"row":"={{$json.row_number}}"},"position":[1450,300]}
  ],
  "connections":{"New Account Trigger":{"main":[[{"node":"Day 0 — Welcome + API Credentials","type":"main","index":0},{"node":"Notify CSM","type":"main","index":0}]]},"Day 0 — Welcome + API Credentials":{"main":[[{"node":"Wait 3 Days","type":"main","index":0}]]},"Notify CSM":{"main":[[]]},"Wait 3 Days":{"main":[[{"node":"Day 3 — Integration Tips","type":"main","index":0}]]},"Day 3 — Integration Tips":{"main":[[{"node":"Wait 4 Days","type":"main","index":0}]]},"Wait 4 Days":{"main":[[{"node":"Day 7 — Success Stories","type":"main","index":0}]]},"Day 7 — Success Stories":{"main":[[{"node":"Mark Onboarding Complete","type":"main","index":0}]]}}
}
Enter fullscreen mode Exit fullscreen mode

What it does: SheetsTrigger fires when a new account row appears → Day 0 email with API credentials + Slack DM to CSM → 3-day wait → Day 3 integration tips → 4-day wait → Day 7 success stories → marks onboarding complete in Sheets.

Customize it: swap Gmail for SendGrid/Postmark, replace Sheets with your CRM webhook, adjust wait periods for your onboarding timeline.


2. Carrier & Supplier Integration Health Monitor

Your platform is only as reliable as its integrations. A down carrier API or failing EDI connection triggers support tickets fast. Monitor every integration endpoint every 5 minutes and alert before customers notice.

{
  "nodes": [
    {"id":"1","name":"Every 5 Minutes","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"minutes","minutesInterval":5}]}},"position":[250,300]},
    {"id":"2","name":"Load Integration Endpoints","type":"n8n-nodes-base.googleSheets","parameters":{"operation":"getAll","sheetId":"YOUR_SHEET_ID","range":"Integrations!A:E"},"position":[450,300]},
    {"id":"3","name":"Health Check Each","type":"n8n-nodes-base.httpRequest","parameters":{"url":"={{$json.health_endpoint}}","method":"GET","timeout":10000,"continueOnFail":true},"position":[650,300]},
    {"id":"4","name":"Classify Status","type":"n8n-nodes-base.code","parameters":{"jsCode":"const items = $input.all();\nreturn items.map(item => {\n  const status = item.json.statusCode;\n  const responseTime = item.json.responseTime || 0;\n  let classification, priority;\n  if (!status || status >= 500) { classification = 'DOWN'; priority = 'CRITICAL'; }\n  else if (status >= 400) { classification = 'ERROR'; priority = 'HIGH'; }\n  else if (responseTime > 5000) { classification = 'SLOW'; priority = 'MEDIUM'; }\n  else { classification = 'OK'; priority = 'LOW'; }\n  return { json: { ...item.json, classification, priority, checked_at: new Date().toISOString() } };\n});"},"position":[850,300]},
    {"id":"5","name":"Filter Non-OK","type":"n8n-nodes-base.filter","parameters":{"conditions":{"string":[{"value1":"={{$json.classification}}","operation":"notEqual","value2":"OK"}]}},"position":[1050,300]},
    {"id":"6","name":"Slack Integration Alert","type":"n8n-nodes-base.slack","parameters":{"channel":"#integrations-ops","text":"[{{$json.priority}}] Integration {{$json.integration_name}} is {{$json.classification}}\nEndpoint: {{$json.health_endpoint}}\nResponse: {{$json.statusCode}} in {{$json.responseTime}}ms\nCustomers affected: {{$json.affected_customer_count}}"},"position":[1250,200]},
    {"id":"7","name":"Log to SLA Tracker","type":"n8n-nodes-base.googleSheets","parameters":{"operation":"append","sheetId":"YOUR_SHEET_ID","range":"SLA_Log!A:F","values":[["={{$json.integration_name}}","={{$json.classification}}","={{$json.priority}}","={{$json.statusCode}}","={{$json.responseTime}}","={{$json.checked_at}}"]]},"position":[1250,400]}
  ],
  "connections":{"Every 5 Minutes":{"main":[[{"node":"Load Integration Endpoints","type":"main","index":0}]]},"Load Integration Endpoints":{"main":[[{"node":"Health Check Each","type":"main","index":0}]]},"Health Check Each":{"main":[[{"node":"Classify Status","type":"main","index":0}]]},"Classify Status":{"main":[[{"node":"Filter Non-OK","type":"main","index":0}]]},"Filter Non-OK":{"main":[[{"node":"Slack Integration Alert","type":"main","index":0},{"node":"Log to SLA Tracker","type":"main","index":0}]]}}
}
Enter fullscreen mode Exit fullscreen mode

Sheets schema: integration_name | health_endpoint | affected_customer_count | sla_tier | owner_team

Why this matters for supply chain SaaS: a DOWN carrier EDI connection affects every customer routing shipments through that carrier. A 5-minute detection gap is the difference between a proactive 'we're aware and working on it' and a 45-minute customer support spiral.


3. Contract Renewal & Compliance Alert Pipeline

Supplier contracts, carrier MSAs, SaaS license agreements — they all have expiry dates. A missed renewal means a lapsed compliance certification, an automatic rollover at unfavorable rates, or an audit finding.

{
  "nodes": [
    {"id":"1","name":"Daily 8AM","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 8 * * *"}]}},"position":[250,300]},
    {"id":"2","name":"Load Contract Register","type":"n8n-nodes-base.googleSheets","parameters":{"operation":"getAll","sheetId":"YOUR_SHEET_ID","range":"Contracts!A:H"},"position":[450,300]},
    {"id":"3","name":"Calculate Urgency","type":"n8n-nodes-base.code","parameters":{"jsCode":"const items = $input.all();\nreturn items.map(item => {\n  const renewalDate = new Date(item.json.renewal_date);\n  const today = new Date();\n  const daysLeft = Math.ceil((renewalDate - today) / (1000*60*60*24));\n  let urgency;\n  if (daysLeft < 0) urgency = 'OVERDUE';\n  else if (daysLeft <= 7) urgency = 'CRITICAL';\n  else if (daysLeft <= 21) urgency = 'URGENT';\n  else if (daysLeft <= 45) urgency = 'WARNING';\n  else if (daysLeft <= 90) urgency = 'NOTICE';\n  else urgency = 'OK';\n  return { json: { ...item.json, daysLeft, urgency } };\n});"},"position":[650,300]},
    {"id":"4","name":"Filter Needs Action","type":"n8n-nodes-base.filter","parameters":{"conditions":{"string":[{"value1":"={{$json.urgency}}","operation":"notEqual","value2":"OK"}]}},"position":[850,300]},
    {"id":"5","name":"Route by Urgency","type":"n8n-nodes-base.switch","parameters":{"dataPropertyName":"urgency","rules":{"rules":[{"value":"OVERDUE","output":0},{"value":"CRITICAL","output":1},{"value":"URGENT","output":2}]},"fallbackOutput":3},"position":[1050,300]},
    {"id":"6","name":"Slack CRITICAL","type":"n8n-nodes-base.slack","parameters":{"channel":"#procurement-ops","text":"[{{$json.urgency}}] Contract EXPIRES IN {{$json.daysLeft}} DAYS\n{{$json.contract_name}} with {{$json.counterparty}}\nOwner: {{$json.owner}} | Value: ${{$json.contract_value}}\nRenew: {{$json.renewal_link}}"},"position":[1250,100]},
    {"id":"7","name":"Email Owner","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{$json.owner_email}}","subject":"[{{$json.urgency}}] Contract renewal due: {{$json.contract_name}} — {{$json.daysLeft}} days","message":"Contract: {{$json.contract_name}}\nCounterparty: {{$json.counterparty}}\nExpiry: {{$json.renewal_date}} ({{$json.daysLeft}} days)\nValue: ${{$json.contract_value}}\n\nRenewal link: {{$json.renewal_link}}\n\nPlease action before expiry."},"position":[1250,400]}
  ],
  "connections":{"Daily 8AM":{"main":[[{"node":"Load Contract Register","type":"main","index":0}]]},"Load Contract Register":{"main":[[{"node":"Calculate Urgency","type":"main","index":0}]]},"Calculate Urgency":{"main":[[{"node":"Filter Needs Action","type":"main","index":0}]]},"Filter Needs Action":{"main":[[{"node":"Route by Urgency","type":"main","index":0}]]},"Route by Urgency":{"main":[[{"node":"Slack CRITICAL","type":"main","index":0}],[{"node":"Slack CRITICAL","type":"main","index":0}],[{"node":"Email Owner","type":"main","index":0}],[{"node":"Email Owner","type":"main","index":0}]]}}
}
Enter fullscreen mode Exit fullscreen mode

Sheets schema: contract_name | counterparty | renewal_date | owner | owner_email | contract_value | renewal_link | compliance_cert

Urgency tiers: OVERDUE (expired) → CRITICAL (≤7 days) → URGENT (≤21 days) → WARNING (≤45 days) → NOTICE (≤90 days).


4. Procurement Spend Anomaly Alert

Maverick spend, PO bypass, and new-vendor spikes are the top audit findings in procurement operations. Catch them daily — before they show up in the quarterly review.

{
  "nodes": [
    {"id":"1","name":"Daily 6AM","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 6 * * *"}]}},"position":[250,300]},
    {"id":"2","name":"Query Spend Data","type":"n8n-nodes-base.postgres","parameters":{"operation":"executeQuery","query":"SELECT vendor_id, vendor_name, SUM(amount) as today_spend, COUNT(*) as txn_count, MAX(po_required) as po_required, MAX(is_new_vendor) as is_new_vendor FROM procurement_transactions WHERE date = CURRENT_DATE GROUP BY vendor_id, vendor_name"},"position":[450,300]},
    {"id":"3","name":"Query 30-Day Baseline","type":"n8n-nodes-base.postgres","parameters":{"operation":"executeQuery","query":"SELECT vendor_id, AVG(daily_spend) as avg_daily_spend FROM (SELECT vendor_id, date, SUM(amount) as daily_spend FROM procurement_transactions WHERE date >= CURRENT_DATE - 30 GROUP BY vendor_id, date) sub GROUP BY vendor_id"},"position":[450,500]},
    {"id":"4","name":"Detect Anomalies","type":"n8n-nodes-base.code","parameters":{"jsCode":"const todayItems = $input.first().json;\nconst baselineMap = {};\n$node['Query 30-Day Baseline'].json.forEach(b => { baselineMap[b.vendor_id] = parseFloat(b.avg_daily_spend); });\nconst anomalies = [];\ntodayItems.forEach(t => {\n  const baseline = baselineMap[t.vendor_id] || 0;\n  const spike = baseline > 0 && parseFloat(t.today_spend) > baseline * 2;\n  const newVendorHigh = t.is_new_vendor && parseFloat(t.today_spend) > 10000;\n  const poBypass = t.po_required && t.txn_count > 0;\n  if (spike || newVendorHigh || poBypass) {\n    anomalies.push({\n      vendor_name: t.vendor_name,\n      today_spend: t.today_spend,\n      avg_daily: baseline.toFixed(2),\n      spike_ratio: baseline > 0 ? (parseFloat(t.today_spend)/baseline).toFixed(1) : 'NEW',\n      flags: [spike?'SPEND_SPIKE':'', newVendorHigh?'NEW_VENDOR_HIGH':'', poBypass?'PO_BYPASS':''].filter(Boolean).join('+'),\n      txn_count: t.txn_count\n    });\n  }\n});\nreturn anomalies.map(a => ({ json: a }));"},"position":[700,300]},
    {"id":"5","name":"Slack Finance Alert","type":"n8n-nodes-base.slack","parameters":{"channel":"#finance-ops","text":"Spend anomaly detected: {{$json.vendor_name}}\nToday: ${{$json.today_spend}} ({{$json.spike_ratio}}x 30d avg of ${{$json.avg_daily}})\nFlags: {{$json.flags}} | Transactions: {{$json.txn_count}}\nReview: https://app.yourplatform.com/spend/{{$json.vendor_id}}"},"position":[900,300]}
  ],
  "connections":{"Daily 6AM":{"main":[[{"node":"Query Spend Data","type":"main","index":0},{"node":"Query 30-Day Baseline","type":"main","index":0}]]},"Query Spend Data":{"main":[[{"node":"Detect Anomalies","type":"main","index":0}]]},"Detect Anomalies":{"main":[[{"node":"Slack Finance Alert","type":"main","index":0}]]}}
}
Enter fullscreen mode Exit fullscreen mode

Anomaly flags: SPEND_SPIKE (>2x 30-day average), NEW_VENDOR_HIGH (new supplier >$10k), PO_BYPASS (transaction without required purchase order).

Adjust the $10,000 threshold and 2x multiplier to match your platform's typical transaction sizes.


5. Weekly Platform KPI Dashboard

Leadership needs a weekly pulse on platform health — active accounts, GMV processed, integration success rate, new signups, churn. Automate the build.

{
  "nodes": [
    {"id":"1","name":"Monday 8AM","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 8 * * 1"}]}},"position":[250,300]},
    {"id":"2","name":"Query Platform Metrics","type":"n8n-nodes-base.postgres","parameters":{"operation":"executeQuery","query":"SELECT (SELECT COUNT(*) FROM accounts WHERE status='active') as active_accounts, (SELECT COUNT(DISTINCT user_id) FROM events WHERE event_date >= CURRENT_DATE - 7) as wau, (SELECT SUM(transaction_value) FROM transactions WHERE created_at >= CURRENT_DATE - 7) as weekly_gmv, (SELECT COUNT(*) FROM accounts WHERE created_at >= CURRENT_DATE - 7) as new_signups, (SELECT COUNT(*) FROM accounts WHERE churned_at >= CURRENT_DATE - 7) as churned_accounts, (SELECT AVG(CASE WHEN status='success' THEN 1.0 ELSE 0.0 END)*100 FROM integration_events WHERE created_at >= CURRENT_DATE - 7) as integration_success_rate"},"position":[450,300]},
    {"id":"3","name":"Build Dashboard","type":"n8n-nodes-base.code","parameters":{"jsCode":"const m = $input.first().json;\nconst prev = $getWorkflowStaticData('global');\nconst pct = (cur, p) => p ? ((cur-p)/p*100).toFixed(1)+'%' : 'n/a';\nconst html = `<h2>Weekly Platform Report — ${new Date().toDateString()}</h2><table border='1' cellpadding='8' style='border-collapse:collapse'><tr><th>Metric</th><th>This Week</th><th>WoW Change</th></tr><tr><td>Active Accounts</td><td>${m.active_accounts}</td><td>${pct(m.active_accounts, prev.active_accounts)}</td></tr><tr><td>Weekly Active Users</td><td>${m.wau}</td><td>${pct(m.wau, prev.wau)}</td></tr><tr><td>GMV Processed</td><td>$${parseFloat(m.weekly_gmv||0).toLocaleString()}</td><td>${pct(m.weekly_gmv, prev.weekly_gmv)}</td></tr><tr><td>New Signups</td><td>${m.new_signups}</td><td>${pct(m.new_signups, prev.new_signups)}</td></tr><tr><td>Churned Accounts</td><td>${m.churned_accounts}</td><td>—</td></tr><tr><td>Integration Success Rate</td><td>${parseFloat(m.integration_success_rate||0).toFixed(1)}%</td><td>—</td></tr></table>`;\n$setWorkflowStaticData('global', { active_accounts: m.active_accounts, wau: m.wau, weekly_gmv: m.weekly_gmv, new_signups: m.new_signups });\nreturn [{ json: { html, ...m } }];"},"position":[650,300]},
    {"id":"4","name":"Email Leadership","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"vp-product@yourcompany.com","bcc":"ceo@yourcompany.com","subject":"Weekly Platform Report — {{$json.active_accounts}} active accounts, ${{$json.weekly_gmv}} GMV","message":"={{$json.html}}","emailType":"html"},"position":[850,200]},
    {"id":"5","name":"Slack One-Liner","type":"n8n-nodes-base.slack","parameters":{"channel":"#platform-metrics","text":"Weekly: {{$json.active_accounts}} active accounts | {{$json.wau}} WAU | ${{$json.weekly_gmv}} GMV | {{$json.new_signups}} new | {{$json.integration_success_rate}}% integration success rate"},"position":[850,400]}
  ],
  "connections":{"Monday 8AM":{"main":[[{"node":"Query Platform Metrics","type":"main","index":0}]]},"Query Platform Metrics":{"main":[[{"node":"Build Dashboard","type":"main","index":0}]]},"Build Dashboard":{"main":[[{"node":"Email Leadership","type":"main","index":0},{"node":"Slack One-Liner","type":"main","index":0}]]}}
}
Enter fullscreen mode Exit fullscreen mode

$getWorkflowStaticData('global') stores last week's numbers for WoW% change — no extra database table needed.


Why supply chain and procurement SaaS teams choose self-hosted n8n

n8n (self-hosted) Zapier Make.com
Supplier pricing data stays in your network
Customer spend patterns under NDA
Carrier rate card confidentiality
Git-versioned audit trail
Cost at 50K ops/month ~$0 $299+/mo $99+/mo
Custom integrations with carrier/ERP APIs Limited Limited

Supply chain SaaS platforms handle data that is commercially sensitive by definition — supplier pricing strategies, customer spend patterns, logistics contracts, and carrier rate negotiations are all covered by NDAs. Routing that data through Zapier or Make creates a GDPR Art.28 sub-processor relationship and a potential NDA breach with every automation you build.

Self-hosted n8n means the data never leaves your infrastructure.


All 5 workflows in one place

These 5 automations — plus 10 more for SaaS, e-commerce, finance, and DevOps teams — are in the FlowKit n8n template store:

Browse all templates → stripeai.gumroad.com

Each template includes: import-ready workflow JSON + step-by-step setup guide.


Which integration headache is costing you the most engineering time? Drop it in the comments.

Top comments (0)