AutomationFlowsAI & RAG › Analyze Contract Risk From Pdfs with Openai, Supabase and Slack Alerts

Analyze Contract Risk From Pdfs with Openai, Supabase and Slack Alerts

Byben daamer @ahmedbendaamer on n8n.io

Legal, Procurement, and Compliance teams at mid-size companies. ESN and agencies selling AI-powered contract review as a service.

Event trigger★★★★★ complexity31 nodesForm TriggerHTTP RequestFormSupabaseError TriggerSlackGmail
AI & RAG Trigger: Event Nodes: 31 Complexity: ★★★★★ Added:

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

This workflow follows the Error Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "8a78e62f-28c0-4d24-9a33-521acd836974",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        1552
      ],
      "parameters": {
        "color": 5,
        "width": 560,
        "height": 520,
        "content": "## Enterprise AI Contract Intelligence - Pro\n\n**Who it's for:** Legal, Procurement, Compliance teams. ESN/agencies.\n\n**What it does:**\n1. Upload contract PDF via form (with metadata: type, department, counterparty)\n2. Deduplication check against Supabase\n3. AI Pass 1 - Classification (parties, dates, jurisdiction, contract type)\n4. AI Pass 2 - Deep risk analysis (clauses, red flags, obligations, recommendations)\n5. Build structured report (Slack Blocks + HTML email)\n6. Store in Supabase with full metadata\n7. Slack Block Kit alert (high risk) or summary (low risk)\n8. Gmail HTML report to submitter (configurable)\n9. Error handling with admin Slack channel\n\n**Setup:** Run the SQL in the \"Supabase Schema\" sticky. Add credentials: OpenAI (Header Auth), Supabase, Slack, Gmail. Configure the Config node."
      },
      "typeVersion": 1
    },
    {
      "id": "54d26b31-109f-4508-aaeb-0ecbd3bea3d6",
      "name": "Supabase Schema",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        896
      ],
      "parameters": {
        "color": 6,
        "width": 564,
        "height": 620,
        "content": "**Supabase Schema** \u2014 Run in Supabase SQL Editor before use:\n\n```sql\nCREATE TABLE IF NOT EXISTS contract_analyses (\n  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,\n  filename TEXT NOT NULL,\n  contract_type TEXT,\n  jurisdiction TEXT,\n  submitter_name TEXT,\n  submitter_email TEXT,\n  department TEXT,\n  counterparty TEXT,\n  raw_text_preview TEXT,\n  parties JSONB,\n  key_clauses JSONB,\n  overall_risk_score INT DEFAULT 0,\n  risk_level TEXT DEFAULT 'unknown',\n  executive_summary TEXT,\n  top_risks JSONB,\n  recommendations JSONB,\n  classification JSONB,\n  status TEXT DEFAULT 'analyzed'\n    CHECK (status IN ('analyzed','flagged','archived','reviewing')),\n  analyzed_at TIMESTAMPTZ DEFAULT NOW(),\n  created_at TIMESTAMPTZ DEFAULT NOW()\n);\nCREATE INDEX IF NOT EXISTS idx_ca_score\n  ON contract_analyses(overall_risk_score DESC);\nCREATE INDEX IF NOT EXISTS idx_ca_status\n  ON contract_analyses(status);\nCREATE INDEX IF NOT EXISTS idx_ca_type\n  ON contract_analyses(contract_type);\nCREATE INDEX IF NOT EXISTS idx_ca_created\n  ON contract_analyses(created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_ca_filename\n  ON contract_analyses(filename);\n```"
      },
      "typeVersion": 1
    },
    {
      "id": "87dfd4af-c94e-4d18-b51f-0b3c56cf7072",
      "name": "Step 1 Ingest",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2640,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 140,
        "content": "**Step 1 - Ingest**\n\nForm upload with metadata (contract type, submitter, department, counterparty) or Manual test with sample data. Config node centralizes all settings."
      },
      "typeVersion": 1
    },
    {
      "id": "e63d0723-0341-43a0-b15b-f48d78178052",
      "name": "Step 2 Deduplicate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2976,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 140,
        "content": "**Step 2 - Deduplicate**\n\nQuery Supabase for existing analysis with the same filename. If found, skip analysis and show previous result. Avoids wasting AI tokens on re-uploads."
      },
      "typeVersion": 1
    },
    {
      "id": "f658564e-c7a3-4808-a59e-0815adbcceba",
      "name": "Step 3 Extract",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3312,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 300,
        "height": 140,
        "content": "**Step 3 - Extract & Prepare**\n\nExtract text from PDF binary, normalize, truncate to 12k chars, merge with form metadata and config for downstream AI calls."
      },
      "typeVersion": 1
    },
    {
      "id": "a8abaca7-fffe-4268-a7fd-4b8f54fd3086",
      "name": "Step 4 AI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3696,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 340,
        "content": "**Step 4 - AI Two-Pass Analysis**\n\nPass 1: Quick classification (type, parties, dates, jurisdiction, ~300 tokens).\nPass 2: Deep risk analysis (clauses, obligations, red flags, recommendations, ~2000 tokens).\nSplit for cost efficiency and structured output."
      },
      "typeVersion": 1
    },
    {
      "id": "aa16c460-b7aa-4b7f-9a75-fd05b74287c9",
      "name": "Step 5 Report Alert",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4144,
        1552
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "content": "**Step 5 - Report, Store & Alert**\n\nBuild Report generates Slack Block Kit JSON + HTML email body.\nSupabase insert stores full analysis with metadata.\nHigh risk \u2192 Slack Blocks alert + Gmail HTML report.\nLow risk \u2192 Slack summary notification.\nForm Ending shows completion to user."
      },
      "typeVersion": 1
    },
    {
      "id": "cb0e27e8-f26e-436d-a474-f10c699dccb1",
      "name": "Error Handling Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4832,
        1552
      ],
      "parameters": {
        "color": 2,
        "width": 320,
        "height": 120,
        "content": "**Error Handling**\n\nError Trigger catches any workflow failure and sends a notification to the admin Slack channel (#n8n-errors). Includes workflow name, error message, and timestamp."
      },
      "typeVersion": 1
    },
    {
      "id": "2dc1a40d-d75a-43c6-a19f-ac60f5b6a88a",
      "name": "Upload Contract",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        2736,
        1856
      ],
      "parameters": {
        "options": {},
        "formTitle": "Contract AI Analysis \u2014 Upload & Analyze",
        "formFields": {
          "values": [
            {
              "fieldType": "file",
              "fieldLabel": "Contract PDF",
              "requiredField": true,
              "acceptFileTypes": ".pdf,.docx"
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Contract Type",
              "fieldOptions": {
                "values": [
                  {
                    "option": "NDA"
                  },
                  {
                    "option": "MSA"
                  },
                  {
                    "option": "SOW / Statement of Work"
                  },
                  {
                    "option": "Employment"
                  },
                  {
                    "option": "SaaS / License"
                  },
                  {
                    "option": "Consulting"
                  },
                  {
                    "option": "Partnership"
                  },
                  {
                    "option": "Other"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldLabel": "Contract Name",
              "placeholder": "e.g. NDA - Acme Corp Q1 2025",
              "requiredField": true
            },
            {
              "fieldLabel": "Counterparty Name",
              "placeholder": "e.g. Acme Corporation",
              "requiredField": true
            },
            {
              "fieldLabel": "Your Name",
              "placeholder": "e.g. John Doe",
              "requiredField": true
            },
            {
              "fieldType": "email",
              "fieldLabel": "Your Email",
              "placeholder": "user@example.com",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Department",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Legal"
                  },
                  {
                    "option": "Procurement"
                  },
                  {
                    "option": "Finance"
                  },
                  {
                    "option": "Sales"
                  },
                  {
                    "option": "Engineering"
                  },
                  {
                    "option": "HR"
                  },
                  {
                    "option": "Operations"
                  },
                  {
                    "option": "Other"
                  }
                ]
              }
            }
          ]
        },
        "formDescription": "Upload a contract PDF for AI-powered analysis. The system will extract key clauses, assess risk, and provide actionable recommendations."
      },
      "typeVersion": 2.2
    },
    {
      "id": "536c0506-43b9-4869-9e66-182864bf5d63",
      "name": "Manual Test",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        2736,
        2160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5d3ff4a9-4d75-4f35-b767-84c8f7f680f7",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "position": [
        2736,
        2000
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "RISK_THRESHOLD",
              "type": "number",
              "value": 70
            },
            {
              "id": "2",
              "name": "SLACK_CHANNEL",
              "type": "string",
              "value": "#legal-alerts"
            },
            {
              "id": "3",
              "name": "ADMIN_SLACK_CHANNEL",
              "type": "string",
              "value": "#n8n-errors"
            },
            {
              "id": "4",
              "name": "AI_MODEL",
              "type": "string",
              "value": "gpt-4o-mini"
            },
            {
              "id": "5",
              "name": "ALERT_EMAIL",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "6",
              "name": "ENABLE_EMAIL",
              "type": "boolean",
              "value": true
            },
            {
              "id": "7",
              "name": "CONTRACT_LANG",
              "type": "string",
              "value": "en"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "5003aa1e-ea7e-46c0-8e1c-0a3844f2c25c",
      "name": "Check Duplicate",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        3040,
        2000
      ],
      "parameters": {
        "url": "={{ $env.SUPABASE_URL }}/rest/v1/contract_analyses",
        "options": {
          "response": {
            "response": {}
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "filename",
              "value": "=eq.{{ $json.filename ?? $json['Contract Name'] ?? 'unknown' }}"
            },
            {
              "name": "select",
              "value": "id,filename,overall_risk_score,status,analyzed_at"
            },
            {
              "name": "limit",
              "value": "1"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.SUPABASE_SERVICE_KEY }}"
            },
            {
              "name": "Prefer",
              "value": "return=representation"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a0749143-735f-4443-b85f-6748c8a36c7d",
      "name": "Already Exists?",
      "type": "n8n-nodes-base.if",
      "position": [
        3264,
        2000
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ Array.isArray($json) ? $json.length : 0 }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d6f15504-861a-48c9-b078-4158d17bb5d7",
      "name": "Already Analyzed",
      "type": "n8n-nodes-base.form",
      "position": [
        3472,
        1920
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "Already Analyzed",
        "completionMessage": "This contract has already been analyzed. Check Supabase for the existing report."
      },
      "typeVersion": 2.4
    },
    {
      "id": "1a67b439-5764-4fce-a3ef-5f4f28f70211",
      "name": "Has File?",
      "type": "n8n-nodes-base.if",
      "position": [
        3472,
        2096
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ ($binary || {}).data !== undefined }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "3352c639-8fbe-40c9-ae9b-30faa9c8b083",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        3696,
        2016
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1.1
    },
    {
      "id": "9b475384-b8d7-480a-a249-607a16d68cfb",
      "name": "Sample Data",
      "type": "n8n-nodes-base.set",
      "position": [
        3696,
        2192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "contractText",
              "type": "string",
              "value": "SAMPLE CONTRACT FOR TESTING\n\nThis Master Service Agreement (MSA) is entered into as of January 15, 2025, by and between:\n\nParty A: TechCorp International Ltd., a company incorporated under the laws of France, with registered office at 42 Avenue des Champs-\u00c9lys\u00e9es, 75008 Paris (\"Client\")\n\nParty B: CloudVendor Solutions Inc., a Delaware corporation, with principal office at 100 Market Street, San Francisco, CA 94105 (\"Vendor\")\n\n1. TERM: This Agreement shall commence on the Effective Date and continue for a period of thirty-six (36) months, automatically renewing for successive 12-month periods unless terminated.\n\n2. TERMINATION: Either party may terminate with 30 days written notice. Client may terminate for convenience with 15 days notice. Early termination fee: 50% of remaining contract value.\n\n3. LIABILITY: Vendor's total aggregate liability shall not exceed the fees paid in the 12 months preceding the claim. EXCLUSION: This cap does NOT apply to breaches of confidentiality, IP infringement, or gross negligence.\n\n4. INDEMNIFICATION: Vendor shall indemnify, defend, and hold harmless Client against all third-party claims arising from: (a) IP infringement, (b) data breaches caused by Vendor negligence, (c) violation of applicable law. Client indemnifies Vendor against claims arising from Client's misuse of services.\n\n5. INTELLECTUAL PROPERTY: All pre-existing IP remains with original owner. Work product created specifically for Client shall be owned by Client upon full payment. Vendor retains license to use general methodologies and tools.\n\n6. CONFIDENTIALITY: 5-year obligation post-termination. Covers all non-public business and technical information. Standard exceptions apply (public domain, independent development, legal compulsion).\n\n7. DATA PROTECTION: Vendor shall comply with GDPR and applicable data protection laws. DPA attached as Annex B. Sub-processors require prior written consent.\n\n8. WARRANTY: Vendor warrants services will be performed in a professional manner consistent with industry standards. 30-day cure period for material defects.\n\n9. GOVERNING LAW: This Agreement shall be governed by French law. Disputes shall be resolved by the Commercial Court of Paris.\n\n10. FORCE MAJEURE: Neither party liable for delays due to events beyond reasonable control, provided affected party notifies within 5 business days."
            },
            {
              "id": "2",
              "name": "filename",
              "type": "string",
              "value": "sample-msa-techcorp.pdf"
            },
            {
              "id": "3",
              "name": "Contract Type",
              "type": "string",
              "value": "MSA"
            },
            {
              "id": "4",
              "name": "Counterparty Name",
              "type": "string",
              "value": "CloudVendor Solutions Inc."
            },
            {
              "id": "5",
              "name": "Your Name",
              "type": "string",
              "value": "Ahmed Test"
            },
            {
              "id": "6",
              "name": "Your Email",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "7",
              "name": "Department",
              "type": "string",
              "value": "Legal"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "06ff5e07-5631-4d13-998f-976be80288b5",
      "name": "Prepare Text",
      "type": "n8n-nodes-base.code",
      "position": [
        3920,
        2096
      ],
      "parameters": {
        "jsCode": "const item = $input.first();\nconst raw = item.json?.contractText ?? item.json?.text ?? item.json?.data ?? '';\nconst pages = item.json?.pages;\nlet fullText = raw;\nif (Array.isArray(pages)) fullText = pages.map(p => p?.text ?? p).filter(Boolean).join('\\n\\n');\nif (!fullText) {\n  const found = Object.values(item.json).find(v => typeof v === 'string' && v.length > 100);\n  if (found) fullText = found;\n}\n\nconst config = $('Config').first()?.json ?? {};\nconst formData = item.json;\nconst contractText = String(fullText).slice(0, 12000);\nconst contractTextShort = contractText.substring(0, 4000);\n\nreturn { json: {\n  ...config,\n  contractText,\n  contractTextSafe: JSON.stringify(contractText).slice(1, -1),\n  contractTextShortSafe: JSON.stringify(contractTextShort).slice(1, -1),\n  charCount: String(fullText).length,\n  filename: formData['Contract Name'] ?? formData.filename ?? 'contract.pdf',\n  contract_type: formData['Contract Type'] ?? 'Other',\n  counterparty: formData['Counterparty Name'] ?? '',\n  submitter_name: formData['Your Name'] ?? '',\n  submitter_email: formData['Your Email'] ?? '',\n  department: formData['Department'] ?? ''\n} };"
      },
      "typeVersion": 2
    },
    {
      "id": "b6bc0ed1-4325-4d2a-8a81-ebf730dc9fac",
      "name": "Parse Classification",
      "type": "n8n-nodes-base.code",
      "position": [
        4384,
        2096
      ],
      "parameters": {
        "jsCode": "const resp = $input.first().json;\nconst raw = resp.choices?.[0]?.message?.content ?? '{}';\nlet classification = {};\ntry {\n  classification = JSON.parse(raw.replace(/```json?/g,'').replace(/```/g,'').trim());\n} catch(e) {\n  classification = { contract_type: 'Unknown', parties: [], jurisdiction: 'Unknown' };\n}\nconst prev = $('Prepare Text').first()?.json ?? {};\nreturn { json: { ...prev, classification } };"
      },
      "typeVersion": 2
    },
    {
      "id": "eebe98e1-641f-4930-ae9a-0005ea1b1ed2",
      "name": "Parse Risk Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        4816,
        2096
      ],
      "parameters": {
        "jsCode": "const resp = $input.first().json;\nconst raw = resp.choices?.[0]?.message?.content ?? '{}';\nlet analysis = {};\ntry {\n  analysis = JSON.parse(raw.replace(/```json?/g,'').replace(/```/g,'').trim());\n} catch(e) {\n  analysis = { overall_risk_score: 50, risk_level: 'MEDIUM', executive_summary: 'AI parse error \u2014 manual review recommended', key_clauses: [], top_risks: ['Parse error'] };\n}\n\nconst prev = $('Parse Classification').first()?.json ?? {};\nconst score = Math.min(100, Math.max(0, analysis.overall_risk_score ?? 50));\nlet riskLevel = analysis.risk_level ?? 'MEDIUM';\nif (score >= 85) riskLevel = 'CRITICAL';\nelse if (score >= 70) riskLevel = 'HIGH';\nelse if (score >= 40) riskLevel = 'MEDIUM';\nelse riskLevel = 'LOW';\n\nreturn { json: {\n  ...prev,\n  ...analysis,\n  overall_risk_score: score,\n  risk_level: riskLevel,\n  key_clauses: analysis.key_clauses ?? [],\n  top_risks: analysis.top_risks ?? [],\n  missing_clauses: analysis.missing_clauses ?? [],\n  obligations: analysis.obligations ?? [],\n  negotiation_points: analysis.negotiation_points ?? [],\n  compliance_flags: analysis.compliance_flags ?? [],\n  raw_text_preview: (prev.contractText || '').slice(0, 500),\n  status: score >= (prev.RISK_THRESHOLD ?? 70) ? 'flagged' : 'analyzed'\n} };"
      },
      "typeVersion": 2
    },
    {
      "id": "db2f0f88-2472-4d28-97e2-a6ab518c7996",
      "name": "Build Report",
      "type": "n8n-nodes-base.code",
      "position": [
        5040,
        2096
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json;\nconst score = d.overall_risk_score;\nconst level = d.risk_level;\nconst clauses = (d.key_clauses || []).slice(0, 5);\nconst topRisks = (d.top_risks || []).slice(0, 5);\nconst missing = (d.missing_clauses || []).slice(0, 3);\nconst negotiations = (d.negotiation_points || []).slice(0, 3);\nconst compliance = (d.compliance_flags || []).slice(0, 3);\n\nconst emoji = level === 'CRITICAL' ? '\ud83d\udd34' : level === 'HIGH' ? '\ud83d\udfe0' : level === 'MEDIUM' ? '\ud83d\udfe1' : '\ud83d\udfe2';\nconst color = level === 'CRITICAL' ? '#dc2626' : level === 'HIGH' ? '#ea580c' : level === 'MEDIUM' ? '#ca8a04' : '#16a34a';\n\nconst slackBlocks = [\n  { type: 'header', text: { type: 'plain_text', text: `${emoji} Contract Analysis: ${d.filename}` } },\n  { type: 'section', text: { type: 'mrkdwn', text: `*Risk Score:* ${score}/100 \u2014 *${level}*\\n*Type:* ${d.classification?.contract_type ?? d.contract_type ?? 'N/A'} | *Jurisdiction:* ${d.classification?.jurisdiction ?? 'N/A'}\\n*Parties:* ${(d.classification?.parties ?? []).map(p => `${p.name} (${p.role})`).join(', ') || 'N/A'}\\n*Counterparty:* ${d.counterparty || 'N/A'} | *Submitted by:* ${d.submitter_name || 'N/A'} (${d.department || 'N/A'})` } },\n  { type: 'divider' },\n  { type: 'section', text: { type: 'mrkdwn', text: `*Executive Summary*\\n${d.executive_summary || 'N/A'}` } }\n];\n\nif (topRisks.length > 0) {\n  slackBlocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Top Risks*\\n${topRisks.map((r, i) => `${i+1}. ${r}`).join('\\n')}` } });\n}\nif (clauses.length > 0) {\n  slackBlocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Key Clauses*\\n${clauses.map(c => `\u2022 *${c.clause_type}* [${(c.risk_level || '').toUpperCase()}]: ${c.risk_explanation || c.excerpt || ''}`).join('\\n')}` } });\n}\nif (missing.length > 0) {\n  slackBlocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Missing Clauses*\\n${missing.map(m => `\u26a0\ufe0f ${m}`).join('\\n')}` } });\n}\nif (negotiations.length > 0) {\n  slackBlocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Negotiation Points*\\n${negotiations.map(n => `\ud83d\udca1 ${n}`).join('\\n')}` } });\n}\n\nslackBlocks.push({ type: 'context', elements: [{ type: 'mrkdwn', text: `Analyzed at ${new Date().toISOString()} | Contract Intelligence Pro` }] });\n\nconst clauseRows = clauses.map(c => {\n  const bg = c.risk_level === 'critical' ? '#fecaca' : c.risk_level === 'high' ? '#fed7aa' : c.risk_level === 'medium' ? '#fef08a' : '#bbf7d0';\n  return `<tr style=\"background:${bg}\"><td style=\"padding:8px;border:1px solid #e5e7eb\">${c.clause_type}</td><td style=\"padding:8px;border:1px solid #e5e7eb\">${(c.risk_level||'').toUpperCase()}</td><td style=\"padding:8px;border:1px solid #e5e7eb\">${c.risk_explanation || ''}</td><td style=\"padding:8px;border:1px solid #e5e7eb\">${c.recommendation || ''}</td></tr>`;\n}).join('');\n\nconst emailHtml = `\n<div style=\"font-family:Arial,sans-serif;max-width:700px;margin:0 auto\">\n  <div style=\"background:${color};color:white;padding:20px;border-radius:8px 8px 0 0\">\n    <h1 style=\"margin:0;font-size:22px\">${emoji} Contract Analysis Report</h1>\n    <p style=\"margin:8px 0 0;font-size:16px\">${d.filename} \u2014 Risk Score: ${score}/100 (${level})</p>\n  </div>\n  <div style=\"padding:20px;background:#f9fafb;border:1px solid #e5e7eb\">\n    <table style=\"width:100%;border-collapse:collapse;margin-bottom:16px\">\n      <tr><td style=\"padding:6px 12px;font-weight:bold;width:140px\">Contract Type</td><td style=\"padding:6px 12px\">${d.classification?.contract_type ?? d.contract_type ?? 'N/A'}</td></tr>\n      <tr><td style=\"padding:6px 12px;font-weight:bold\">Counterparty</td><td style=\"padding:6px 12px\">${d.counterparty || 'N/A'}</td></tr>\n      <tr><td style=\"padding:6px 12px;font-weight:bold\">Jurisdiction</td><td style=\"padding:6px 12px\">${d.classification?.jurisdiction ?? 'N/A'}</td></tr>\n      <tr><td style=\"padding:6px 12px;font-weight:bold\">Submitted by</td><td style=\"padding:6px 12px\">${d.submitter_name || 'N/A'} (${d.department || 'N/A'})</td></tr>\n      <tr><td style=\"padding:6px 12px;font-weight:bold\">Parties</td><td style=\"padding:6px 12px\">${(d.classification?.parties ?? []).map(p => `${p.name} (${p.role})`).join(', ') || 'N/A'}</td></tr>\n    </table>\n    <h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Executive Summary</h2>\n    <p style=\"color:#4b5563;line-height:1.6\">${d.executive_summary || 'N/A'}</p>\n    ${topRisks.length > 0 ? `<h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Top Risks</h2><ul style=\"color:#4b5563\">${topRisks.map(r => `<li>${r}</li>`).join('')}</ul>` : ''}\n    ${clauses.length > 0 ? `<h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Key Clauses</h2><table style=\"width:100%;border-collapse:collapse\"><tr style=\"background:#f3f4f6\"><th style=\"padding:8px;border:1px solid #e5e7eb;text-align:left\">Clause</th><th style=\"padding:8px;border:1px solid #e5e7eb;text-align:left\">Risk</th><th style=\"padding:8px;border:1px solid #e5e7eb;text-align:left\">Explanation</th><th style=\"padding:8px;border:1px solid #e5e7eb;text-align:left\">Recommendation</th></tr>${clauseRows}</table>` : ''}\n    ${missing.length > 0 ? `<h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Missing Clauses</h2><ul style=\"color:#dc2626\">${missing.map(m => `<li>${m}</li>`).join('')}</ul>` : ''}\n    ${negotiations.length > 0 ? `<h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Negotiation Points</h2><ul style=\"color:#2563eb\">${negotiations.map(n => `<li>${n}</li>`).join('')}</ul>` : ''}\n    ${compliance.length > 0 ? `<h2 style=\"color:#1f2937;font-size:16px;margin:16px 0 8px\">Compliance Flags</h2><ul style=\"color:#9333ea\">${compliance.map(c => `<li>${c}</li>`).join('')}</ul>` : ''}\n  </div>\n  <div style=\"padding:12px 20px;background:#f3f4f6;border-radius:0 0 8px 8px;text-align:center;color:#9ca3af;font-size:12px\">\n    Contract Intelligence Pro \u2014 Analyzed ${new Date().toISOString().split('T')[0]}\n  </div>\n</div>`;\n\nreturn { json: { ...d, slackBlocks: JSON.stringify(slackBlocks), emailHtml, emailSubject: `${emoji} [${level}] Contract Analysis: ${d.filename} \u2014 Score ${score}/100` } };"
      },
      "typeVersion": 2
    },
    {
      "id": "7f7d0872-3bb8-4480-b748-96722b07ca77",
      "name": "Supabase Insert",
      "type": "n8n-nodes-base.supabase",
      "position": [
        5264,
        2096
      ],
      "parameters": {
        "tableId": "contract_analyses"
      },
      "typeVersion": 1
    },
    {
      "id": "607470fa-deb2-4e4d-9a14-80494cf17b61",
      "name": "High Risk?",
      "type": "n8n-nodes-base.if",
      "position": [
        5472,
        2096
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.overall_risk_score }}",
              "rightValue": "={{ $json.RISK_THRESHOLD ?? 70 }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "c745489a-3292-424d-9d66-c9d10dd9c8cd",
      "name": "Form Ending",
      "type": "n8n-nodes-base.form",
      "position": [
        6144,
        2096
      ],
      "parameters": {
        "options": {},
        "operation": "completion",
        "completionTitle": "={{ 'Analysis Complete \u2014 ' + ($json.risk_level ?? 'N/A') }}",
        "completionMessage": "={{ 'Contract: ' + ($json.filename ?? 'N/A') + '\\nRisk Score: ' + ($json.overall_risk_score ?? 'N/A') + '/100 (' + ($json.risk_level ?? 'N/A') + ')\\nType: ' + ($json.classification?.contract_type ?? $json.contract_type ?? 'N/A') + '\\n\\n' + ($json.executive_summary ?? '') + '\\n\\nFull report stored in Supabase.' + ($json.ENABLE_EMAIL ? ' Email report sent to ' + ($json.submitter_email || $json.ALERT_EMAIL || '') + '.' : '') }}"
      },
      "typeVersion": 2.4
    },
    {
      "id": "45f67023-11d0-4198-a0dd-4172751ce974",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        4944,
        2352
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "a10dab0a-9abd-4fa9-9ea5-8cb4ab4811fd",
      "name": "Slack Admin \u2014 Error",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        5184,
        2352
      ],
      "parameters": {
        "operation": "create"
      },
      "typeVersion": 2.4
    },
    {
      "id": "4fcde5ae-05c7-4c05-ab89-72169fbe7c5a",
      "name": "AI Pass 1 - Classify",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 2,
      "position": [
        4160,
        2096
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"{{ $json.AI_MODEL ?? 'gpt-4o-mini' }}\",\n  \"response_format\": { \"type\": \"json_object\" },\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a legal document classifier. Extract metadata from the contract. Reply ONLY with valid JSON. Language: {{ $json.CONTRACT_LANG ?? 'en' }}.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Classify this contract and return JSON with EXACTLY these fields: { \\\"contract_type\\\": \\\"NDA|MSA|SOW|Employment|SaaS|Consulting|Partnership|Other\\\", \\\"parties\\\": [{\\\"name\\\": string, \\\"role\\\": \\\"client|vendor|employer|employee|partner|other\\\"}], \\\"effective_date\\\": \\\"ISO date or unknown\\\", \\\"expiration_date\\\": \\\"ISO date or unknown\\\", \\\"auto_renewal\\\": boolean, \\\"jurisdiction\\\": \\\"country/region\\\", \\\"governing_law\\\": string, \\\"language\\\": string, \\\"estimated_value\\\": \\\"amount or unknown\\\" }. Submitted as: {{ $json.contract_type }}. Counterparty: {{ $json.counterparty }}.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"{{ $json.contractTextShortSafe }}\"\n    }\n  ],\n  \"temperature\": 0.1,\n  \"max_tokens\": 500\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "97fb58ef-4cc7-4d7d-b0f1-80a915064880",
      "name": "AI Pass 2 - Deep Risk",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 2,
      "position": [
        4592,
        2096
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"model\": \"{{ $json.AI_MODEL ?? 'gpt-4o-mini' }}\",\n  \"response_format\": { \"type\": \"json_object\" },\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a senior legal risk analyst specializing in contract review. Perform a thorough clause-by-clause risk assessment. Reply ONLY with valid JSON. Language: {{ $json.CONTRACT_LANG ?? 'en' }}.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"Perform a deep risk analysis of this {{ $json.classification.contract_type ?? 'contract' }} between {{ ($json.classification.parties ?? []).map(p => p.name).join(' and ') || 'the parties' }}. Jurisdiction: {{ $json.classification.jurisdiction ?? 'Unknown' }}.\\n\\nReturn JSON with EXACTLY these fields:\\n{\\n  \\\"key_clauses\\\": [{ \\\"clause_type\\\": string, \\\"excerpt\\\": \\\"verbatim quote max 100 chars\\\", \\\"risk_level\\\": \\\"critical|high|medium|low\\\", \\\"risk_explanation\\\": string, \\\"recommendation\\\": string, \\\"negotiation_leverage\\\": \\\"strong|moderate|weak\\\" }],\\n  \\\"overall_risk_score\\\": 0-100,\\n  \\\"risk_level\\\": \\\"CRITICAL|HIGH|MEDIUM|LOW\\\",\\n  \\\"executive_summary\\\": \\\"3-5 sentences\\\",\\n  \\\"top_risks\\\": [\\\"concise risk descriptions\\\"],\\n  \\\"missing_clauses\\\": [\\\"important clauses not found in contract\\\"],\\n  \\\"obligations\\\": [{ \\\"party\\\": string, \\\"obligation\\\": string, \\\"deadline\\\": \\\"string or none\\\" }],\\n  \\\"negotiation_points\\\": [\\\"specific actionable suggestions\\\"],\\n  \\\"compliance_flags\\\": [\\\"GDPR/regulatory concerns\\\"]\\n}\\n\\nFocus areas: indemnification, liability caps, termination penalties, IP ownership, data protection, warranty, non-compete, auto-renewal, governing law, force majeure.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"{{ $json.contractTextSafe }}\"\n    }\n  ],\n  \"temperature\": 0.15,\n  \"max_tokens\": 2500\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "3951bc85-a646-4284-93d8-0dd60965d3fd",
      "name": "Slack Alert - High Risk",
      "type": "n8n-nodes-base.slack",
      "position": [
        5696,
        1904
      ],
      "parameters": {
        "operation": "create"
      },
      "typeVersion": 2.4
    },
    {
      "id": "06fbd53b-aba8-4de0-b8ea-2df20c8a7a71",
      "name": "Gmail - Risk Report",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueRegularOutput",
      "position": [
        5696,
        2096
      ],
      "parameters": {
        "sendTo": "={{ $json.ENABLE_EMAIL ? ($json.submitter_email || $json.ALERT_EMAIL || 'legal@company.com') : '' }}",
        "message": "={{ $json.emailHtml }}",
        "options": {},
        "subject": "={{ $json.emailSubject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "971832e8-72b7-4794-bf2f-0e1dacf61ad8",
      "name": "Slack Summary - Low Risk",
      "type": "n8n-nodes-base.slack",
      "position": [
        5696,
        2304
      ],
      "parameters": {
        "operation": "create"
      },
      "typeVersion": 2.4
    }
  ],
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Check Duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has File?": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sample Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "High Risk?": {
      "main": [
        [
          {
            "node": "Slack Alert - High Risk",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail - Risk Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack Summary - Low Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Test": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sample Data": {
      "main": [
        [
          {
            "node": "Prepare Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Report": {
      "main": [
        [
          {
            "node": "Supabase Insert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Text": {
      "main": [
        [
          {
            "node": "AI Pass 1 - Classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Slack Admin \u2014 Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already Exists?": {
      "main": [
        [
          {
            "node": "Already Analyzed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has File?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Duplicate": {
      "main": [
        [
          {
            "node": "Already Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Supabase Insert": {
      "main": [
        [
          {
            "node": "High Risk?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Contract": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Prepare Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Risk Analysis": {
      "main": [
        [
          {
            "node": "Build Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Pass 1 - Classify": {
      "main": [
        [
          {
            "node": "Parse Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Classification": {
      "main": [
        [
          {
            "node": "AI Pass 2 - Deep Risk",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Pass 2 - Deep Risk": {
      "main": [
        [
          {
            "node": "Parse Risk Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert - High Risk": {
      "main": [
        [
          {
            "node": "Form Ending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Summary - Low Risk": {
      "main": [
        [
          {
            "node": "Form Ending",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Legal, Procurement, and Compliance teams at mid-size companies. ESN and agencies selling AI-powered contract review as a service.

Source: https://n8n.io/workflows/13875/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow is ideal for content creators, video marketers, and research professionals who need to extract actionable insights, detailed transcripts, or metadata from YouTube videos efficiently. It

HTTP Request, Google Drive, Gmail +2
AI & RAG

This workflow is an AI-powered lighting and look development pipeline designed for VFX production. It transforms a single lighting brief into multiple high-quality cinematic lighting references using

Form Trigger, HTTP Request, Google Drive +4
AI & RAG

Consultants, agencies, freelancers, and professional service firms who need to create customized proposals and contracts quickly and efficiently.

Google Sheets Trigger, OpenAI, Google Docs +5
AI & RAG

This workflow automates the "speed-to-lead" process for insurance agencies. It instantly triggers an AI voice call when a new lead comes in, qualifies their needs via conversation, and automatically g

Form Trigger, Airtable, HTTP Request +3
AI & RAG

13195 Search Slack For N8N Templates With Openai Tips Google Sheets Cache And Weekly Analytics. Uses slackTrigger, googleSheets, httpRequest, slack. Event-driven trigger; 31 nodes.

Slack Trigger, Google Sheets, HTTP Request +2