{
  "id": "JZ8C3IFRtHrntshw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "File Processing Pipeline with Email and GitHub",
  "tags": [],
  "nodes": [
    {
      "id": "ef26b9a6-1831-46e6-bcab-181075c3a072",
      "name": "Workflow Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        208
      ],
      "parameters": {
        "width": 550,
        "height": 706,
        "content": "## How it works\nThis workflow runs on a fixed schedule and checks an external endpoint for any newly-uploaded files that need processing.  If files exist, each one is downloaded in turn and its type is inspected.  CSV files are parsed inside a Code node and the resulting records are validated against simple business rules.  Valid data are committed to a GitHub repository, while any problems (missing fields, wrong file type, or empty job queue) trigger an error-handling branch.  After every run the appropriate success or failure email is issued so stakeholders always know the outcome.\n\n## Setup steps\n1. Create an HTTP endpoint that returns a JSON array of pending files (id, url, fileName, mimeType).\n2. Add GitHub OAuth credentials in n8n and replace the owner/repo placeholders.\n3. Configure an SMTP credential for the Email Send node and update the to/from addresses.\n4. Adjust the schedule trigger interval to suit your cadence.\n5. Edit validation rules inside the **Validate Data** Code node as needed.\n6. (Optional) Tweak commit paths, branch names or email templates for your environment."
      },
      "typeVersion": 1
    },
    {
      "id": "7b77f257-297a-4c42-b211-f3923de8c071",
      "name": "Trigger & Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 994,
        "height": 654,
        "content": "## Trigger & Retrieval\nThis section contains the **Schedule Trigger** that launches the workflow and the HTTP Request that fetches the list of files waiting to be processed.  A preliminary Code node counts the files so the following IF node can decide whether to continue or exit early.  If no files are returned we immediately branch to the notification path so stakeholders know the run had nothing to do.  Keeping retrieval logic isolated here makes it easier to swap in an S3 or SharePoint source later without touching the downstream processing chain."
      },
      "typeVersion": 1
    },
    {
      "id": "e57d8c88-feed-49e8-85d1-6d7a74cc246c",
      "name": "Processing & Validation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 1026,
        "height": 654,
        "content": "## Processing & Validation\nNodes in this cluster handle per-file work.  Files arrive one at a time via **SplitInBatches**, get downloaded, then routed by an IF node that checks MIME/type.  CSVs are parsed in **Parse CSV** and examined in **Validate Data** for required columns, data types and any custom business rules you add.  Failures are piped straight to the error-email branch, while clean data move forward to storage.  This modular approach lets you bolt on extra transforms (enrichment, filtering, aggregation) without breaking existing logic."
      },
      "typeVersion": 1
    },
    {
      "id": "f1c7b100-a7bc-47f2-8bfa-43c5ec84d1f6",
      "name": "Storage & Notification",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2576,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 994,
        "height": 622,
        "content": "## Storage & Notification\nAfter a record set passes validation, it is wrapped in JSON, committed to GitHub and the success branch crafts a confirmation email.  Any failure along the way jumps to an alternate Set+Email pair so the operations team receives actionable information.  Because commit metadata, email subject and body are assembled in dedicated **Set** nodes, you can change wording, recipients or file-naming conventions in one place without touching upstream logic."
      },
      "typeVersion": 1
    },
    {
      "id": "267a08d7-e5dd-4beb-8e7e-53f71655aab2",
      "name": "Daily File Check",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        512,
        576
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "45b00107-991d-410a-a9c1-b1d454ac6d38",
      "name": "Fetch File List",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        704,
        576
      ],
      "parameters": {
        "url": "https://api.example.com/uploads/pending",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "acf76dcf-9922-4e18-a640-d18b8143eb61",
      "name": "Check File Count",
      "type": "n8n-nodes-base.code",
      "position": [
        912,
        576
      ],
      "parameters": {
        "jsCode": "// Expect an object like { files: [...] }\nconst files = $json.files || [];\nreturn [{ json: { files, fileCount: files.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f55e226c-09e7-40df-a0d2-032e4b8325bd",
      "name": "Any New Files?",
      "type": "n8n-nodes-base.if",
      "position": [
        1104,
        576
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.fileCount }}",
              "value2": 0,
              "operation": "larger"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "5e90a2c9-8a61-4158-9d0e-ccbbaf3671a8",
      "name": "Prepare File Items",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        576
      ],
      "parameters": {
        "jsCode": "// Expand array into individual items\nreturn ($json.files || []).map(f => ({ json: f }));"
      },
      "typeVersion": 2
    },
    {
      "id": "eab73259-410f-40ae-add9-d0ab535dc08b",
      "name": "Iterate Files",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1504,
        576
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "3a5a7674-e38e-4325-8d49-889ec1f43ad5",
      "name": "Download File",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1712,
        576
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "2c1cc809-e708-4a24-b792-ef052ac88897",
      "name": "Is CSV?",
      "type": "n8n-nodes-base.if",
      "position": [
        1904,
        576
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.mimeType || $json.type || 'unknown' }}",
              "value2": "text/csv",
              "operation": "equals"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2b67be02-6092-4309-8f8f-2106caeb58fe",
      "name": "Parse CSV",
      "type": "n8n-nodes-base.code",
      "position": [
        2112,
        576
      ],
      "parameters": {
        "jsCode": "// Simple CSV parser (no external deps)\nconst csvText = $json.body;\nconst lines = csvText.trim().split(/\\r?\\n/);\nconst headers = lines.shift().split(',');\nconst records = lines.map(line => {\n  const cols = line.split(',');\n  const obj = {};\n  headers.forEach((h, i) => { obj[h.trim()] = (cols[i] || '').trim(); });\n  return obj;\n});\nreturn [{ json: { originalFileName: $json.fileName, records } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "e775790b-55c1-4835-aab4-2ccef039a327",
      "name": "Validate Data",
      "type": "n8n-nodes-base.code",
      "position": [
        2304,
        576
      ],
      "parameters": {
        "jsCode": "const items = $input.item.records;\nconst invalid = items.filter(r => !r.id || !r.id.length);\nreturn [{ json: {\n  originalFileName: $input.item.originalFileName,\n  data: items,\n  isValid: invalid.length === 0,\n  errors: invalid\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bdfd326d-0806-44b8-bab3-5cc1b3a29426",
      "name": "Validation Passed?",
      "type": "n8n-nodes-base.if",
      "position": [
        2656,
        576
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.isValid }}",
              "operation": "isTrue"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "77243b3b-8b6f-403b-abac-1900ca662af1",
      "name": "Prepare GitHub Commit",
      "type": "n8n-nodes-base.set",
      "position": [
        2832,
        496
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "9595ba2f-a213-4bc6-9813-8b3e83bbb226",
      "name": "Create/Update File",
      "type": "n8n-nodes-base.github",
      "position": [
        2992,
        480
      ],
      "parameters": {
        "owner": "{{YOUR_GITHUB_USERNAME}}",
        "labels": [],
        "assignees": [],
        "repository": "{{YOUR_REPOSITORY_NAME}}"
      },
      "typeVersion": 1
    },
    {
      "id": "d7468b8b-c647-411a-9a6f-8f65ebff11d8",
      "name": "Success Email Content",
      "type": "n8n-nodes-base.set",
      "position": [
        3152,
        512
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "814955dc-d483-470d-af17-a8feb058678c",
      "name": "Send Success Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3360,
        544
      ],
      "parameters": {
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "={{ $json.toEmail }}",
        "fromEmail": "={{ $json.fromEmail }}"
      },
      "typeVersion": 2
    },
    {
      "id": "94405ec6-a93c-412c-8a2b-f1973ca4b650",
      "name": "Error Email Content",
      "type": "n8n-nodes-base.set",
      "position": [
        2768,
        784
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "cc483eaf-cce5-4232-8769-269f296f03d1",
      "name": "Send Error Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3040,
        784
      ],
      "parameters": {
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "={{ $json.toEmail }}",
        "fromEmail": "={{ $json.fromEmail }}"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7e62b283-e4ce-4942-9ba2-1f78c04280ae",
  "connections": {
    "Is CSV?": {
      "main": [
        [
          {
            "node": "Parse CSV",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse CSV": {
      "main": [
        [
          {
            "node": "Validate Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download File": {
      "main": [
        [
          {
            "node": "Is CSV?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Iterate Files": {
      "main": [
        [
          {
            "node": "Download File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Data": {
      "main": [
        [
          {
            "node": "Validation Passed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any New Files?": {
      "main": [
        [
          {
            "node": "Prepare File Items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch File List": {
      "main": [
        [
          {
            "node": "Check File Count",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check File Count": {
      "main": [
        [
          {
            "node": "Any New Files?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily File Check": {
      "main": [
        [
          {
            "node": "Fetch File List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create/Update File": {
      "main": [
        [
          {
            "node": "Success Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare File Items": {
      "main": [
        [
          {
            "node": "Iterate Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validation Passed?": {
      "main": [
        [
          {
            "node": "Prepare GitHub Commit",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Error Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Email Content": {
      "main": [
        [
          {
            "node": "Send Error Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare GitHub Commit": {
      "main": [
        [
          {
            "node": "Create/Update File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Success Email Content": {
      "main": [
        [
          {
            "node": "Send Success Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}