Complete "Lesson 5: Error Handling Strategies and the Error Trigger" first to unlock this lesson.Go to previous lesson

Introduction

By this point in the course, you have branching with IF and Switch nodes, looping with item splitting and batch processing, merging parallel branches, and wrapping risky operations in error handling. This final lesson is about putting all of that together into a single workflow that reflects how real production automations are built.

The goal is not to introduce new nodes. The goal is to show how these patterns compose — how a loop feeds into a merge, how an error handler wraps a loop, and how branching logic determines which path a merged result takes next. The workflow you build here is a practical template you can adapt for your own use cases.

The scenario: a workflow that reads a list of contacts from an external API, enriches each contact by calling a second API, handles failures gracefully, merges the results, and sends a summary report via email.

End-to-end workflow diagram showing 5 stages in sequence: Fetch Contacts → Loop + Enrich (with error branch) → Merge Results → Branch on Success/Partial/Failure → Send Report

Stage 1: Fetching and Splitting Input Data

The workflow starts with a scheduled trigger that fires every morning. The first HTTP Request node calls your contacts API and returns an array of contact objects. Because n8n wraps this in a single item by default, you need to split the array before looping over it.

Use a Code node to split the array into individual items that the rest of the workflow can process one at a time:

javascript
return $input.first().json.contacts.map(contact => ({ json: contact }));

This is the foundation. Every downstream node now receives one contact per item, which means loop-style processing with SplitInBatches or direct per-item HTTP calls will work correctly.

Stage 2: Enriching Each Contact with Error Handling

The enrichment step calls a third-party API for each contact. Third-party APIs fail. They rate-limit, time out, and return unexpected shapes. Wrapping this call in a Try/Catch pattern is non-negotiable for production.

Connect your HTTP Request node (the enrichment call) to an Error Trigger scoped to that node. The success path adds enriched data to the item. The error path adds a fallback structure so the item is never silently dropped:

javascript
// On the error branch — Code node that normalizes the failed item
return [{
json: {
...($input.first().json),
enriched: false,
enrichmentError: $input.first().json.error?.message ?? 'Unknown error'
}
}];

Both the success path and the error path must produce items with the same top-level shape. This is critical for the merge step that follows — if one branch returns objects with different keys, downstream nodes become brittle.

Flowchart showing HTTP Request node splitting into two paths: Success path adds enriched data, Error path adds fallback fields, both paths converge at Merge node

Stage 3: Merging and Aggregating Results

After the enrichment step (both success and error branches), a Merge node in Append mode collects all items back into a single stream. From there, a Code node aggregates the results into a summary object:

javascript
const items = $input.all();
const total = items.length;
const succeeded = items.filter(i => i.json.enriched !== false).length;
const failed = items.filter(i => i.json.enriched === false).length;
return [{
json: {
total,
succeeded,
failed,
failureRate: (failed / total * 100).toFixed(1) + '%',
items: items.map(i => i.json)
}
}];

This aggregation node is the boundary between per-item processing and workflow-level logic. Everything before it operates on individual contacts. Everything after it operates on the batch as a whole.

Stage 4: Branching on Outcome

Not every run should produce the same notification. A 0% failure rate deserves a clean success email. A 100% failure rate should page someone immediately. A partial failure sits in between.

Use an IF or Switch node to route based on the failure rate:

javascript
// Switch node — Expression for routing
{{ $json.failed === 0 ? 'success' : $json.succeeded === 0 ? 'critical' : 'partial' }}

Each branch then connects to a different notification node — email for success and partial, Slack or PagerDuty for critical. This is where branching and merging from earlier lessons pay off: you built the ability to produce a clean single item summary specifically so this Switch node has something simple to evaluate.

Stage 5: Sending the Report

Each notification branch formats its own message. The success branch sends a short confirmation. The partial branch includes the list of failed contacts so someone can investigate. The critical branch triggers an urgent alert with full error details.

The email node on the partial branch uses an expression to build the body dynamically:

javascript
// Code node before the email — builds the failure list
const failures = $json.items
.filter(i => i.enriched === false)
.map(i => `${i.email}: ${i.enrichmentError}`)
.join('\n');
return [{ json: { ...$json, failureList: failures } }];

At this point the workflow is complete. It has a trigger, per-item processing with error handling, result merging, outcome-based branching, and targeted notifications.

Summary

A production-ready workflow is not one that handles the happy path well — it is one that handles failure without losing data, branches without duplicating logic, and reports on its own health. The patterns you applied here — split before loop, normalize error branches, aggregate before branching, branch on outcome — are the repeatable building blocks for almost any automation you will build in n8n. Use this workflow as a reference template and adapt the stages to your own data sources, APIs, and notification channels.

Lesson practice

Lesson Checkpoint

Review what you learned and get feedback on your work.

0/1
Answered · 0/1