Your clinical operations SaaS manages EDC (electronic data capture) for 43 active clinical trials.
On April 25, 2025 — the effective date for ICH GCP E6(R3) — the ICH guidance updated the requirements for electronic source data, audit trails in EDC systems, and risk-based monitoring. Your CRO customers started asking whether your audit trail meets the new E6(R3) requirements for audit trail completeness, data integrity, and computer system validation.
Your platform also runs on FDA 21 CFR Part 11 for electronic records and electronic signatures. If Part 11 audit trail entries are missing, truncated, or not attributable to an authorized user, every dataset from every trial using your platform is potentially inadmissible in an NDA submission. That is not a hypothetical — FDA Warning Letters citing Part 11 violations routinely result in clinical holds.
The same platform likely also tracks:
- FDA 21 CFR Part 820 QSR — Quality System Regulation for medical device manufacturers; design controls, CAPA, device history records (DHR) — transitioning to ISO 13485-aligned QSR in 2026
- EU MDR 2017/745 Post-Market Surveillance — Annual periodic safety update report (PSUR) for Class IIa/IIb, summary of safety and clinical performance (SSCP) for Class III/implantables
- FDA PDUFA milestones — Prescription Drug User Fee Act action dates, 6-month standard/priority review clocks, refuse-to-file (RTF) 60-day window
- FDA Breakthrough Device 6-week response — §3051 of the 21st Century Cures Act; FDA must respond within 6 weeks to Breakthrough Device designation requests
- FDA 483 inspection response — 15 business-day response to each observation; Warning Letter escalation after non-response
- GDPR Art.9 — health data as special category personal data requiring explicit consent and specific safeguards for CRO data processing
n8n handles all of it — self-hosted, inside your VPC, with Git-versioned audit trails that FDA inspectors can follow.
Why BioTech/PharmaTech SaaS Needs Self-Hosted Automation
Clinical and regulatory data is uniquely regulated:
- 21 CFR Part 11 audit trails — FDA requires attribution of every record creation, modification, or deletion to the authorized individual; cloud automation vendor logs may not satisfy this standard
- HIPAA BAA for CROs — every automation tool that touches patient data in a trial is a business associate; Zapier/Make are not HIPAA BAAs for regulated clinical data
- FDA 483 response document chain — every draft, revision, and final submission must be version-controlled with timestamp; cloud automation pipeline logs are not FDA-ready
- EU MDR PSUR/SSCP — Notified Body review of PMS data expects complete traceability of data sources; vendor retention policy vs. MDR 10-year record retention requirement
A Zapier/Make stack processing EDC data, adverse event reports, or CAPA records creates a third-party custodian problem for every FDA inspection. n8n self-hosted keeps all clinical and regulatory data inside your infrastructure.
Customer Tiers & Compliance Profile
| Tier | Customer Type | Key Regulations |
|---|---|---|
| ENTERPRISE_PHARMA_SAAS | Large pharma/CRO EDC platforms (100+ trials) | FDA 21 CFR Part 11, ICH GCP E6(R3), PDUFA, GDPR Art.9, HIPAA BAA |
| CRO_EDC_SAAS | Contract research organization data capture | ICH GCP E6(R3), 21 CFR Part 11, FDA 483, GDPR Art.9 HIPAA |
| REGULATORY_AFFAIRS_SAAS | eCTD submission, regulatory strategy platforms | FDA PDUFA milestones, EU MDR/IVDR, EMA CHMP, 21st Century Cures Act |
| CLINICAL_OPERATIONS_SAAS | Site management, patient recruitment, monitoring | ICH GCP E6(R3) risk-based monitoring, FDA 483, GDPR Art.46 transfers |
| BIOTECH_QMS_SAAS | Quality management systems for biotech/devices | FDA 21 CFR Part 820 QSR, ISO 13485:2016, EU MDR PSUR, FDA CAPA 21 CFR Part 820.100 |
| MEDICAL_DEVICE_SAAS | Device design, DHR, PMS for Class II/III | EU MDR 2017/745 PSUR/SSCP, FDA 510(k)/PMA, QSR, IVDR 2017/746 |
| BIOTECH_STARTUP | Seed-stage biotech/device company | FDA pre-submission meeting, IND application, ICH GCP basics, GDPR Art.9 |
Workflow 1: FDA 21 CFR Part 11 Electronic Records Audit Trail Monitor
Who it's for: EDC vendors, CTMS platforms, eTMF systems, lab information management (LIMS) SaaS
What it automates: Continuously monitors audit trail completeness for Part 11 compliance — checks for missing required fields (user ID, date/time stamp, reason for change, previous value), flags records modified without electronic signature, detects audit trail gaps, and generates compliance status reports.
Why it matters: FDA 21 CFR Part 11.10(e) requires that audit trails include the date and time of operator entries and actions that create, modify, or delete electronic records — and the change must capture the prior value. ICH GCP E6(R3) §5.5.3 requires audit trails to be protected from deletion. Missing audit trail entries = inadmissible dataset = potential clinical hold.
{
"name": "FDA 21 CFR Part 11 Audit Trail Monitor",
"nodes": [
{
"name": "Every Hour",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 * * * *"
}
]
}
},
"position": [
200,
300
]
},
{
"name": "Query Audit Trail Gaps",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT record_id, table_name, user_id, action_type, changed_at, previous_value, new_value, reason_for_change, electronic_signature_id FROM audit_trail WHERE changed_at >= NOW() - INTERVAL '1 hour' AND (user_id IS NULL OR changed_at IS NULL OR (action_type IN ('UPDATE','DELETE') AND previous_value IS NULL) OR (action_type IN ('UPDATE','DELETE') AND reason_for_change IS NULL)) ORDER BY changed_at DESC"
},
"position": [
400,
300
]
},
{
"name": "Classify Part 11 Violations",
"type": "n8n-nodes-base.code",
"parameters": {
"language": "javascript",
"jsCode": "const records = $input.all().map(i => i.json);\nconst PART11_REQUIREMENTS = {\n MISSING_USER_ID: { citation: '21 CFR \u00a711.10(e)', description: 'Audit trail entry missing user ID \u2014 not attributable to authorized individual', severity: 'CRITICAL', consequence: 'Record not Part 11 compliant \u2014 potentially inadmissible in NDA/BLA submission' },\n MISSING_TIMESTAMP: { citation: '21 CFR \u00a711.10(e)', description: 'Audit trail entry missing date/time stamp', severity: 'CRITICAL', consequence: 'Cannot establish chronological sequence of events \u2014 FDA 483 observation' },\n MISSING_PRIOR_VALUE: { citation: '21 CFR \u00a711.10(e), ICH GCP E6(R3) \u00a75.5.3', description: 'Record modification does not capture previous value', severity: 'HIGH', consequence: 'Cannot reconstruct original data \u2014 ICH GCP E6(R3) audit trail completeness failure' },\n MISSING_REASON_FOR_CHANGE: { citation: '21 CFR \u00a711.10(e)', description: 'Data change without documented reason for change', severity: 'HIGH', consequence: 'GCP violation \u2014 sponsor's data integrity procedures require documented reason for any post-entry edit' },\n UNSIGNED_CRITICAL_RECORD: { citation: '21 CFR \u00a711.50, ICH GCP E6(R3) \u00a75.5.2', description: 'Critical record (protocol deviation, AE, or primary endpoint) lacks electronic signature', severity: 'HIGH', consequence: 'E-signature not bound to record \u2014 Part 11 \u00a711.50 requires e-sig to be part of the human readable form' }\n};\nconst violations = [];\nfor (const rec of records) {\n if (!rec.user_id) violations.push({ ...PART11_REQUIREMENTS.MISSING_USER_ID, record_id: rec.record_id, table: rec.table_name, changed_at: rec.changed_at });\n if (!rec.changed_at) violations.push({ ...PART11_REQUIREMENTS.MISSING_TIMESTAMP, record_id: rec.record_id, table: rec.table_name });\n if (['UPDATE','DELETE'].includes(rec.action_type) && !rec.previous_value) violations.push({ ...PART11_REQUIREMENTS.MISSING_PRIOR_VALUE, record_id: rec.record_id, table: rec.table_name, user: rec.user_id, changed_at: rec.changed_at });\n if (['UPDATE','DELETE'].includes(rec.action_type) && !rec.reason_for_change) violations.push({ ...PART11_REQUIREMENTS.MISSING_REASON_FOR_CHANGE, record_id: rec.record_id, table: rec.table_name, user: rec.user_id });\n if (!rec.electronic_signature_id && ['adverse_events','protocol_deviations','primary_endpoints'].includes(rec.table_name)) violations.push({ ...PART11_REQUIREMENTS.UNSIGNED_CRITICAL_RECORD, record_id: rec.record_id, table: rec.table_name, user: rec.user_id });\n}\nreturn violations.length > 0 ? violations.map(v => ({ json: v })) : [{ json: { no_violations: true, checked_at: new Date().toISOString() } }];"
},
"position": [
600,
300
]
},
{
"name": "Violations Found?",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{!!$json.severity}}",
"operation": "equal",
"value2": true
}
]
}
},
"position": [
800,
300
]
},
{
"name": "Alert #part11-violations Slack",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#part11-violations",
"text": "={{$json.severity}} PART 11 VIOLATION \u2014 Table: {{$json.table}} | Record: {{$json.record_id}} | {{$json.citation}}: {{$json.description}} | Consequence: {{$json.consequence}}"
},
"position": [
1000,
200
]
},
{
"name": "Log to Compliance DB",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "part11_violation_log",
"columns": "record_id, table_name, violation_type, severity, citation, description, consequence, detected_at",
"values": "='{{$json.record_id}}', '{{$json.table}}', '{{$json.description}}', '{{$json.severity}}', '{{$json.citation}}', '{{$json.description}}', '{{$json.consequence}}', NOW()"
},
"position": [
1000,
400
]
}
],
"connections": {
"Every Hour": {
"main": [
[
{
"node": "Query Audit Trail Gaps",
"type": "main",
"index": 0
}
]
]
},
"Query Audit Trail Gaps": {
"main": [
[
{
"node": "Classify Part 11 Violations",
"type": "main",
"index": 0
}
]
]
},
"Classify Part 11 Violations": {
"main": [
[
{
"node": "Violations Found?",
"type": "main",
"index": 0
}
]
]
},
"Violations Found?": {
"main": [
[
{
"node": "Alert #part11-violations Slack",
"type": "main",
"index": 0
}
],
[]
]
},
"Alert #part11-violations Slack": {
"main": [
[
{
"node": "Log to Compliance DB",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 2: FDA 483 Inspection Response Tracker — 15-Business-Day Clock
Who it's for: QMS SaaS vendors, regulatory affairs platforms, biotech/device company compliance portals
What it automates: Tracks every FDA 483 observation from the moment of receipt, computes the 15-business-day response deadline (excluding federal holidays), sends escalating alerts at 10/12/14/15 business days, routes draft responses through an internal review queue, and logs final submission timestamps for audit trail.
Why it matters: 21 CFR Part 820 and FDA guidance state that failure to respond to a 483 within 15 business days typically triggers escalation to a Warning Letter. A Warning Letter is public record — it appears on FDA's website within 15 business days of issuance. For a SaaS company serving regulated customers, an unresolved Warning Letter on the FDA website can kill enterprise deals instantly.
{
"name": "FDA 483 Inspection Response Tracker",
"nodes": [
{
"name": "Webhook \u2014 New 483 Observation",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "fda-483-observation",
"httpMethod": "POST"
},
"position": [
200,
300
]
},
{
"name": "Compute 15-Business-Day Deadline",
"type": "n8n-nodes-base.code",
"parameters": {
"language": "javascript",
"jsCode": "const obs = $json;\nconst FDA_FEDERAL_HOLIDAYS_2026 = [\n '2026-01-01','2026-01-19','2026-02-16','2026-05-25','2026-06-19',\n '2026-07-03','2026-09-07','2026-10-12','2026-11-11','2026-11-26','2026-12-25'\n];\nfunction addBusinessDays(startDate, days) {\n let d = new Date(startDate);\n let added = 0;\n while (added < days) {\n d.setDate(d.getDate() + 1);\n const iso = d.toISOString().split('T')[0];\n const dow = d.getDay();\n if (dow !== 0 && dow !== 6 && !FDA_FEDERAL_HOLIDAYS_2026.includes(iso)) added++;\n }\n return d.toISOString().split('T')[0];\n}\nconst receivedDate = obs.received_date || new Date().toISOString().split('T')[0];\nconst deadline15 = addBusinessDays(receivedDate, 15);\nconst alert10 = addBusinessDays(receivedDate, 10);\nconst alert12 = addBusinessDays(receivedDate, 12);\nconst alert14 = addBusinessDays(receivedDate, 14);\nreturn [{ json: {\n observation_id: obs.observation_id,\n observation_text: obs.observation_text,\n inspection_id: obs.inspection_id,\n facility: obs.facility_name,\n received_date: receivedDate,\n deadline_15biz: deadline15,\n alert_10biz: alert10,\n alert_12biz: alert12,\n alert_14biz: alert14,\n responsible_person: obs.responsible_person,\n citation: '21 CFR Part 820, FDA Regulatory Procedures Manual Chapter 4 \u2014 Warning Letter escalation after 15-biz-day non-response',\n consequence: 'FDA Warning Letter public record within 15 biz days of issue \u2014 enterprise deals at risk',\n status: 'OPEN'\n} }];"
},
"position": [
400,
300
]
},
{
"name": "Save to 483 Tracking DB",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "fda_483_observations",
"columns": "observation_id, inspection_id, facility, observation_text, received_date, deadline_15biz, alert_10biz, alert_12biz, alert_14biz, responsible_person, status, created_at",
"values": "='{{$json.observation_id}}', '{{$json.inspection_id}}', '{{$json.facility}}', '{{$json.observation_text}}', '{{$json.received_date}}', '{{$json.deadline_15biz}}', '{{$json.alert_10biz}}', '{{$json.alert_12biz}}', '{{$json.alert_14biz}}', '{{$json.responsible_person}}', 'OPEN', NOW()"
},
"position": [
600,
300
]
},
{
"name": "Notify Responsible Person",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{$json.responsible_person}}",
"subject": "FDA 483 Observation Received \u2014 Response due {{$json.deadline_15biz}} (15 business days)",
"message": "FDA 483 Observation ID {{$json.observation_id}} received {{$json.received_date}}. Response required by {{$json.deadline_15biz}} (15 business days). Observation: {{$json.observation_text}}. Citation: {{$json.citation}}. Consequence: {{$json.consequence}}."
},
"position": [
800,
300
]
},
{
"name": "Respond 200",
"type": "n8n-nodes-base.respondToWebhook",
"parameters": {
"responseCode": 200,
"responseBody": "={\"received\":true,\"deadline\":\"{{$json.deadline_15biz}}\"}"
},
"position": [
1000,
300
]
}
],
"connections": {
"Webhook \u2014 New 483 Observation": {
"main": [
[
{
"node": "Compute 15-Business-Day Deadline",
"type": "main",
"index": 0
}
]
]
},
"Compute 15-Business-Day Deadline": {
"main": [
[
{
"node": "Save to 483 Tracking DB",
"type": "main",
"index": 0
}
]
]
},
"Save to 483 Tracking DB": {
"main": [
[
{
"node": "Notify Responsible Person",
"type": "main",
"index": 0
}
]
]
},
"Notify Responsible Person": {
"main": [
[
{
"node": "Respond 200",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 3: EU MDR 2017/745 Post-Market Surveillance (PMS) Annual Report Pipeline
Who it's for: Medical device SaaS vendors, QMS platforms for Class IIa/IIb/III device manufacturers, Notified Body integration platforms
What it automates: Tracks EU MDR PSUR (Periodic Safety Update Report) annual deadlines per device class, aggregates PMS data from field complaints, vigilance reports, and literature surveillance into a structured PSUR draft, and sends the draft to the Notified Body submission queue 30 days before the deadline.
Why it matters: EU MDR 2017/745 Art.86 requires Class IIa/IIb manufacturers to submit PSUR at least annually. Class III and implantables: PSUR updated when needed and at least every year — plus SSCP (Summary of Safety and Clinical Performance) updated annually for Class III. Missing PSUR = Notified Body may suspend CE marking. No CE marking = device cannot be sold in EU. For a SaaS company serving device manufacturers, missed PSUR deadlines at a customer = customer loses CE marking = customer loses you.
{
"name": "EU MDR 2017/745 PMS Annual Report Pipeline",
"nodes": [
{
"name": "Daily 9AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * *"
}
]
}
},
"position": [
200,
300
]
},
{
"name": "Load Device PMS Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "readRows",
"sheetId": "={{$vars.MDR_DEVICE_REGISTRY_SHEET_ID}}",
"range": "PMS_Deadlines!A:J",
"headerRow": true
},
"position": [
400,
300
]
},
{
"name": "Evaluate MDR Deadlines",
"type": "n8n-nodes-base.code",
"parameters": {
"language": "javascript",
"jsCode": "const today = new Date();\nconst rows = $input.all().map(i => i.json);\nconst MDR_REQUIREMENTS = {\n CLASS_IIA_PSUR: { citation: 'EU MDR 2017/745 Art.86(2)', description: 'Class IIa PSUR \u2014 annual minimum, Notified Body on request', consequence: 'Notified Body may suspend or restrict CE certificate until PSUR submitted' },\n CLASS_IIB_PSUR: { citation: 'EU MDR 2017/745 Art.86(3)', description: 'Class IIb PSUR \u2014 at least annual, submitted proactively to Notified Body', consequence: 'CE marking suspension risk; Notified Body audit trigger' },\n CLASS_III_PSUR: { citation: 'EU MDR 2017/745 Art.86(4)', description: 'Class III PSUR \u2014 at least annual, proactively submitted to Notified Body + EUDAMED upload', consequence: 'CE marking suspension; EUDAMED non-compliance; competent authority escalation' },\n CLASS_III_SSCP: { citation: 'EU MDR 2017/745 Art.32', description: 'Class III Summary of Safety and Clinical Performance \u2014 annual update on EUDAMED', consequence: 'EUDAMED non-compliance \u2014 competent authority may suspend market access' },\n IMPLANTABLE_SSCP: { citation: 'EU MDR 2017/745 Art.32', description: 'Implantable device SSCP \u2014 annual update, publicly available on EUDAMED', consequence: 'Public SSCP non-compliance \u2014 competent authority investigation' },\n VIGILANCE_TREND_ANALYSIS: { citation: 'EU MDR 2017/745 Art.87(1), MDCG 2023-3', description: 'Annual trend analysis of vigilance events for statistically significant increases', consequence: 'FSCA (Field Safety Corrective Action) may be required retroactively if trend identified late' },\n IVDR_ANNUAL_PSUR: { citation: 'EU IVDR 2017/746 Art.81', description: 'Class C/D IVD PSUR \u2014 annual minimum', consequence: 'CE marking for IVD suspended if PSUR overdue; EUDAMED non-compliance' }\n};\nconst alerts = [];\nfor (const row of rows) {\n const deadline = new Date(row.psur_deadline);\n const daysUntil = Math.floor((deadline - today) / 86400000);\n const meta = MDR_REQUIREMENTS[row.requirement_type] || { citation: row.regulation, description: row.description, consequence: row.consequence };\n let urgency;\n if (daysUntil < 0) urgency = 'OVERDUE';\n else if (daysUntil <= 14) urgency = 'CRITICAL';\n else if (daysUntil <= 30) urgency = 'URGENT';\n else if (daysUntil <= 60) urgency = 'WARNING';\n else if (daysUntil <= 90) urgency = 'NOTICE';\n else continue;\n alerts.push({ ...row, ...meta, urgency, daysUntil, deadlineDisplay: deadline.toISOString().split('T')[0] });\n}\nreturn alerts.map(a => ({ json: a }));"
},
"position": [
600,
300
]
},
{
"name": "Alerts?",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"number": [
{
"value1": "={{$items().length}}",
"operation": "larger",
"value2": 0
}
]
}
},
"position": [
800,
300
]
},
{
"name": "Slack #mdr-pms-deadlines",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#mdr-pms-deadlines",
"text": "={{$json.urgency}} \u2014 {{$json.device_name}} ({{$json.requirement_type}}) \u2014 due {{$json.deadlineDisplay}} ({{$json.daysUntil}}d) | {{$json.citation}}: {{$json.consequence}}"
},
"position": [
1000,
200
]
},
{
"name": "Email Regulatory Affairs",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{$json.regulatory_affairs_email}}",
"subject": "={{$json.urgency}}: {{$json.device_name}} \u2014 {{$json.requirement_type}} due {{$json.deadlineDisplay}}",
"message": "={{$json.description}} | {{$json.citation}} | Consequence: {{$json.consequence}}"
},
"position": [
1000,
400
]
}
],
"connections": {
"Daily 9AM": {
"main": [
[
{
"node": "Load Device PMS Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Load Device PMS Deadlines": {
"main": [
[
{
"node": "Evaluate MDR Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Evaluate MDR Deadlines": {
"main": [
[
{
"node": "Alerts?",
"type": "main",
"index": 0
}
]
]
},
"Alerts?": {
"main": [
[
{
"node": "Slack #mdr-pms-deadlines",
"type": "main",
"index": 0
}
],
[]
]
},
"Slack #mdr-pms-deadlines": {
"main": [
[
{
"node": "Email Regulatory Affairs",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 4: FDA PDUFA Milestone & Breakthrough Device 6-Week Response Tracker
Who it's for: Regulatory affairs SaaS, pharma pipeline tracking platforms, biotech investor portals
What it automates: Tracks FDA PDUFA action dates (standard 10-month, priority 6-month review clocks), Refuse-to-File (RTF) 60-day window from NDA/BLA filing, Breakthrough Device designation 6-week response window under 21st Century Cures Act §3051, and Accelerated Approval confirmatory trial milestone deadlines.
Why it matters: PDUFA action dates are contractual FDA commitments under the user fee program — missing one triggers Congressional scrutiny. A missed RTF window (60 days from receipt date) means the NDA/BLA is in review — you cannot fix submission deficiencies without a complete response cycle. Breakthrough Device response at 6 weeks is not a hard deadline but FDA tracks non-response and it affects subsequent interaction scheduling.
{
"name": "FDA PDUFA Milestone & Breakthrough Device Tracker",
"nodes": [
{
"name": "Daily 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * *"
}
]
}
},
"position": [
200,
300
]
},
{
"name": "Load FDA Regulatory Milestones",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "readRows",
"sheetId": "={{$vars.FDA_PIPELINE_SHEET_ID}}",
"range": "PDUFA_Milestones!A:K",
"headerRow": true
},
"position": [
400,
300
]
},
{
"name": "Evaluate FDA Milestones",
"type": "n8n-nodes-base.code",
"parameters": {
"language": "javascript",
"jsCode": "const today = new Date();\nconst rows = $input.all().map(i => i.json);\nconst FDA_MILESTONE_TYPES = {\n PDUFA_STANDARD_ACTION: { citation: 'PDUFA VII (FY2023-2027) commitment letter', description: 'Standard review NDA/BLA action date \u2014 10 months from receipt date', consequence: 'FDA exceeding PDUFA commitment triggers Congressional notification; approval delay metrics published in annual PDUFA report' },\n PDUFA_PRIORITY_ACTION: { citation: 'PDUFA VII priority review \u2014 6 months from receipt', description: 'Priority review action date \u2014 6 months from receipt (disease that is serious or life-threatening with unmet medical need)', consequence: 'Priority review slip is public \u2014 stock impact, patient advocacy escalation' },\n RTF_WINDOW: { citation: '21 CFR \u00a7314.101(b), \u00a7601.2 \u2014 60-day RTF window', description: 'Refuse-to-File 60-day clock from NDA/BLA filing date \u2014 FDA must issue RTF or begin review', consequence: 'After 60 days: submission is in review \u2014 major submission deficiencies require complete response cycle (6-12 months additional)' },\n BREAKTHROUGH_DEVICE_RESPONSE: { citation: '21st Century Cures Act \u00a73051, 21 CFR \u00a7515B', description: 'Breakthrough Device designation request \u2014 FDA responds within 6 weeks of receipt', consequence: 'Designation enables interactive review, senior FDA staff, manufacturing advice during review \u2014 delay in designation = no fast-track benefits' },\n ACCELERATED_APPROVAL_CONFIRMATORY: { citation: 'FDA Omnibus Reform Act 2022 \u00a7506b \u2014 confirmatory trial milestone', description: 'Post-market confirmatory trial enrollment/completion milestone for accelerated approval', consequence: 'FDA can withdraw accelerated approval administratively if confirmatory trial milestone missed \u2014 market withdrawal without full Advisory Committee' },\n TYPE_A_MEETING: { citation: '21 CFR \u00a7312.47, FDA PDUFA VII commitment', description: 'Type A meeting \u2014 30-day FDA response from receipt of meeting request', consequence: 'Type A meeting supports clinical holds, Complete Response Letters \u2014 delay affects trial restart or resubmission timeline' },\n NDA_RESUBMISSION_CLASS1: { citation: '21 CFR \u00a7314.110 \u2014 Class 1 resubmission: 2-month action date', description: 'Class 1 Complete Response resubmission \u2014 FDA action within 2 months', consequence: 'Class 1 resubmission covers minor deficiencies; Class 2 (6-month) if misclassified = 4-month delay surprise' },\n NDA_RESUBMISSION_CLASS2: { citation: '21 CFR \u00a7314.110 \u2014 Class 2 resubmission: 6-month action date', description: 'Class 2 Complete Response resubmission \u2014 FDA action within 6 months', consequence: '6-month clock starts from resubmission date \u2014 additional data package must be complete or FDA may issue another CRL' }\n};\nconst alerts = [];\nfor (const row of rows) {\n const deadline = new Date(row.milestone_date);\n const daysUntil = Math.floor((deadline - today) / 86400000);\n const meta = FDA_MILESTONE_TYPES[row.milestone_type] || { citation: row.regulation, description: row.description, consequence: row.consequence };\n let urgency;\n if (daysUntil < 0) urgency = 'OVERDUE';\n else if (daysUntil <= 7) urgency = 'CRITICAL';\n else if (daysUntil <= 14) urgency = 'URGENT';\n else if (daysUntil <= 30) urgency = 'WARNING';\n else if (daysUntil <= 60) urgency = 'NOTICE';\n else continue;\n alerts.push({ ...row, ...meta, urgency, daysUntil, deadlineDisplay: deadline.toISOString().split('T')[0] });\n}\nreturn alerts.map(a => ({ json: a }));"
},
"position": [
600,
300
]
},
{
"name": "Alerts Found?",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"number": [
{
"value1": "={{$items().length}}",
"operation": "larger",
"value2": 0
}
]
}
},
"position": [
800,
300
]
},
{
"name": "Slack #fda-milestones",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#fda-milestones",
"text": "={{$json.urgency}} \u2014 {{$json.product_name}} \u2014 {{$json.milestone_type}} due {{$json.deadlineDisplay}} ({{$json.daysUntil}}d) | {{$json.citation}}: {{$json.consequence}}"
},
"position": [
1000,
200
]
},
{
"name": "Email Regulatory Lead",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{$json.regulatory_lead_email}}",
"subject": "={{$json.urgency}}: {{$json.product_name}} \u2014 {{$json.milestone_type}} due {{$json.deadlineDisplay}}",
"message": "={{$json.description}} | {{$json.citation}} | Consequence: {{$json.consequence}}"
},
"position": [
1000,
400
]
}
],
"connections": {
"Daily 7AM": {
"main": [
[
{
"node": "Load FDA Regulatory Milestones",
"type": "main",
"index": 0
}
]
]
},
"Load FDA Regulatory Milestones": {
"main": [
[
{
"node": "Evaluate FDA Milestones",
"type": "main",
"index": 0
}
]
]
},
"Evaluate FDA Milestones": {
"main": [
[
{
"node": "Alerts Found?",
"type": "main",
"index": 0
}
]
]
},
"Alerts Found?": {
"main": [
[
{
"node": "Slack #fda-milestones",
"type": "main",
"index": 0
}
],
[]
]
},
"Slack #fda-milestones": {
"main": [
[
{
"node": "Email Regulatory Lead",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 5: Weekly BioTech/Pharma Compliance & Regulatory KPI Dashboard
Who it's for: Enterprise pharma/biotech SaaS operations and compliance teams
What it automates: Every Monday morning, pulls compliance data from Postgres (Part 11 violations, 483 observations open/closed, PDUFA milestones within 30 days, MDR PSUR deadlines within 60 days) and generates a color-coded HTML KPI dashboard for VP Regulatory, CCO, and QA Director — BCC to compliance-officer@company.com intentional for 21 CFR Part 820.180 quality record retention.
{
"name": "Weekly BioTech/Pharma Compliance KPI Dashboard",
"nodes": [
{
"name": "Monday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1"
}
]
}
},
"position": [
200,
300
]
},
{
"name": "Query Part 11 Violations",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) FILTER (WHERE severity='CRITICAL') as critical_7d, COUNT(*) FILTER (WHERE severity='HIGH') as high_7d, COUNT(*) as total_7d FROM part11_violation_log WHERE detected_at >= NOW() - INTERVAL '7 days'"
},
"position": [
400,
200
]
},
{
"name": "Query 483 Open Observations",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) as open_483, COUNT(*) FILTER (WHERE deadline_15biz <= CURRENT_DATE + INTERVAL '3 days') as critical_deadline FROM fda_483_observations WHERE status='OPEN'"
},
"position": [
400,
400
]
},
{
"name": "Merge KPI Data",
"type": "n8n-nodes-base.merge",
"parameters": {
"mode": "mergeByIndex"
},
"position": [
600,
300
]
},
{
"name": "Build Pharma KPI Report",
"type": "n8n-nodes-base.code",
"parameters": {
"language": "javascript",
"jsCode": "const part11 = $input.first().json;\nconst obs = $input.last().json;\nconst p11Critical = parseInt(part11.critical_7d || 0);\nconst p11High = parseInt(part11.high_7d || 0);\nconst open483 = parseInt(obs.open_483 || 0);\nconst critical483 = parseInt(obs.critical_deadline || 0);\nlet ragStatus = 'GREEN'; let ragColor = '#16a766';\nif (p11Critical > 0 || critical483 > 0) { ragStatus = 'RED'; ragColor = '#fb4c2f'; }\nelse if (p11High > 0 || open483 > 0) { ragStatus = 'AMBER'; ragColor = '#ffad47'; }\nconst html = '<div style=\"font-family:sans-serif;max-width:700px\">'\n + '<h2 style=\"background:' + ragColor + ';color:white;padding:12px\">BioTech/Pharma Compliance Dashboard \u2014 Week of ' + new Date().toISOString().split('T')[0] + ' \u2014 ' + ragStatus + '</h2>'\n + '<table style=\"width:100%;border-collapse:collapse\">'\n + '<tr style=\"background:#f3f3f3\"><th>Metric</th><th>Value</th><th>Regulation</th></tr>'\n + '<tr><td>Part 11 Critical Violations (7d)</td><td style=\"color:' + (p11Critical>0?'#fb4c2f':'#16a766') + '\">' + p11Critical + '</td><td>21 CFR \u00a711.10(e), ICH GCP E6(R3) \u00a75.5.3</td></tr>'\n + '<tr><td>Part 11 High Violations (7d)</td><td style=\"color:' + (p11High>0?'#ffad47':'#16a766') + '\">' + p11High + '</td><td>21 CFR \u00a711.50 e-signature binding</td></tr>'\n + '<tr><td>Open FDA 483 Observations</td><td style=\"color:' + (open483>0?'#ffad47':'#16a766') + '\">' + open483 + '</td><td>21 CFR Part 820, FDA RPM Chapter 4 Warning Letter escalation</td></tr>'\n + '<tr><td>483s with Deadline \u2264 3 Days</td><td style=\"color:' + (critical483>0?'#fb4c2f':'#16a766') + '\">' + critical483 + '</td><td>15-biz-day response window \u2014 Warning Letter risk</td></tr>'\n + '</table>'\n + '<p style=\"font-size:11px;color:#666\">BCC: compliance-officer@company.com \u2014 intentional for 21 CFR Part 820.180 quality record retention and FDA audit trail requirements. Self-hosted n8n: all Part 11 audit trail data, 483 responses, and MDR PSUR records remain inside VPC. Cloud automation vendor custody of Part 11 records expands FDA inspection scope.</p>'\n + '</div>';\nreturn [{ json: { html, ragStatus, p11Critical, p11High, open483, critical483 } }];"
},
"position": [
800,
300
]
},
{
"name": "Email VP Regulatory + CCO",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "vp-regulatory@company.com",
"cc": "cco@company.com",
"bcc": "compliance-officer@company.com",
"subject": "=[{{$json.ragStatus}}] BioTech/Pharma Weekly Compliance \u2014 Part 11: {{$json.p11Critical}} critical | Open 483s: {{$json.open483}}",
"message": "={{$json.html}}",
"messageType": "html"
},
"position": [
1000,
300
]
}
],
"connections": {
"Monday 7AM": {
"main": [
[
{
"node": "Query Part 11 Violations",
"type": "main",
"index": 0
},
{
"node": "Query 483 Open Observations",
"type": "main",
"index": 0
}
]
]
},
"Query Part 11 Violations": {
"main": [
[
{
"node": "Merge KPI Data",
"type": "main",
"index": 0
}
]
]
},
"Query 483 Open Observations": {
"main": [
[
{
"node": "Merge KPI Data",
"type": "main",
"index": 1
}
]
]
},
"Merge KPI Data": {
"main": [
[
{
"node": "Build Pharma KPI Report",
"type": "main",
"index": 0
}
]
]
},
"Build Pharma KPI Report": {
"main": [
[
{
"node": "Email VP Regulatory + CCO",
"type": "main",
"index": 0
}
]
]
}
}
}
The Self-Hosting Argument for BioTech/PharmaTech SaaS
Why regulated clinical and regulatory data cannot safely transit cloud automation vendors:
| Data Type | Regulation | Cloud Automation Risk |
|---|---|---|
| 21 CFR Part 11 audit trail records | FDA audit trail completeness — must be attributable to authorized individual | Cloud vendor logs may not satisfy §11.10(e) attribution requirement; FDA inspection scope expands |
| FDA 483 response drafts | FDA RPM Chapter 4 — 15-biz-day response; internal version history critical | Cloud vendor retention policy vs. FDA audit trail of response drafts |
| ICH GCP E6(R3) clinical trial data | §5.5.3 audit trail protection from deletion | Cloud vendor data lifecycle management conflicts with E6(R3) immutability requirement |
| EU MDR PSUR/SSCP | MDR 2017/745 — 10-year record retention, Notified Body access | Vendor retention policy (30-90 days) vs. 10-year MDR requirement — irreconcilable |
| GDPR Art.9 health data | Special category data — explicit consent + specific safeguards required for each processing | Cloud iPaaS as processor requires GDPR Art.28 DPA; Art.9 special category DPIA required |
| HIPAA PHI in CRO data | HIPAA BAA required for every business associate processing PHI | Zapier/Make are not HIPAA BAAs for regulated clinical data |
n8n self-hosted gives you:
- Part 11 audit trail inside your VPC — FDA inspection scope does not expand to cloud vendor
- 483 response version history in Git — complete draft-to-submission audit trail
- ICH GCP E6(R3) data integrity: audit trail protected from deletion by access controls, not vendor policy
- EU MDR 10-year record retention: Postgres inside your infrastructure, not subject to vendor 90-day purge
Get All 5 Workflows — Free
All 5 workflow JSONs are included in the FlowKit n8n Template Bundle — 15 production-ready workflows for $97 at stripeai.gumroad.com.
Or grab the BioTech/Pharma-specific workflows individually:
- Webhook to Database — $12 (use for Part 11 audit trail pipeline)
- AI Customer Support Bot — $29 (adapt for regulatory inquiry routing)
- Daily Report Generator — $19 (adapt for PDUFA milestone reports)
Need something custom? Drop a comment below or reach me at flowkithq.substack.com.
n8n is MIT-licensed. Self-host for free — docs.n8n.io













