Tips > Reliability & Performance

Handle Partial Failures in Batch Operations

When processing batches (importing records, sending emails, updating a CRM), some items will succeed and others will fail.

When a batch operation processes many items, some succeed and some fail. Track both counts separately and produce a clear summary instead of letting one bad record kill the whole batch. A Code node placed after a step with Continue on Fail enabled can tally successes and failures, compute a success rate, and drive alerting.

What is a partial failure in a batch operation?

When processing batches (importing records, sending emails, updating a CRM), some items will succeed and others will fail. Track both counts, handle them separately, and produce a clear execution summary. This prevents the all-or-nothing problem where one bad record kills the entire batch.

Real-world example: A nightly workflow imports 500 contacts from a CSV into HubSpot. 12 contacts have invalid email addresses and fail. The other 488 should still be imported, and the 12 failures should be reported.

How do you track successes and failures separately?

Place this Code node after a step with Continue on Fail enabled to tally the results:

// Code node: "Process Batch Results"
// Place after a node with "Continue on Fail" enabled

const allItems = $input.all();

const results = {
  total: allItems.length,
  succeeded: 0,
  failed: 0,
  successItems: [],
  failedItems: []
};

for (const item of allItems) {
  if (item.json.error) {
    results.failed++;
    results.failedItems.push({
      originalData: item.json,
      error: item.json.error.message || 'Unknown error',
      index: results.succeeded + results.failed - 1
    });
  } else {
    results.succeeded++;
    results.successItems.push(item.json);
  }
}

results.successRate = Math.round(
  (results.succeeded / results.total) * 100
);

return [{
  json: results
}];

Generate a batch processing report:

// Code node: "Generate Batch Report"
const r = $input.first().json;

const report = {
  summary: `Batch complete: ${r.succeeded}/${r.total} succeeded (${r.successRate}%)`,
  succeeded: r.succeeded,
  failed: r.failed,
  total: r.total,
  successRate: r.successRate,
  needsAttention: r.failed > 0,
  failedItemsSample: r.failedItems.slice(0, 10).map(f => ({
    error: f.error,
    data: JSON.stringify(f.originalData).substring(0, 200)
  }))
};

// Determine if this batch needs an alert
if (r.successRate < 90) {
  report.alertLevel = 'critical';
  report.alertMessage = `Batch success rate below 90%: only ${r.successRate}%`;
} else if (r.failed > 0) {
  report.alertLevel = 'warning';
  report.alertMessage = `${r.failed} items failed in batch`;
} else {
  report.alertLevel = 'info';
  report.alertMessage = 'All items processed successfully';
}

return [{ json: report }];

How do you route the batch report?

Route the report by alert level so a healthy batch is logged quietly while a low success rate triggers investigation:

Routing after the report:

[Batch Report] --> [Switch: alertLevel]
                      |
           +----------+----------+
           |          |          |
       [critical]  [warning]  [info]
           |          |          |
       [Slack +    [Slack    [Log to
        DLQ for     alert]    DB only]
        failed
        items]

This gives you full visibility into batch health. A 95% success rate might be acceptable; a 50% success rate triggers immediate investigation.

Related: Use "Pin Data" to Freeze Node Output · Break Large Workflows into Sub-Workflows

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call