DEV Community

Alex Kane
Alex Kane

Posted on

n8n for MedTech SaaS: 5 Automations for FDA 21 CFR Part 820, ISO 13485, and MDR EU 2017/745 Compliance (Free JSON)

If you're building medical device software — a QMS platform, eQMS solution, design controls system, or SaMD analytics tool — your customers aren't just evaluating features. They're evaluating your infrastructure's compliance posture. Every automation workflow that routes NCR records, CAPA data, or vigilance event alerts through a cloud-based iPaaS is a potential audit finding under FDA 21 CFR Part 820 (QMSR), ISO 13485:2016, or MDR EU 2017/745.

This article covers 5 production-ready n8n workflow patterns specifically for MedTech SaaS vendors — companies building software for medical device manufacturers, SaMD developers, IVD companies, and clinical operations teams. These are the automation patterns that MedTech SaaS engineering and customer success teams need on Day 1.

All workflow JSON is import-ready. Copy to a .json file and import via n8n's workflow import feature.

Why MedTech SaaS Vendors Are Moving Away from Zapier

Three structural reasons why medical device quality data cannot safely route through Zapier's shared cloud infrastructure:

Record retention. FDA 21 CFR §820.180 requires quality records to be retained for the design and expected life of the device, or a minimum of 2 years from device release, whichever is longer. Zapier's 30-day automatic task history deletion is a documented non-conformance under QMSR record control requirements.

Audit trail integrity. ISO 13485:2016 §4.2.5 requires that quality records be legible, readily identifiable, and retrievable. MDR EU Art. 87 vigilance reports require a traceable chain from event detection to NCA submission. A workflow task log that auto-deletes is not 'retrievable' in the sense intended by either standard.

Supplier qualification. ISO 13485 §7.4 and FDA 21 CFR §820.50 (supplier controls) both require that externally provided services be evaluated and selected based on their ability to meet requirements. Zapier's standard terms do not include quality agreement provisions compatible with ISO 13485 supplier qualification audits.

Self-hosted n8n on your infrastructure (or your customer's VPC) eliminates all three issues: workflow JSON is git-versioned, execution history is retained in your own database, and there's no third-party data routing to qualify.


Workflow 1: MedTech Customer QMS & Regulatory Onboarding Drip

{
  "name": "MedTech Customer QMS & Regulatory Onboarding Drip",
  "nodes": [
    {
      "id": "1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        250,
        300
      ],
      "parameters": {
        "path": "medtech-customer-onboarding",
        "method": "POST",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "2",
      "name": "Classify SaMD & QMS Tier",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        470,
        300
      ],
      "parameters": {
        "jsCode": "const d=items[0].json;\nconst tier=d.annual_revenue_usd>500e6?'LARGE_MEDTECH':d.annual_revenue_usd>50e6?'MID_MARKET_MEDTECH':d.company_type==='IVD_MANUFACTURER'?'IVD_MANUFACTURER':d.company_type==='STARTUP_SAMD'?'STARTUP_SAMD':'SMB_MEDTECH';\nconst flags=[];\nif(d.fda_510k_cleared||d.fda_pma_approved)flags.push('FDA_CLEARED_DEVICE');\nif(d.samd_class==='II'||d.samd_class==='III')flags.push('SAMD_CLASS_'+d.samd_class);\nif(d.ce_marked_mdr||d.eu_market)flags.push('MDR_EU_2017_745');\nif(d.ce_marked_ivdr||d.ivd_product)flags.push('IVDR_EU_2017_746');\nif(d.iso_13485_certified)flags.push('ISO_13485_CERTIFIED');\nif(d.iec_62304_class)flags.push('IEC_62304_CLASS_'+d.iec_62304_class);\nif(d.mdsap_participating)flags.push('MDSAP_ENROLLED');\nif(d.us_commercial_device)flags.push('FDA_QMSR_21_CFR_820');\nreturn[{json:{...d,customer_tier:tier,compliance_flags:flags,onboarding_ts:new Date().toISOString()}}];"
      }
    },
    {
      "id": "3",
      "name": "Log to Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        690,
        300
      ],
      "parameters": {
        "operation": "insert",
        "table": "medtech_customer_onboarding",
        "columns": "customer_id,company_name,customer_tier,compliance_flags,onboarding_ts",
        "values": "={{$json.customer_id}},={{$json.company_name}},={{$json.customer_tier}},={{JSON.stringify($json.compliance_flags)}},={{$json.onboarding_ts}}"
      }
    },
    {
      "id": "4",
      "name": "Day 0 Welcome Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        910,
        200
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.contact_email}}",
        "subject": "Welcome to FlowKit \u2014 Your MedTech QMS Automation Platform is Ready",
        "message": "={{$json.customer_tier==='LARGE_MEDTECH'?'Your dedicated MedTech CSM will contact you within 2 business hours.':$json.compliance_flags.includes('SAMD_CLASS_III')?'Your Class III SaMD validation workflow templates are pre-configured per IEC 62304 Class C.':$json.compliance_flags.includes('MDR_EU_2017_745')?'Your EU MDR vigilance and PMCF workflow templates are active.':'Your onboarding checklist is attached.'}} Active compliance scope: {{$json.compliance_flags.join(', ')}}"
      }
    },
    {
      "id": "5",
      "name": "Day 0 CSM Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        910,
        400
      ],
      "parameters": {
        "channel": "#csm-medtech-onboarding",
        "text": "New {{$json.customer_tier}} customer: *{{$json.company_name}}*. Flags: {{$json.compliance_flags.join(' | ')}}. {{$json.compliance_flags.includes('SAMD_CLASS_III')?'\u26a0\ufe0f Class III SaMD \u2014 IEC 62304 Class C software lifecycle documentation required':''}} {{$json.compliance_flags.includes('MDR_EU_2017_745')?'\u26a0\ufe0f MDR EU \u2014 vigilance & PMCF workflows must be active pre-go-live':''}}"
      }
    },
    {
      "id": "6",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        1130,
        300
      ],
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "7",
      "name": "Check eQMS Integration",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1350,
        300
      ],
      "parameters": {
        "operation": "select",
        "query": "SELECT integration_status FROM medtech_integration_status WHERE customer_id = '{{$json.customer_id}}'"
      }
    },
    {
      "id": "8",
      "name": "Integration Connected?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1570,
        300
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json.integration_status}}",
              "operation": "notEqual",
              "value2": "CONNECTED"
            }
          ]
        }
      }
    },
    {
      "id": "9",
      "name": "Day 3 Reminder Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1790,
        200
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.contact_email}}",
        "subject": "Your eQMS/Design Control integration \u2014 5-minute setup",
        "message": "Hi {{$json.contact_name}}, your system integration is still pending. {{$json.compliance_flags.includes('FDA_QMSR_21_CFR_820')?'21 CFR Part 820.70(i) computer systems require validated connections before GxP data processing.':''}} {{$json.compliance_flags.includes('MDR_EU_2017_745')?'MDR EU Art. 87 vigilance reports require traceable audit trail from your eQMS.':''}} Book a 15-min setup call: calendly.com/flowkit-medtech"
      }
    },
    {
      "id": "10",
      "name": "Wait 4 More Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        2010,
        300
      ],
      "parameters": {
        "amount": 4,
        "unit": "days"
      }
    },
    {
      "id": "11",
      "name": "Day 7 Check-In Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        2230,
        300
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.contact_email}}",
        "subject": "Week 1 \u2014 Are your MedTech compliance workflows running?",
        "message": "Hi {{$json.contact_name}}, it has been 7 days. {{$json.compliance_flags.includes('ISO_13485_CERTIFIED')?'Your ISO 13485 internal audit tracker should be active.':''}} {{$json.compliance_flags.includes('IEC_62304_CLASS_C')?'Your IEC 62304 Class C anomaly resolution workflow should be running.':''}} Anything blocking you? Reply to this email."
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Classify SaMD & QMS Tier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify SaMD & QMS Tier": {
      "main": [
        [
          {
            "node": "Log to Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Postgres": {
      "main": [
        [
          {
            "node": "Day 0 Welcome Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Day 0 CSM Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 0 Welcome Email": {
      "main": [
        [
          {
            "node": "Wait 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Days": {
      "main": [
        [
          {
            "node": "Check eQMS Integration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check eQMS Integration": {
      "main": [
        [
          {
            "node": "Integration Connected?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Integration Connected?": {
      "main": [
        [
          {
            "node": "Day 3 Reminder Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 4 More Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 3 Reminder Email": {
      "main": [
        [
          {
            "node": "Wait 4 More Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 More Days": {
      "main": [
        [
          {
            "node": "Day 7 Check-In Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What it does: Fires on a new customer webhook. Classifies the account into tiers — LARGE_MEDTECH, MID_MARKET_MEDTECH, IVD_MANUFACTURER, STARTUP_SAMD — and applies compliance flags based on the customer's regulatory profile: FDA_CLEARED_DEVICE, SAMD_CLASS_II/III, MDR_EU_2017_745, IVDR_EU_2017_746, ISO_13485_CERTIFIED, IEC_62304_CLASS_B/C, MDSAP_ENROLLED, FDA_QMSR_21_CFR_820. Day 0 sends a tailored welcome email and CSM Slack alert. Day 3 checks eQMS integration status and fires a reminder for QMSR-regulated accounts with no confirmed connection. Day 7 sends a check-in referencing the customer's active compliance flags.

Key vendor angle: Your CSM team needs to know within minutes whether an account is Class III SaMD, MDR EU-regulated, or ISO 13485-certified. Automating tier + flag classification at webhook receipt means the CSM who calls LARGE_MEDTECH on Day 1 already knows they need to discuss IEC 62304 Class C software lifecycle documentation requirements before the customer attempts any GxP data processing.


Workflow 2: eQMS & Design Controls Integration Health Monitor

{
  "name": "eQMS & Design Controls Integration Health Monitor",
  "nodes": [
    {
      "id": "1",
      "name": "Schedule 15min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Fetch Integration Status",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        470,
        300
      ],
      "parameters": {
        "operation": "select",
        "query": "SELECT customer_id, company_name, integration_type, last_sync_ts, expected_sync_interval_minutes, compliance_tier, validation_status, open_ncrs FROM medtech_integrations WHERE active = true ORDER BY last_sync_ts ASC"
      }
    },
    {
      "id": "3",
      "name": "Evaluate Health & Regulatory Risk",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        690,
        300
      ],
      "parameters": {
        "jsCode": "const alerts=[];\nconst now=Date.now();\nconst integrationLabels={\n  'EQMS':'Electronic Quality Management System',\n  'DESIGN_CONTROLS':'Design Controls & DHF System',\n  'CAPA_MODULE':'CAPA Management Module',\n  'DOCUMENT_CONTROL':'Document Control System',\n  'COMPLAINT_HANDLING':'Complaint Handling System',\n  'AUDIT_MANAGEMENT':'Audit Management Module',\n  'RISK_MANAGEMENT':'Risk Management (ISO 14971)',\n  'PMCF_MODULE':'Post-Market Clinical Follow-Up System'\n};\nfor(const s of items){\n  const d=s.json;\n  const minsSince=(now-new Date(d.last_sync_ts).getTime())/60000;\n  const threshold=d.expected_sync_interval_minutes||60;\n  const label=integrationLabels[d.integration_type]||d.integration_type;\n  const isRegulated=d.compliance_tier==='QMSR_REGULATED'||d.compliance_tier==='ISO_13485_CERTIFIED';\n  const isLargeMedtech=d.customer_tier==='LARGE_MEDTECH';\n  if(minsSince>threshold*2){\n    const qmsrNote=isRegulated?' \u2014 21 CFR \u00a7820.70(i) computer system data gap risk':'';\n    alerts.push({...d,alert_type:'OFFLINE',severity:'CRITICAL',label,mins_stale:Math.round(minsSince),message:`CRITICAL: ${d.company_name} ${label} OFFLINE ${Math.round(minsSince)}min${qmsrNote}`,requires_proactive_email:isRegulated&&isLargeMedtech});\n  } else if(minsSince>threshold*1.5){\n    alerts.push({...d,alert_type:'DEGRADED',severity:'HIGH',label,mins_stale:Math.round(minsSince),message:`HIGH: ${d.company_name} ${label} DEGRADED \u2014 ${Math.round(minsSince)}min since last sync`,requires_proactive_email:false});\n  }\n  if(d.validation_status==='EXPIRED'||d.validation_status==='OUT_OF_VALIDATION'){\n    alerts.push({...d,alert_type:'VALIDATION_EXPIRED',severity:'CRITICAL',label,message:`CRITICAL: ${d.company_name} ${label} validation EXPIRED \u2014 21 CFR \u00a7820.70(i) blocked, ISO 13485 \u00a77.5.1 non-conformance`,requires_proactive_email:true});\n  }\n  if(d.open_ncrs>5){\n    alerts.push({...d,alert_type:'NCR_BACKLOG',severity:'HIGH',label,message:`HIGH: ${d.company_name} has ${d.open_ncrs} open NCRs in ${label} \u2014 ISO 13485 \u00a78.3 non-conforming product backlog`,requires_proactive_email:false});\n  }\n}\nreturn alerts.length?alerts.map(a=>({json:a})):[{json:{no_alerts:true,checked_at:new Date().toISOString()}}];"
      }
    },
    {
      "id": "4",
      "name": "Has Alerts?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        910,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.no_alerts===true}}",
              "operation": "equal",
              "value2": true
            }
          ]
        }
      }
    },
    {
      "id": "5",
      "name": "Log Alert to Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1130,
        200
      ],
      "parameters": {
        "operation": "insert",
        "table": "medtech_integration_alerts",
        "columns": "customer_id,integration_type,alert_type,severity,message,detected_at",
        "values": "={{$json.customer_id}},={{$json.integration_type}},={{$json.alert_type}},={{$json.severity}},={{$json.message}},={{new Date().toISOString()}}"
      }
    },
    {
      "id": "6",
      "name": "Slack #platform-medtech-ops",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1350,
        200
      ],
      "parameters": {
        "channel": "#platform-medtech-ops",
        "text": "{{$json.message}} | {{$json.company_name}} | {{new Date().toISOString()}}"
      }
    },
    {
      "id": "7",
      "name": "Proactive Customer Email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1350,
        400
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.requires_proactive_email}}",
              "operation": "equal",
              "value2": true
            }
          ]
        }
      }
    },
    {
      "id": "8",
      "name": "Proactive Customer Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1570,
        400
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.contact_email}}",
        "subject": "[ACTION REQUIRED] {{$json.label}} connection issue \u2014 QMS data gap risk",
        "message": "Hi {{$json.contact_name}}, our platform monitoring detected your {{$json.label}} has been offline for {{$json.mins_stale}} minutes. This may create a 21 CFR \u00a7820.70(i) audit trail gap. Our team is investigating. Please confirm your systems are operational and reply to this email if you need assistance."
      }
    }
  ],
  "connections": {
    "Schedule 15min": {
      "main": [
        [
          {
            "node": "Fetch Integration Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Integration Status": {
      "main": [
        [
          {
            "node": "Evaluate Health & Regulatory Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Health & Regulatory Risk": {
      "main": [
        [
          {
            "node": "Has Alerts?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Alerts?": {
      "main": [
        [],
        [
          {
            "node": "Log Alert to Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Alert to Postgres": {
      "main": [
        [
          {
            "node": "Slack #platform-medtech-ops",
            "type": "main",
            "index": 0
          },
          {
            "node": "Proactive Customer Email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Proactive Customer Email?": {
      "main": [
        [
          {
            "node": "Proactive Customer Email",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What it does: Polls active customer integrations every 15 minutes. Classifies integration types — EQMS, DESIGN_CONTROLS, CAPA_MODULE, DOCUMENT_CONTROL, COMPLAINT_HANDLING, AUDIT_MANAGEMENT, RISK_MANAGEMENT (ISO 14971), PMCF_MODULE — and evaluates health against expected sync intervals. OFFLINE (over 2x expected interval) fires CRITICAL to Slack #platform-medtech-ops and logs to Postgres with the 21 CFR §820.70(i) citation. DEGRADED (over 1.5x) fires HIGH. Expired validation status fires CRITICAL with §820.70(i) blocked language. For QMSR_REGULATED accounts at LARGE_MEDTECH tier, the workflow sends a proactive customer email about the data gap risk. Open NCR backlogs (>5) fire HIGH with the ISO 13485 §8.3 citation.

Key vendor angle: Proactive outreach on integration downtime is a churn prevention play disguised as compliance monitoring. The customer doesn't discover the eQMS gap during their next CAPA review — you tell them first. That turns a potential audit finding into a customer success moment.


Workflow 3: FDA 21 CFR 820 / ISO 13485 / MDR EU Compliance Deadline Tracker

{
  "name": "FDA 21 CFR 820 / ISO 13485 / MDR EU Compliance Deadline Tracker",
  "nodes": [
    {
      "id": "1",
      "name": "Weekdays 7 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * 1-5"
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Fetch Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        470,
        300
      ],
      "parameters": {
        "operation": "getAll",
        "sheetId": "YOUR_SHEET_ID",
        "range": "Deadlines!A:H"
      }
    },
    {
      "id": "3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        690,
        300
      ],
      "parameters": {
        "jsCode": "const today=new Date();\nconst deadlineActions={\n  'FDA_QMSR_MANAGEMENT_REVIEW':'Annual Management Review per 21 CFR \u00a7820.20(c) \u2014 schedule exec meeting, prepare quality system metrics',\n  'FDA_MDR_ADVERSE_EVENT_ANNUAL':'Annual MDR Adverse Event Report per 21 CFR \u00a7803.57 \u2014 compile device complaint data, submit via MedWatch',\n  'FDA_UDI_LABELING_UPDATE':'UDI labeling review per 21 CFR Part 830 \u2014 verify UDI on all device labels and packaging',\n  'ISO_13485_INTERNAL_AUDIT':'ISO 13485:2016 \u00a78.2.4 internal audit \u2014 schedule cross-functional audit, prepare audit checklist',\n  'ISO_13485_SURVEILLANCE_AUDIT':'ISO 13485 surveillance audit (notified body) \u2014 prepare technical documentation, DHF review',\n  'ISO_13485_RECERTIFICATION':'ISO 13485 3-year recertification \u2014 full QMS review, prepare for notified body assessment',\n  'IEC_62304_SOFTWARE_REVIEW':'IEC 62304 software maintenance review \u2014 review anomaly log, update SOUP list, verify version control',\n  'MDR_EU_VIGILANCE_TREND_REPORT':'MDR EU Art. 88 trend report to national competent authority \u2014 compile adverse events, prepare PSUR update',\n  'MDR_EU_PMCF_PLAN_UPDATE':'MDR EU Annex XIV PMCF plan annual update \u2014 review PMCF activities, compile real-world evidence',\n  'MDR_EU_SSCP_UPDATE':'MDR EU Annex III SSCP update \u2014 update Summary of Safety and Clinical Performance on EUDAMED',\n  'MDSAP_ANNUAL_AUDIT':'MDSAP audit cycle (FDA/Health Canada/ANVISA/TGA/MHLW) \u2014 prepare integrated QMS evidence package',\n  'FDA_510K_ANNUAL_REVIEW':'510(k) annual equivalent device review \u2014 assess any design changes requiring new 510(k) submission'\n};\nreturn items.map(item=>{\n  const d=item.json;\n  const deadline=new Date(d.deadline_date);\n  const daysLeft=Math.round((deadline-today)/(1000*60*60*24));\n  let urgency,slackChannel;\n  if(daysLeft<0){urgency='OVERDUE';slackChannel='#regulatory-critical';}\n  else if(daysLeft<=7){urgency='CRITICAL';slackChannel='#regulatory-critical';}\n  else if(daysLeft<=21){urgency='URGENT';slackChannel='#regulatory-ops';}\n  else if(daysLeft<=45){urgency='WARNING';slackChannel='#regulatory-ops';}\n  else{urgency='NOTICE';slackChannel='#regulatory-ops';}\n  const action=deadlineActions[d.deadline_type]||'Review and action required';\n  return{json:{...d,days_left:daysLeft,urgency,slackChannel,action,checked_at:today.toISOString()}};\n}).filter(i=>i.json.urgency!=='NOTICE');"
      }
    },
    {
      "id": "4",
      "name": "Route by Urgency",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 1,
      "position": [
        910,
        300
      ],
      "parameters": {
        "dataType": "string",
        "value": "={{$json.urgency}}",
        "rules": {
          "rules": [
            {
              "value": "OVERDUE",
              "output": 0
            },
            {
              "value": "CRITICAL",
              "output": 1
            },
            {
              "value": "URGENT",
              "output": 2
            },
            {
              "value": "WARNING",
              "output": 3
            }
          ]
        }
      }
    },
    {
      "id": "5",
      "name": "Slack Critical Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1130,
        200
      ],
      "parameters": {
        "channel": "#regulatory-critical",
        "text": "\ud83d\udea8 *{{$json.urgency}}* \u2014 {{$json.customer_name}} {{$json.deadline_type}} {{$json.days_left<0?'OVERDUE by '+Math.abs($json.days_left)+' days':'due in '+$json.days_left+' days'}}. Action: {{$json.action}}"
      }
    },
    {
      "id": "6",
      "name": "Gmail RA Director",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1350,
        200
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.ra_director_email}}",
        "subject": "[{{$json.urgency}}] {{$json.customer_name}} \u2014 {{$json.deadline_type}} {{$json.days_left<0?'OVERDUE':'due in '+$json.days_left+' days'}}",
        "message": "Regulatory deadline alert:\n\nCustomer: {{$json.customer_name}}\nDeadline Type: {{$json.deadline_type}}\nDue Date: {{$json.deadline_date}}\nDays Remaining: {{$json.days_left}}\nUrgency: {{$json.urgency}}\n\nRequired Action: {{$json.action}}\n\nThis alert is logged to the regulatory register for audit purposes."
      }
    },
    {
      "id": "7",
      "name": "Log to Regulatory Register",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        1570,
        300
      ],
      "parameters": {
        "operation": "insert",
        "table": "regulatory_deadline_register",
        "columns": "customer_id,deadline_type,deadline_date,days_left,urgency,action,logged_at",
        "values": "={{$json.customer_id}},={{$json.deadline_type}},={{$json.deadline_date}},={{$json.days_left}},={{$json.urgency}},={{$json.action}},={{new Date().toISOString()}}"
      }
    }
  ],
  "connections": {
    "Weekdays 7 AM": {
      "main": [
        [
          {
            "node": "Fetch Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Deadlines": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "Route by Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Urgency": {
      "main": [
        [
          {
            "node": "Slack Critical Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack Critical Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail RA Director",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail RA Director",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Critical Alert": {
      "main": [
        [
          {
            "node": "Gmail RA Director",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail RA Director": {
      "main": [
        [
          {
            "node": "Log to Regulatory Register",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What it does: Runs weekdays at 7 AM. Reads a Google Sheet of per-customer regulatory deadlines and classifies them across twelve types with regulatory citations: FDA QMSR Annual Management Review (§820.20(c)), FDA MDR Adverse Event Annual Report (§803.57), FDA UDI Labeling Update (21 CFR Part 830), ISO 13485 Internal Audit (§8.2.4), ISO 13485 Surveillance and Recertification audits, IEC 62304 Software Maintenance Review, MDR EU Art. 88 Trend Report, MDR EU Annex XIV PMCF Plan Update, MDR EU Annex III SSCP Update on EUDAMED, MDSAP Annual Audit Cycle, and FDA 510(k) Annual Equivalent Device Review. OVERDUE and CRITICAL route to Slack #regulatory-critical and Gmail to the RA Director. URGENT and WARNING route to Gmail. All fire to Postgres for the regulatory register.

Key vendor angle: MedTech enterprises track dozens of FDA, EU, and international regulatory deadlines per product line. If your platform owns the regulatory calendar and can demonstrate that every deadline was tracked with timestamped evidence — that's a defensible audit record and a strong renewal argument.


Workflow 4: FDA MDR / EU Vigilance Adverse Event Alert Pipeline

{
  "name": "FDA MDR / EU MDR Vigilance Adverse Event Alert Pipeline",
  "nodes": [
    {
      "id": "1",
      "name": "Adverse Event Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        250,
        300
      ],
      "parameters": {
        "path": "medtech-adverse-event",
        "method": "POST",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "2",
      "name": "Dedup & Classify Event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        470,
        300
      ],
      "parameters": {
        "jsCode": "const d=items[0].json;\nconst key=`${d.event_type}_${d.customer_id}_${d.device_id}_${d.event_date}`;\nconst seen=$getWorkflowStaticData('global');\nif(seen[key]&&(Date.now()-seen[key]<30*60*1000))return[{json:{duplicate:true,key}}];\nseen[key]=Date.now();\nconst eventMap={\n  'FDA_MDR_DEATH_SERIOUS_INJURY':{severity:'CRITICAL',deadline_days:30,channel:'#ra-mdr-critical',cite:'21 CFR \u00a7803.52 \u2014 30-day MDR to FDA required',email_director:true},\n  'FDA_MDR_MALFUNCTION':{severity:'HIGH',deadline_days:30,channel:'#ra-mdr-ops',cite:'21 CFR \u00a7803.52 \u2014 30-day MDR if malfunction would likely cause serious injury if recurring',email_director:false},\n  'FDA_MDR_SUPPLEMENTAL':{severity:'MEDIUM',deadline_days:30,channel:'#ra-mdr-ops',cite:'21 CFR \u00a7803.56 \u2014 supplemental MDR within 30 days of new information',email_director:false},\n  'MDR_EU_SERIOUS_INCIDENT':{severity:'CRITICAL',deadline_days:15,channel:'#ra-mdr-critical',cite:'MDR EU Art. 87(1) \u2014 15-day vigilance report to NCA required',email_director:true},\n  'MDR_EU_TREND_REPORT':{severity:'HIGH',deadline_days:30,channel:'#ra-mdr-ops',cite:'MDR EU Art. 88 \u2014 trend report within 30 days of threshold',email_director:false},\n  'IVDR_SERIOUS_INCIDENT':{severity:'CRITICAL',deadline_days:15,channel:'#ra-mdr-critical',cite:'IVDR EU Art. 82(1) \u2014 15-day vigilance report to NCA required',email_director:true},\n  'FDA_FIELD_SAFETY_CORRECTION':{severity:'CRITICAL',deadline_days:10,channel:'#ra-mdr-critical',cite:'FDA 21 CFR Part 806 \u2014 field safety correction / device recall, 10-day report',email_director:true},\n  'IEC_62304_ANOMALY_SAFETY_CRITICAL':{severity:'HIGH',deadline_days:7,channel:'#ra-mdr-ops',cite:'IEC 62304 \u00a79.8 \u2014 safety-class B/C anomaly, evaluate for MDR reportability within 7 days',email_director:false}\n};\nconst config=eventMap[d.event_type]||{severity:'MEDIUM',deadline_days:30,channel:'#ra-mdr-ops',cite:'Review required',email_director:false};\nconst deadlineDate=new Date(Date.now()+config.deadline_days*86400000).toISOString().split('T')[0];\nreturn[{json:{...d,...config,deadline_date:deadlineDate,alert_key:key}}];"
      }
    },
    {
      "id": "3",
      "name": "Duplicate?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        690,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.duplicate===true}}",
              "operation": "equal",
              "value2": true
            }
          ]
        }
      }
    },
    {
      "id": "4",
      "name": "Respond OK (dup)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        910,
        200
      ],
      "parameters": {
        "responseCode": 200,
        "responseBody": "duplicate"
      }
    },
    {
      "id": "5",
      "name": "Log Event to Postgres",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        910,
        400
      ],
      "parameters": {
        "operation": "insert",
        "table": "medtech_adverse_events",
        "columns": "customer_id,device_id,event_type,severity,deadline_date,regulatory_citation,event_date,logged_at",
        "values": "={{$json.customer_id}},={{$json.device_id}},={{$json.event_type}},={{$json.severity}},={{$json.deadline_date}},={{$json.cite}},={{$json.event_date}},={{new Date().toISOString()}}"
      }
    },
    {
      "id": "6",
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1130,
        400
      ],
      "parameters": {
        "channel": "={{$json.channel}}",
        "text": "{{$json.severity==='CRITICAL'?'\ud83d\udea8':'\u26a0\ufe0f'}} *{{$json.event_type}}* \u2014 {{$json.customer_name}} | Device: {{$json.device_id}} | Deadline: {{$json.deadline_date}} | {{$json.cite}}"
      }
    },
    {
      "id": "7",
      "name": "Email RA Director?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1350,
        400
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.email_director}}",
              "operation": "equal",
              "value2": true
            }
          ]
        }
      }
    },
    {
      "id": "8",
      "name": "Gmail RA Director",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1570,
        400
      ],
      "parameters": {
        "operation": "send",
        "to": "={{$json.ra_director_email}}",
        "subject": "[{{$json.severity}}] {{$json.event_type}} \u2014 {{$json.customer_name}} \u2014 Action by {{$json.deadline_date}}",
        "message": "Adverse event alert:\n\nCustomer: {{$json.customer_name}}\nDevice: {{$json.device_id}}\nEvent Type: {{$json.event_type}}\nEvent Date: {{$json.event_date}}\nDeadline: {{$json.deadline_date}}\nRegulatory Citation: {{$json.cite}}\n\nThis event has been logged to the MedTech adverse event register.\nImmediate review required.\n\nSeverity: {{$json.severity}}"
      }
    },
    {
      "id": "9",
      "name": "Respond OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1130,
        600
      ],
      "parameters": {
        "responseCode": 200,
        "responseBody": "logged"
      }
    }
  ],
  "connections": {
    "Adverse Event Webhook": {
      "main": [
        [
          {
            "node": "Dedup & Classify Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedup & Classify Event": {
      "main": [
        [
          {
            "node": "Duplicate?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Duplicate?": {
      "main": [
        [
          {
            "node": "Respond OK (dup)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Event to Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Event to Postgres": {
      "main": [
        [
          {
            "node": "Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert": {
      "main": [
        [
          {
            "node": "Email RA Director?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email RA Director?": {
      "main": [
        [
          {
            "node": "Gmail RA Director",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail RA Director": {
      "main": [
        [
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What it does: Webhook-triggered with 30-minute deduplication via $getWorkflowStaticData. Classifies and routes eight event types with regulatory deadlines and citations: FDA MDR Death/Serious Injury (21 CFR §803.52, 30-day), FDA MDR Malfunction (§803.52, 30-day if recurrence would cause serious injury), FDA MDR Supplemental (§803.56, 30-day new information), MDR EU Serious Incident (Art. 87(1), 15-day NCA report), MDR EU Trend Report (Art. 88, 30-day), IVDR EU Serious Incident (Art. 82(1), 15-day), FDA Field Safety Correction (21 CFR Part 806, 10-day recall report), and IEC 62304 Class B/C Safety-Critical Anomaly (§9.8, 7-day MDR reportability evaluation). Each event writes to Postgres for device lifetime retention. CRITICAL events email the RA Director.

Key vendor angle: During enterprise MedTech sales, the hardest question is: 'Where do your adverse event records go if we receive an FDA inspector?' With self-hosted n8n, the answer is: 'They stay in your environment, retained for the life of the device, with full git-versioned workflow audit trail.' That answer closes deals that Zapier cannot.


Workflow 5: Weekly MedTech SaaS KPI Dashboard

{
  "name": "Weekly MedTech SaaS KPI Dashboard",
  "nodes": [
    {
      "id": "1",
      "name": "Monday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Fetch Platform Metrics",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        470,
        200
      ],
      "parameters": {
        "operation": "select",
        "query": "SELECT total_customers, large_medtech_count, mid_market_count, active_this_week, avg_open_ncrs, customers_with_expired_validation, integrations_offline, customers_with_vigilance_events_open FROM medtech_platform_metrics WHERE report_date = CURRENT_DATE - INTERVAL '7 days'"
      }
    },
    {
      "id": "3",
      "name": "Fetch Business Metrics",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        470,
        400
      ],
      "parameters": {
        "operation": "select",
        "query": "SELECT total_mrr, mrr_prev_week, churn_risk_count, new_customers_this_week, overdue_regulatory_deadlines FROM medtech_business_metrics WHERE report_week = date_trunc('week', CURRENT_DATE)"
      }
    },
    {
      "id": "4",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2,
      "position": [
        690,
        300
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll"
      }
    },
    {
      "id": "5",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        910,
        300
      ],
      "parameters": {
        "jsCode": "const d=items[0].json;\nconst prev=$getWorkflowStaticData('global');\nconst mrrWoW=prev.last_mrr?((d.total_mrr-prev.last_mrr)/prev.last_mrr*100).toFixed(1):null;\nprev.last_mrr=d.total_mrr;\nconst alerts=[];\nif(d.customers_with_expired_validation>0)alerts.push(`\u26a0\ufe0f ${d.customers_with_expired_validation} customers with expired 21 CFR \u00a7820.70(i) validation`);\nif(d.integrations_offline>0)alerts.push(`\ud83d\udd34 ${d.integrations_offline} eQMS/Design Control integrations offline`);\nif(d.customers_with_vigilance_events_open>0)alerts.push(`\u26a0\ufe0f ${d.customers_with_vigilance_events_open} customers with open vigilance events (MDR/IVDR deadline risk)`);\nif(d.overdue_regulatory_deadlines>0)alerts.push(`\ud83d\udea8 ${d.overdue_regulatory_deadlines} overdue regulatory deadlines (FDA/ISO/MDR)`);\nconst alertStr=alerts.length?alerts.join('\\n'):'\u2705 No active regulatory or operational alerts';\nconst subject=alerts.length?`[MEDTECH KPI \u2014 ${alerts.length} ALERT${alerts.length>1?'S':''}] Week of ${new Date().toISOString().split('T')[0]}`:`[MEDTECH KPI] Week of ${new Date().toISOString().split('T')[0]}`;\nconst html=`<h2>MedTech SaaS Weekly KPI \u2014 ${new Date().toISOString().split('T')[0]}</h2><table border='1' cellpadding='6' style='border-collapse:collapse'><tr><th>Metric</th><th>Value</th></tr><tr><td>Total MRR</td><td>$${d.total_mrr?.toLocaleString()}${mrrWoW?` (${mrrWoW>0?'+':''}${mrrWoW}% WoW)`:''}</td></tr><tr><td>Total Customers</td><td>${d.total_customers} (${d.large_medtech_count} Large / ${d.mid_market_count} Mid-Market)</td></tr><tr><td>Active This Week</td><td>${d.active_this_week}</td></tr><tr><td>Churn Risk</td><td>${d.churn_risk_count}</td></tr><tr><td>New Customers</td><td>${d.new_customers_this_week}</td></tr><tr><td>Avg Open NCRs per Customer</td><td>${d.avg_open_ncrs?.toFixed(1)}</td></tr><tr><td>Expired \u00a7820.70(i) Validations</td><td style='color:${d.customers_with_expired_validation>0?'red':'green'}'>${d.customers_with_expired_validation}</td></tr><tr><td>Integrations Offline</td><td style='color:${d.integrations_offline>0?'red':'green'}'>${d.integrations_offline}</td></tr><tr><td>Open Vigilance Events</td><td style='color:${d.customers_with_vigilance_events_open>0?'orange':'green'}'>${d.customers_with_vigilance_events_open}</td></tr></table><h3>Alerts</h3><pre>${alertStr}</pre>`;\nreturn[{json:{...d,html,subject,slack_summary:`MedTech KPI: MRR $${d.total_mrr?.toLocaleString()}${mrrWoW?` (${mrrWoW>0?'+':''}${mrrWoW}% WoW)`:''} | ${d.total_customers} customers | ${alerts.length} alerts`,alerts_count:alerts.length}}];"
      }
    },
    {
      "id": "6",
      "name": "Gmail CEO + BCC",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1130,
        200
      ],
      "parameters": {
        "operation": "send",
        "to": "ceo@yourcompany.com",
        "bcc": "vp-quality@yourcompany.com, cto@yourcompany.com, ra-director@yourcompany.com",
        "subject": "={{$json.subject}}",
        "message": "={{$json.html}}",
        "options": {
          "isHtml": true
        }
      }
    },
    {
      "id": "7",
      "name": "Slack #exec-medtech-kpis",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1130,
        400
      ],
      "parameters": {
        "channel": "#exec-medtech-kpis",
        "text": "={{$json.slack_summary}}"
      }
    }
  ],
  "connections": {
    "Monday 8 AM": {
      "main": [
        [
          {
            "node": "Fetch Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Business Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Platform Metrics": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Business Metrics": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Gmail CEO + BCC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack #exec-medtech-kpis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What it does: Monday 8 AM: pulls platform metrics (total customers, LARGE/MID breakdown, active this week, avg open NCRs, customers with expired §820.70(i) validation, integrations offline, open vigilance events) and business metrics (MRR with WoW %, churn risk count, new customers, overdue regulatory deadlines) from two Postgres views. Builds conditional alert flags with regulatory citations. Subject line includes [MEDTECH KPI — N ALERTS] when flags are present. Emails CEO with BCC to VP Quality, CTO, and RA Director. Posts one-liner to Slack #exec-medtech-kpis.

Key vendor angle: The CEO of a MedTech SaaS company should know every Monday morning how many customer eQMS integrations are offline, how many customers have expired 21 CFR §820.70(i) validation status, and how many open vigilance events carry MDR or IVDR deadlines. Those aren't just operational metrics — they're churn and compliance liability signals that predict both customer risk and regulatory exposure.


Self-Hosted n8n vs Zapier/Make for MedTech SaaS: 6 Decision Points

Consideration Zapier/Make Self-Hosted n8n
FDA 21 CFR §820.180 record retention 30-day auto-delete = audit finding Retain indefinitely in your Postgres
ISO 13485 §4.2.5 record retrievability Task log deleted after 30 days Full execution log in your environment
ISO 13485 §7.4 supplier qualification Zapier standard terms insufficient Self-hosted = no third party to qualify
MDR EU Art. 87 vigilance audit trail No traceable chain to NCA submission Git-versioned JSON = traceable evidence
IEC 62304 §9.8 anomaly audit trail Zapier task history not audit-evidence Each workflow run = IEC 62304-compatible log
MDSAP multi-country evidence No country-specific routing Code node routes per FDA/HC/ANVISA/TGA/MHLW rules

Get the Complete MedTech Automation Kit

The workflows above are a starting point. The FlowKit store has production-ready n8n templates for QMS automation, regulatory tracking, and MedTech customer success operations — including CAPA management, complaint handling workflows, and ISO 13485 internal audit schedulers.

Browse FlowKit n8n Templates →


Questions? Drop them in the comments — I read every one.

Top comments (0)