{
  "name": "GDPR Data Subject Request Handler",
  "nodes": [
    {
      "id": "sticky-overview", "name": "Overview", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, -500],
      "parameters": { "content": "## GDPR Data Subject Request Handler\nVersion 1.0.0 — Compliance\n\nProcesses inbound GDPR access/erasure requests from Gmail. Classifies the request with AI, queries Supabase (primary DB) and Airtable (CRM), compiles a full data report, sends the response to the data subject, and logs the completion to Google Sheets for audit.\n\nFlow: Gmail Trigger => Classify Request (AI Agent) => Query Supabase => Consolidate DB => Query Airtable => Consolidate CRM => Compile Report (AI Agent) => Access or Erasure? (IF) => Send Access Report / Execute Erasure => Log Audit Trail", "height": 260, "width": 680, "color": 7 }
    },
    {
      "id": "sticky-prereqs", "name": "Prerequisites", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, -220],
      "parameters": { "content": "## Prerequisites\n- Gmail account for incoming DSR emails (Gmail OAuth2 credential)\n- OpenAI API key (GPT-4o)\n- Supabase project with users table (Supabase API credential)\n- Airtable base with a Contacts table (Airtable Personal Access Token)\n- Google Sheets for audit log (Google Sheets OAuth2)\n- Gmail for sending responses (reuse inbox or separate send-only account)", "height": 200, "width": 420, "color": 5 }
    },
    {
      "id": "sticky-setup", "name": "Setup Required", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [200, -220],
      "parameters": { "content": "## Setup Required\n1. Gmail Trigger — connect Gmail OAuth2 credential\n2. Classify DSR Request — connect OpenAI credential\n3. Query Supabase — connect Supabase API credential, update table name 'users'\n4. Query Airtable — connect Airtable credential, replace YOUR_BASE_ID and YOUR_TABLE_NAME\n5. Compile Report — connect OpenAI credential\n6. Send Access Report / Erasure Notice — connect Gmail OAuth2 credential\n7. Log Audit Trail — connect Google Sheets credential, replace YOUR_AUDIT_SHEET_ID", "height": 200, "width": 420, "color": 3 }
    },
    {
      "id": "sticky-howitworks", "name": "How It Works", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, 0],
      "parameters": { "content": "## How It Works\n1. Gmail Trigger fires when a new email arrives\n2. AI Agent classifies: access or erasure, extracts requester and data subject email\n3. Supabase queried for all rows matching data subject email; Code consolidates to 1 item\n4. Airtable CRM queried for matching contact; Code consolidates to 1 item\n5. Second AI Agent compiles all found data into a human-readable GDPR report\n6. IF node routes: access => send report email; erasure => delete from both systems then send confirmation\n7. Audit trail logged to Google Sheets with timestamp, request type, and outcome", "height": 200, "width": 500, "color": 4 }
    },

    {
      "id": "gmail-trigger", "name": "Gmail — DSR Inbox", "type": "n8n-nodes-base.gmailTrigger", "typeVersion": 1.2, "position": [0, 100],
      "parameters": { "pollTimes": { "item": [{ "mode": "everyMinute" }] }, "filters": { "includeSpamTrash": false }, "options": { "downloadAttachments": false } },
      "credentials": { "gmailOAuth2": { "id": "gmail-cred", "name": "Gmail OAuth2" } }
    },

    {
      "id": "sni-classify", "name": "Classify note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [265, -35],
      "parameters": { "content": "AI classifies request type and extracts emails from raw email body", "height": 60, "width": 270 }
    },
    {
      "id": "classify-agent", "name": "Classify DSR Request", "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 1.8, "position": [260, 100],
      "parameters": {
        "agentType": "toolsAgent",
        "promptType": "define",
        "text": "=Classify this GDPR data subject request email and extract all relevant details.\n\nFrom: {{ $json.from }}\nSubject: {{ $json.subject }}\nBody: {{ $json.text || $json.snippet }}\n\nDetermine if this is an access request (subject wants to see their data) or erasure request (subject wants data deleted). Extract the requester email and the data subject email (may differ if sent by a lawyer or agent).",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are a GDPR compliance assistant. Analyse inbound emails to determine if they are data access requests (Article 15) or data erasure requests (Article 17). Extract the requester's email and the data subject email precisely. If the request type is ambiguous, classify as 'unknown'.",
          "returnIntermediateSteps": false
        }
      }
    },
    {
      "id": "classify-openai", "name": "OpenAI — Classify", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "typeVersion": 1.2, "position": [260, 300],
      "parameters": { "model": { "__rl": true, "value": "gpt-4o", "mode": "list" }, "options": { "temperature": 0 } },
      "credentials": { "openAiApi": { "id": "openai-cred", "name": "OpenAI API" } }
    },
    {
      "id": "classify-parser", "name": "DSR Classification Schema", "type": "@n8n/n8n-nodes-langchain.outputParserStructured", "typeVersion": 1.2, "position": [460, 300],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\":\"object\",\"properties\":{\"request_type\":{\"type\":\"string\",\"enum\":[\"access\",\"erasure\",\"unknown\"]},\"requester_email\":{\"type\":\"string\"},\"requester_name\":{\"type\":\"string\"},\"data_subject_email\":{\"type\":\"string\"},\"request_summary\":{\"type\":\"string\"}},\"required\":[\"request_type\",\"requester_email\",\"requester_name\",\"data_subject_email\",\"request_summary\"]}"
      }
    },

    {
      "id": "sni-supabase", "name": "Supabase note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [525, -35],
      "parameters": { "content": "Query users table for all rows matching data subject email", "height": 60, "width": 255 }
    },
    {
      "id": "query-supabase", "name": "Query Supabase", "type": "n8n-nodes-base.supabase", "typeVersion": 1, "position": [520, 100],
      "parameters": {
        "operation": "getAll",
        "tableId": "users",
        "returnAll": true,
        "filters": {
          "conditions": [
            { "keyName": "email", "keyValue": "={{ $json.output.data_subject_email }}", "condition": "eq" }
          ]
        }
      },
      "credentials": { "supabaseApi": { "id": "supabase-cred", "name": "Supabase API" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-consolidate-db", "name": "Consolidate DB note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [785, -35],
      "parameters": { "content": "Merges all Supabase rows into a single item to prevent duplication downstream", "height": 60, "width": 270 }
    },
    {
      "id": "consolidate-db", "name": "Consolidate DB Results", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [780, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst classify = $('Classify DSR Request').first().json.output || {};\nconst dbRecords = rows.filter(r => !r.json.error).map(r => r.json);\nreturn [{ json: {\n  request_type: classify.request_type,\n  requester_email: classify.requester_email,\n  requester_name: classify.requester_name,\n  data_subject_email: classify.data_subject_email,\n  request_summary: classify.request_summary,\n  db_records: dbRecords,\n  db_record_count: dbRecords.length\n}}];"
      }
    },

    {
      "id": "sni-airtable", "name": "Airtable note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1045, -35],
      "parameters": { "content": "Query CRM Contacts table for matching email via filterByFormula", "height": 60, "width": 255 }
    },
    {
      "id": "query-airtable", "name": "Query Airtable CRM", "type": "n8n-nodes-base.airtable", "typeVersion": 2.1, "position": [1040, 100],
      "parameters": {
        "operation": "search",
        "base": { "__rl": true, "value": "YOUR_BASE_ID", "mode": "id" },
        "table": { "__rl": true, "value": "YOUR_TABLE_NAME", "mode": "name" },
        "filterByFormula": "={{ `{Email}='${$json.data_subject_email}'` }}",
        "returnAll": true
      },
      "credentials": { "airtableTokenApi": { "id": "airtable-cred", "name": "Airtable Personal Access Token" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-consolidate-crm", "name": "Consolidate CRM note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1305, -35],
      "parameters": { "content": "Merges all Airtable rows into a single item for the AI agent", "height": 60, "width": 255 }
    },
    {
      "id": "consolidate-crm", "name": "Consolidate CRM Results", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1300, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate DB Results').first().json;\nconst crmRecords = rows.filter(r => !r.json.error).map(r => r.json.fields || r.json);\nreturn [{ json: {\n  ...ctx,\n  crm_records: crmRecords,\n  crm_record_count: crmRecords.length\n}}];"
      }
    },

    {
      "id": "sni-compile", "name": "Compile note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1565, -35],
      "parameters": { "content": "AI compiles all data sources into a legally-compliant GDPR report email", "height": 60, "width": 260 }
    },
    {
      "id": "compile-agent", "name": "Compile Data Report", "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 1.8, "position": [1560, 100],
      "parameters": {
        "agentType": "toolsAgent",
        "promptType": "define",
        "text": "=Compile a comprehensive GDPR data report for the following request.\n\nRequest Type: {{ $json.request_type }}\nData Subject Email: {{ $json.data_subject_email }}\nRequester: {{ $json.requester_name }} <{{ $json.requester_email }}>\nRequest Summary: {{ $json.request_summary }}\n\nDatabase Records Found ({{ $json.db_record_count }}):\n{{ JSON.stringify($json.db_records, null, 2) }}\n\nCRM Records Found ({{ $json.crm_record_count }}):\n{{ JSON.stringify($json.crm_records, null, 2) }}\n\nGenerate an email subject line and full HTML email body. For access requests, list all personal data found. For erasure requests, confirm what data will be deleted.",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are a GDPR Data Protection Officer assistant. Compile accurate data reports for data subjects. For access requests (Article 15), list all personal data found across all systems in a clear, readable HTML table. For erasure requests (Article 17), list the data that will be erased and provide a legally-compliant confirmation. Be precise, professional, and GDPR-compliant. Output valid HTML for the email body.",
          "returnIntermediateSteps": false
        }
      }
    },
    {
      "id": "compile-openai", "name": "OpenAI — Compile", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "typeVersion": 1.2, "position": [1560, 300],
      "parameters": { "model": { "__rl": true, "value": "gpt-4o", "mode": "list" }, "options": { "temperature": 0.2 } },
      "credentials": { "openAiApi": { "id": "openai-cred", "name": "OpenAI API" } }
    },
    {
      "id": "compile-parser", "name": "Report Schema", "type": "@n8n/n8n-nodes-langchain.outputParserStructured", "typeVersion": 1.2, "position": [1760, 300],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\":\"object\",\"properties\":{\"email_subject\":{\"type\":\"string\"},\"email_body_html\":{\"type\":\"string\"},\"data_found\":{\"type\":\"boolean\"},\"systems_checked\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"erasure_scope\":{\"type\":\"string\"}},\"required\":[\"email_subject\",\"email_body_html\",\"data_found\",\"systems_checked\"]}"
      }
    },

    {
      "id": "sni-route", "name": "Route note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1825, -35],
      "parameters": { "content": "Routes to access response path or erasure execution path", "height": 60, "width": 255 }
    },
    {
      "id": "route-request", "name": "Access or Erasure?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [1820, 100],
      "parameters": {
        "conditions": {
          "options": { "typeValidation": "loose" },
          "conditions": [{ "id": "cond-access", "leftValue": "={{ $('Consolidate CRM Results').first().json.request_type }}", "rightValue": "access", "operator": { "type": "string", "operation": "equals" } }],
          "combinator": "and"
        }
      }
    },

    {
      "id": "send-access-report", "name": "Send Access Report", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [2080, -80],
      "parameters": {
        "sendTo": "={{ $('Consolidate CRM Results').first().json.requester_email }}",
        "subject": "={{ $json.output.email_subject }}",
        "emailType": "html",
        "message": "={{ $json.output.email_body_html }}",
        "options": {}
      },
      "credentials": { "gmailOAuth2": { "id": "gmail-cred", "name": "Gmail OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-delete-supabase", "name": "Delete DB note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2085, 205],
      "parameters": { "content": "Deletes data subject records from Supabase (primary DB)", "height": 60, "width": 250 }
    },
    {
      "id": "delete-supabase", "name": "Delete from Supabase", "type": "n8n-nodes-base.supabase", "typeVersion": 1, "position": [2080, 280],
      "parameters": {
        "operation": "delete",
        "tableId": "users",
        "filters": {
          "conditions": [
            { "keyName": "email", "keyValue": "={{ $('Consolidate CRM Results').first().json.data_subject_email }}", "condition": "eq" }
          ]
        }
      },
      "credentials": { "supabaseApi": { "id": "supabase-cred", "name": "Supabase API" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "delete-airtable", "name": "Delete from Airtable", "type": "n8n-nodes-base.airtable", "typeVersion": 2.1, "position": [2340, 280],
      "parameters": {
        "operation": "delete",
        "base": { "__rl": true, "value": "YOUR_BASE_ID", "mode": "id" },
        "table": { "__rl": true, "value": "YOUR_TABLE_NAME", "mode": "name" },
        "id": "={{ $('Consolidate CRM Results').first().json.crm_records[0]?.id || 'no-record' }}"
      },
      "credentials": { "airtableTokenApi": { "id": "airtable-cred", "name": "Airtable Personal Access Token" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "send-erasure-confirm", "name": "Send Erasure Confirmation", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [2600, 280],
      "parameters": {
        "sendTo": "={{ $('Consolidate CRM Results').first().json.requester_email }}",
        "subject": "={{ $('Compile Data Report').first().json.output.email_subject }}",
        "emailType": "html",
        "message": "={{ $('Compile Data Report').first().json.output.email_body_html }}",
        "options": {}
      },
      "credentials": { "gmailOAuth2": { "id": "gmail-cred", "name": "Gmail OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-merge", "name": "Merge note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2865, -35],
      "parameters": { "content": "Merges both paths back to a single flow for audit logging", "height": 60, "width": 260 }
    },
    {
      "id": "merge-paths", "name": "Merge Paths", "type": "n8n-nodes-base.merge", "typeVersion": 3, "position": [2860, 100],
      "parameters": { "mode": "chooseBranch", "chooseBranch": { "mode": "waitForBoth" } }
    },

    {
      "id": "sni-audit", "name": "Audit note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [3125, -35],
      "parameters": { "content": "Logs every DSR to Google Sheets for GDPR-required audit trail", "height": 60, "width": 260 }
    },
    {
      "id": "log-audit", "name": "Log Audit Trail", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [3120, 100],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": { "__rl": true, "value": "YOUR_AUDIT_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "DSR Audit Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp":         "={{ DateTime.now().toISO() }}",
            "Request Type":      "={{ $('Consolidate CRM Results').first().json.request_type }}",
            "Requester Email":   "={{ $('Consolidate CRM Results').first().json.requester_email }}",
            "Data Subject":      "={{ $('Consolidate CRM Results').first().json.data_subject_email }}",
            "DB Records Found":  "={{ $('Consolidate CRM Results').first().json.db_record_count }}",
            "CRM Records Found": "={{ $('Consolidate CRM Results').first().json.crm_record_count }}",
            "Status":            "completed",
            "Summary":           "={{ $('Consolidate CRM Results').first().json.request_summary }}"
          }
        }
      },
      "credentials": { "googleSheetsOAuth2Api": { "id": "gsheets-cred", "name": "Google Sheets OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    }
  ],
  "connections": {
    "Gmail — DSR Inbox":         { "main": [[{ "node": "Classify DSR Request",    "type": "main", "index": 0 }]] },
    "OpenAI — Classify":         { "ai_languageModel": [[{ "node": "Classify DSR Request", "type": "ai_languageModel", "index": 0 }]] },
    "DSR Classification Schema": { "ai_outputParser":  [[{ "node": "Classify DSR Request", "type": "ai_outputParser",  "index": 0 }]] },
    "Classify DSR Request":      { "main": [[{ "node": "Query Supabase",          "type": "main", "index": 0 }]] },
    "Query Supabase":            { "main": [[{ "node": "Consolidate DB Results",  "type": "main", "index": 0 }]] },
    "Consolidate DB Results":    { "main": [[{ "node": "Query Airtable CRM",      "type": "main", "index": 0 }]] },
    "Query Airtable CRM":        { "main": [[{ "node": "Consolidate CRM Results", "type": "main", "index": 0 }]] },
    "Consolidate CRM Results":   { "main": [[{ "node": "Compile Data Report",     "type": "main", "index": 0 }]] },
    "OpenAI — Compile":          { "ai_languageModel": [[{ "node": "Compile Data Report", "type": "ai_languageModel", "index": 0 }]] },
    "Report Schema":             { "ai_outputParser":  [[{ "node": "Compile Data Report", "type": "ai_outputParser",  "index": 0 }]] },
    "Compile Data Report":       { "main": [[{ "node": "Access or Erasure?",      "type": "main", "index": 0 }]] },
    "Access or Erasure?": {
      "main": [
        [{ "node": "Send Access Report",      "type": "main", "index": 0 }],
        [{ "node": "Delete from Supabase",    "type": "main", "index": 0 }]
      ]
    },
    "Send Access Report":        { "main": [[{ "node": "Merge Paths", "type": "main", "index": 0 }]] },
    "Delete from Supabase":      { "main": [[{ "node": "Delete from Airtable",     "type": "main", "index": 0 }]] },
    "Delete from Airtable":      { "main": [[{ "node": "Send Erasure Confirmation","type": "main", "index": 0 }]] },
    "Send Erasure Confirmation": { "main": [[{ "node": "Merge Paths",              "type": "main", "index": 1 }]] },
    "Merge Paths":               { "main": [[{ "node": "Log Audit Trail",           "type": "main", "index": 0 }]] }
  },
  "pinData": {},
  "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner" },
  "staticData": null,
  "tags": ["GDPR", "Compliance", "Gmail", "Supabase", "Airtable", "AI"],
  "triggerCount": 1,
  "active": false
}
