Build an n8n Order Quality Pipeline

Build an n8n workflow to clean, validate, store, and notify on mock orders.

Introduction

30 Second Summary

Order data often arrives messy. A missing customer detail can quietly turn into a misleading report.

In this project, you will build a visual n8n order-quality pipeline for three deliberately messy mock orders. A complete run leaves every result in persistent storage while browser notifications reveal which orders passed.

What You'll Build

A single run ends with three stored decisions visible in n8n plus three matching outcome messages in the ntfy web app.

By the end of this project, you'll have:

  • An on-demand pipeline that brings three deliberately messy mock orders onto the n8n canvas through a clearly marked source boundary.
  • A quality check that cleans every order before routing two passed records to the valid path and one incomplete record to review.
  • A persistent history you can inspect in the order_pipeline_results Data Table alongside real success and review-needed browser notifications.
  • Secret Mission: Replace insert-only storage with an idempotent upsert so repeated workflow runs update existing orders without creating duplicates.

Are there any prerequisites?

You need access to your existing self-hosted n8n instance on a Windows device. Step 1 checks the required n8n interfaces plus access through Microsoft Edge before you build.

Before We Start

This checkpoint helps you commit to building an order-quality pipeline that stops unreliable source data from silently entering a reporting system.

Prepare Storage and Notifications

Unreliable order data needs a controlled place to land before it reaches reporting. The pipeline also needs proof that each result reached someone.

In this step, you will verify that Microsoft Edge can reach your self-hosted n8n editor. You will also confirm that the required workflow interfaces are available.

You will create an empty n8n Data Table for evaluated orders. You will also prepare a private ntfy topic for visible notifications.

In this step, get ready to:
  • Confirm that Microsoft Edge and the required n8n interfaces are available.
  • Create the empty results table with a consistent seven-column schema.
  • Subscribe to a private ntfy topic and test message delivery.
Verify the required interfaces

The workflow relies on interfaces available in n8n 2.36.8. Checking them now prevents a missing node from interrupting the build later.

  • Press the Windows key to open Windows search.
  • Type Microsoft Edge into the search box.
  • Press Enter to open the browser.

✔️ Microsoft Edge opens

Microsoft Edge is open. The browser is ready for the n8n editor and ntfy web app.

ⓧ Microsoft Edge is not installed

The official Windows installer adds Microsoft Edge to your device. Once installed, the same Windows search check confirms that it is ready.

  • Visit the official Microsoft Edge download page in your current browser.
  • Select Download Edge for Windows.
  • Run the downloaded installer from your browser downloads.
  • Complete the installation using the on-screen prompts.
  • Press the Windows key to return to Windows search.
  • Type Microsoft Edge into the search box.
  • Press Enter to open the installed browser.

Edge still not opening?

  • Reopen the downloaded installer if the installation window closed before completion.
  • Check your internet connection if the installer cannot download the browser files.
  • Help me troubleshoot Microsoft Edge installation on Windows.
  • Enter the address of your existing self-hosted n8n editor in the Microsoft Edge address bar.
  • Sign in with your existing n8n account.
  • Select the project you will use for this pipeline.

You should now be signed in and viewing the n8n project editor.

  • Confirm that the Data tables tab is visible in the project.
  • Open the node picker with the add-node control in the workflow editor.
  • Search for each required node name one at a time: Data Table, Code, If, HTTP Request, Manual Trigger, and Sticky Note.
  • Close the node picker without adding a node.

You should find all six nodes in the picker. The project should also keep the Data tables tab visible.

Missing Data Tables or a node?

Updating a self-hosted instance can affect existing workflows. Confirm the installation method before changing the instance.

  • Confirm how your self-hosted n8n instance was installed.
  • Open the official n8n update guide.
  • Follow the update instructions that match your installation method.
  • Help me identify why my n8n instance is missing Data Tables or a required node.
Create the results table

Every evaluated order needs to remain available after the workflow finishes. A stable table schema also lets later nodes map fields without custom storage logic.

Why Data Tables?

Data Tables persist tabular data directly inside n8n. This keeps the project focused on the quality pipeline without adding an external database.

Use This Table Schema

  • The order_id column uses String.
  • The customer_name column uses String.
  • The email column uses String.
  • The total column uses Number.
  • The status column uses String.
  • The is_valid column uses Boolean.
  • The validation_errors column uses String.
  • Select the Data tables tab in your n8n project.
  • Open the split button in the top-right corner.
  • Select Create Data table.
  • Select From scratch.
  • Enter order_pipeline_results in the table name field.
  • Add all seven columns from the schema above with the exact names and types shown.
  • Leave the table without any rows.
  • Use the form's create control to save the empty table.

You should see an empty order_pipeline_results table with all seven columns visible.

Table schema not matching?

  • Compare every column name with the schema above. Automatic mapping later depends on exact spelling.
  • Correct any column whose type differs from the schema above.
  • Remove any test rows so the table is empty before the workflow runs.
  • Help me correct my n8n Data Table schema.
Subscribe to a private test topic

The ntfy web app gives the pipeline a real notification destination without credentials. A delivered test message proves that the topic is ready before the workflow starts sending outcomes.

Keep the Topic Private

On the public ntfy.sh server, the topic name acts as a password. Anyone who knows the name can publish or subscribe.

  • Start the topic name with n8n-orders-.
  • Add at least 12 random letters or numbers after the prefix.
  • Keep the complete topic name under 64 characters.
  • Use a fresh topic name that has not appeared in a tutorial.
  • Use mock data only while working with this public server.
  • Return to Microsoft Edge from earlier.
  • Open a new browser tab.
  • Visit the ntfy web app.

You should see the ntfy web app with a place to enter a topic name.

  • Choose a topic name represented by <your-private-topic> using the rules above.
  • Enter the complete topic name into the topic field.
  • Select Subscribe.
  • Store the exact topic name in a private note for Step 5.

Before you publish the test, do you expect the subscribed topic to display the message immediately?

  • Type setup complete into the message field.
  • Use the web app's publish control to send the message.

You should see setup complete appear in the subscribed topic. This confirms that the notification destination is working.

You made the delivery check work. Your private topic can now receive the pipeline's later success and review messages.

Test Message Not Appearing?

  • Confirm that the subscribed topic matches the private name you created.
  • Check that the message field contains setup complete.
  • Refresh the ntfy web app before publishing the test again.
  • Help me troubleshoot a missing ntfy web app message.

Your empty results table and tested notification topic are ready. Next, you will build the manual workflow that produces three reproducible mock orders.

Retrieve Mock Orders

Your n8n Data Table and ntfy topic are ready to support the pipeline.

A controlled mock dataset gives you the same messy edge case on every run without external credentials. You will create an unpublished manual workflow that returns three orders with inconsistent values.

In this step, get ready to:
  • Create an unpublished workflow with an on-demand manual trigger.
  • Generate three deliberately messy order records.
  • Mark the retrieval stage as a replaceable source adapter.
Start a manual workflow

A manual workflow lets you test the same data whenever you choose. It keeps this starter pipeline under your control while you build each stage.

  • Create a new workflow in the n8n editor from earlier.
  • Enter Mock Order Quality Pipeline as the workflow name.
  • Use the node picker on the canvas to add a Manual Trigger node.
  • Rename the Manual Trigger node to Start Pipeline.

Your canvas now contains a trigger named Start Pipeline. The workflow remains unpublished because new workflows start that way by default.

Why run the workflow manually?

The Manual Trigger starts the workflow when you select Execute Workflow. This gives every test run the same clear starting point.

Generate the source records

A Code node can emit several n8n items from one execution. Each item stores one order under its json property.

These records deliberately mix casing with extra spaces. The second order also contains a whitespace-only email value.

  • Use the node picker on the canvas to add a Code node.
  • Rename the Code node to Retrieve Mock Orders.
  • Connect Start Pipeline to Retrieve Mock Orders.
  • Keep Mode set to Run Once for All Items.
  • Replace the Code node editor contents with the code below:
return [
  {
    json: {
      order_id: ' ORD-1001 ',
      customer_name: '  ada lovelace ',
      email: 'ADA@EXAMPLE.COM ',
      total: '49.95',
      status: ' PAID ',
    },
  },
  {
    json: {
      order_id: 'ord-1002',
      customer_name: 'grace   hopper',
      email: '   ',
      total: '18.00',
      status: 'paid',
    },
  },
  {
    json: {
      order_id: ' Ord-1003 ',
      customer_name: '  alan turing',
      email: 'ALAN@EXAMPLE.COM',
      total: '21.00',
      status: ' Refunded ',
    },
  },
];

What does this code do?

  • The returned array contains three items that n8n can pass through the workflow separately.
  • Each json object uses the same five field names that later stages expect.
  • The spaces with inconsistent capitalization reproduce common source-data problems.
  • The email: ' ' value creates a missing-data edge case without removing the field.

Before you run the node, which order do you expect to expose the missing-email edge case?

  • Select Execute step on Retrieve Mock Orders.

You should see three output items in the Table view. In the JSON view, the second item has order_id set to ord-1002 with an email value containing only spaces.

Not seeing three items?

  • Confirm that Mode still shows Run Once for All Items.
  • Check that the returned array starts with return [.
  • Check that every order keeps its fields inside a json object.
  • Help me troubleshoot my Retrieve Mock Orders output.

✔️ Awesome, I've got everything!

Your Retrieve Mock Orders node now provides all three dirty orders from one execution.

ⓧ I'd like to double check the full code

The complete contents of Retrieve Mock Orders should match this reference.

return [
  {
    json: {
      order_id: ' ORD-1001 ',
      customer_name: '  ada lovelace ',
      email: 'ADA@EXAMPLE.COM ',
      total: '49.95',
      status: ' PAID ',
    },
  },
  {
    json: {
      order_id: 'ord-1002',
      customer_name: 'grace   hopper',
      email: '   ',
      total: '18.00',
      status: 'paid',
    },
  },
  {
    json: {
      order_id: ' Ord-1003 ',
      customer_name: '  alan turing',
      email: 'ALAN@EXAMPLE.COM',
      total: '21.00',
      status: ' Refunded ',
    },
  },
];
Mark the source adapter

A source adapter boundary shows where a future connector can enter the workflow. This boundary lets you swap in Postgres or Google Sheets without changing the downstream field names.

  • Use the node picker on the canvas to add a Sticky Note.
  • Set the Sticky Note title to Source Adapter.
  • Enter Replace this node with Postgres Select or Google Sheets Get Row(s). Preserve the downstream field names. in the note body.
  • Position the Sticky Note beside Retrieve Mock Orders.

Before the final check, do you expect the source to return a usable email address for every order?

  • Select Retrieve Mock Orders on the canvas.
  • Select Execute step.

You should see three items with the fields order_id, customer_name, email, total, and status. The second item still has a whitespace-only email value.

That is your first live pipeline result. Your source now reproduces the same quality problem on every run.

Your workflow now produces the same three messy orders on demand. Next, you will normalize their fields and expose the gap that cleaning cannot repair.

Clean Order Data

Your n8n workflow now produces three reproducible mock orders on demand. Their fields still contain inconsistent formatting.

You will use a JavaScript Code node to apply normalization. Each output item gets a consistent schema.

Cleaning can standardize the records. The open question is what happens to a field that contains no real business value.

In this step, get ready to:
  • Add a cleaning transformation to the workflow.
  • Inspect the normalized output to expose the remaining data gap.
  • Document the normalized-field contract for future source adapters.
Add the cleaning transformation

The Clean Order Data node sits between retrieval and later quality checks. It reshapes every item while preserving its link to the source item.

  • Add a JavaScript Code node directly after Retrieve Mock Orders on the workflow canvas.

You will see the new Code node connected to the retrieval stage.

  • Rename the new node Clean Order Data.
  • Confirm that Mode shows Run Once for All Items.
  • Replace the starter code in Clean Order Data with this transformation:
const items = $input.all();

const normalizeText = (value) =>
  String(value ?? '')
    .trim()
    .replace(/\s+/g, ' ');

const toTitleCase = (value) =>
  value
    .toLowerCase()
    .replace(/\b\w/g, (letter) => letter.toUpperCase());

return items.map((item, index) => {
  const source = item.json;

  return {
    json: {
      order_id: normalizeText(source.order_id).toUpperCase(),
      customer_name: toTitleCase(normalizeText(source.customer_name)),
      email: normalizeText(source.email).toLowerCase(),
      total: Number(normalizeText(source.total)),
      status: normalizeText(source.status).toLowerCase(),
    },
    pairedItem: index,
  };
});

What does this code do?

  • The $input.all() expression loads all three items received from Retrieve Mock Orders.
  • The normalizeText helper turns each value into text. It trims outer whitespace and collapses repeated spaces.
  • The toTitleCase helper converts names such as grace hopper into Grace Hopper.
  • The items.map() call returns one normalized output for each input item. The pairedItem value preserves each output item's source link.
  • The Number conversion changes each text total into a numeric value.

Before you execute the node, predict whether cleaning can turn the whitespace-only email into a usable address.

  • Select Execute step on Clean Order Data.

You will see three output items with uppercase order IDs. The customer names now use title case.

Email addresses and statuses are lowercase. The total values are numeric.

The ORD-1002 email becomes an empty string. Cleaning exposes the missing value without inventing one.

Output still looks dirty?

  • Check that Clean Order Data is connected directly after Retrieve Mock Orders.
  • Confirm that Mode still shows Run Once for All Items.
  • Compare your helper names with normalizeText and toTitleCase because each name must match every use.
  • Help me debug my Clean Order Data output.

✔️ Awesome, I've got everything!

Great. Your Clean Order Data node now contains the complete transformation.

ⓧ I'd like to double check the full code

Compare your Clean Order Data node with this complete transformation.

const items = $input.all();

const normalizeText = (value) =>
  String(value ?? '')
    .trim()
    .replace(/\s+/g, ' ');

const toTitleCase = (value) =>
  value
    .toLowerCase()
    .replace(/\b\w/g, (letter) => letter.toUpperCase());

return items.map((item, index) => {
  const source = item.json;

  return {
    json: {
      order_id: normalizeText(source.order_id).toUpperCase(),
      customer_name: toTitleCase(normalizeText(source.customer_name)),
      email: normalizeText(source.email).toLowerCase(),
      total: Number(normalizeText(source.total)),
      status: normalizeText(source.status).toLowerCase(),
    },
    pairedItem: index,
  };
});
Inspect the normalized schema

A stable schema gives downstream nodes one predictable set of fields. Comparing the raw output with the cleaned output proves that every field follows the contract.

  • Select Clean Order Data on the workflow canvas.
  • Select the Table output view.
  • Confirm that the output fields are exactly order_id, customer_name, email, total, and status.
  • Switch back to Retrieve Mock Orders on the canvas.
  • Select the Table output view.
  • Compare each raw value with its normalized value in Clean Order Data.

The ORD-1001 item shows Ada Lovelace with ada@example.com. The ORD-1003 item shows Alan Turing with alan@example.com.

The ORD-1002 item shows Grace Hopper. Its email remains empty.

The total values are numeric. Every status value is lowercase.

Document the transformation contract

A transformation contract defines the fields every future source adapter must provide. This keeps later workflow stages independent from the original source.

  • Add a Sticky Note beside Retrieve Mock Orders and Clean Order Data.
  • Set the Sticky Note title to Transformation Contract.

The titled note now marks the boundary between source retrieval and normalized output.

  • Enter this exact note: Every source adapter must produce order_id, customer_name, email, total, and status. Cleaning standardizes values but does not approve them.

Before the final check, predict what happens to the whitespace-only email when the transformation runs again.

  • Select Clean Order Data on the workflow canvas.
  • Select Execute step in the node panel.

You will see three items with exactly order_id, customer_name, email, total, and status fields.

The ORD-1002 email is still empty. This is the intended data gap that validation needs to handle.

You have closed the cleaning loop. Every order now reaches the same schema while the missing email stays visible.

Next, you will turn that visible gap into validation evidence and route each order by its quality result.

Validate and Store Results

Your cleaning stage now gives every order the same shape. The blank email on ORD-1002 still proves that tidy data can fail a business rule.

An explicit validation gate in your n8n workflow records the evidence behind each decision. Persistent storage keeps every evaluated order available for review.

In this step, get ready to:
  • Validate each normalized order against the pipeline's quality rules.
  • Store all three evaluated records with their validation evidence.
  • Route valid records separately from records that need review.
Add validation evidence

Cleaning standardized the fields without deciding whether each order is usable. The next Code node turns your quality rules into two new fields: is_valid records the decision. validation_errors records the reason.

  • Add a JavaScript Code node after Clean Order Data.
  • Rename the new node to Validate Order Data.
  • Keep Mode set to Run Once for All Items.
  • Add the first validation rule by pasting this code into Validate Order Data:
const items = $input.all();

return items.map((item, index) => {
  const order = item.json;
  const errors = [];

  if (!order.email || !order.email.includes('@')) {
    errors.push('email must contain @');
  }

  return {
    json: {
      ...order,
      is_valid: errors.length === 0,
      validation_errors: errors.join('; '),
    },
    pairedItem: index,
  };
});

What does this code do?

  • The items array holds every normalized order received from the previous node.
  • The errors array collects the quality problems found in the current order.
  • The email check catches an empty value. It also catches a value without an @ character.
  • The returned item preserves the normalized fields. It adds a Boolean decision plus readable validation evidence.

Before you run the workflow, which order do you expect the email rule to flag?

  • Run the current workflow by selecting Execute Workflow.
  • Select Validate Order Data after the execution finishes.

You should see three output items. ORD-1002 has is_valid set to false with email must contain @ in validation_errors.

Missing the validation fields?

  • Confirm that Validate Order Data is connected directly after Clean Order Data.
  • Check that the returned data remains inside the json object.
  • Ask for help with the validation output.

The email rule exposes the known gap. A complete quality gate also protects the required identity fields, positive totals, and accepted statuses.

  • In Validate Order Data, find this opening block:
const items = $input.all();

return items.map((item, index) => {
  const order = item.json;
  const errors = [];

What does this reference show?

This reference snippet identifies the start of your current code. The email rule and returned item remain directly below it.

  • Replace that opening block with this expanded version:
const items = $input.all();
const allowedStatuses = ['paid', 'pending', 'refunded'];

return items.map((item, index) => {
  const order = item.json;
  const errors = [];

  if (!order.order_id) {
    errors.push('order_id is required');
  }

  if (!order.customer_name) {
    errors.push('customer_name is required');
  }

What changed here?

The allowedStatuses array defines the accepted status values. The first two checks protect the fields that identify the order and customer.

  • Find the block that starts with if (!order.email || !order.email.includes('@')) {.
  • Add the number and status checks directly below that email block by pasting:
  if (!Number.isFinite(order.total) || order.total <= 0) {
    errors.push('total must be greater than 0');
  }

  if (!allowedStatuses.includes(order.status)) {
    errors.push('status must be paid, pending, or refunded');
  }

How do the remaining rules help?

  • The total check rejects values that are not finite numbers. It also rejects totals of zero or less.
  • The status check accepts paid, pending, or refunded.
  • Every failed rule contributes a message to validation_errors.
  • Run the expanded quality gate by selecting Execute Workflow again.
  • Inspect the output of Validate Order Data.

You should still see two valid items. ORD-1002 remains the only invalid item because its email fails the complete quality gate.

✔️ Awesome, I've got everything!

Your three Code nodes now retrieve, clean, and validate the same three linked order items.

ⓧ I'd like to double check the full code

Compare each Code node with the complete versions below. The retrieval and cleaning code remains unchanged from the earlier steps.

The full code for Retrieve Mock Orders is:

return [
  {
    json: {
      order_id: ' ORD-1001 ',
      customer_name: '  ada lovelace ',
      email: 'ADA@EXAMPLE.COM ',
      total: '49.95',
      status: ' PAID ',
    },
  },
  {
    json: {
      order_id: 'ord-1002',
      customer_name: 'grace   hopper',
      email: '   ',
      total: '18.00',
      status: 'paid',
    },
  },
  {
    json: {
      order_id: ' Ord-1003 ',
      customer_name: '  alan turing',
      email: 'ALAN@EXAMPLE.COM',
      total: '21.00',
      status: ' Refunded ',
    },
  },
];

What should this node return?

This node returns the three deliberately messy source records. Their whitespace and casing give the cleaning stage reproducible input.

The full code for Clean Order Data is:

const items = $input.all();

const normalizeText = (value) =>
  String(value ?? '')
    .trim()
    .replace(/\s+/g, ' ');

const toTitleCase = (value) =>
  value
    .toLowerCase()
    .replace(/\b\w/g, (letter) => letter.toUpperCase());

return items.map((item, index) => {
  const source = item.json;

  return {
    json: {
      order_id: normalizeText(source.order_id).toUpperCase(),
      customer_name: toTitleCase(normalizeText(source.customer_name)),
      email: normalizeText(source.email).toLowerCase(),
      total: Number(normalizeText(source.total)),
      status: normalizeText(source.status).toLowerCase(),
    },
    pairedItem: index,
  };
});

What should this node preserve?

This node returns five normalized fields for each order. Its pairedItem value keeps each result linked to its source item.

The full code for Validate Order Data is:

const items = $input.all();
const allowedStatuses = ['paid', 'pending', 'refunded'];

return items.map((item, index) => {
  const order = item.json;
  const errors = [];

  if (!order.order_id) {
    errors.push('order_id is required');
  }

  if (!order.customer_name) {
    errors.push('customer_name is required');
  }

  if (!order.email || !order.email.includes('@')) {
    errors.push('email must contain @');
  }

  if (!Number.isFinite(order.total) || order.total <= 0) {
    errors.push('total must be greater than 0');
  }

  if (!allowedStatuses.includes(order.status)) {
    errors.push('status must be paid, pending, or refunded');
  }

  return {
    json: {
      ...order,
      is_valid: errors.length === 0,
      validation_errors: errors.join('; '),
    },
    pairedItem: index,
  };
});

What should this node add?

This node preserves the five normalized fields. It adds is_valid plus validation_errors to every order.

Persist every evaluated record

A Data Table keeps the evaluated rows inside n8n. Automatic mapping works because the seven item fields match the seven columns in order_pipeline_results.

  • Add a Data Table node after Validate Order Data.
  • Rename the node to Store Pipeline Results.
  • Select Row for Resource.
  • Select Insert for Operation.
  • Choose By Name as the table identification method.
  • Select order_pipeline_results as the table.
  • Choose Map Automatically for field mapping.
  • Leave Optimize Bulk disabled.

Why keep Optimize Bulk disabled?

The next node needs each inserted item to continue through the workflow. Keeping Optimize Bulk disabled allows the stored records to reach the routing stage.

Route records by validation status

The stored records now need separate paths for successful and review-needed outcomes. An If node reads the Boolean is_valid field on each current item.

  • Add an If node after Store Pipeline Results.
  • Rename the node to Route Valid Records.
  • Enter {{$json.is_valid}} as the condition value.
  • Choose Boolean as the condition type.
  • Select is true as the comparison.
  • Add a Sticky Note beside the storage stage.
  • Set the Sticky Note title to Storage Adapter.
  • Enter Replace Data Table with Postgres Insert or Google Sheets Append Row. Keep column names identical for automatic mapping. as the note.

Why mark the storage adapter?

The Sticky Note marks the boundary between pipeline logic and persistent storage. A future connector can replace the Data Table while preserving the same seven field names.

Before you run the complete workflow, how many orders do you expect each branch to receive?

  • Run the complete pipeline by selecting Execute Workflow.

You should see two items leave the true output of Route Valid Records. One item leaves the false output.

  • Switch back to the Data tables tab.
  • Select order_pipeline_results.

You should see exactly three rows. ORD-1001 and ORD-1003 are valid. ORD-1002 is invalid with email must contain @ stored as its validation evidence.

  • Return to the workflow canvas.
  • Select Route Valid Records to inspect its branch counts.

That quality gate now preserves every evaluated order while separating the two usable records from the one that needs review.

Missing rows or branch results?

  • Confirm that Store Pipeline Results uses order_pipeline_results with automatic mapping.
  • Check that Optimize Bulk remains disabled if the If node receives no items.
  • Confirm that the If condition contains {{$json.is_valid}} with the Boolean comparison is true.
  • Ask for help with the storage or routing result.

Your pipeline now stores every quality decision and sends each order down the correct branch. Next, you will connect those branches to visible browser notifications.

Send Outcome Notifications

Your n8n workflow already separates two approved orders from the record that needs review. The result is visible inside n8n, but no one outside the workflow knows what happened.

In this step, each branch sends an HTTP notification to ntfy. The subscribed topic becomes the visible destination for two success messages plus one review-needed message.

In this step, get ready to:
  • Connect the valid branch to a success notification.
  • Connect the invalid branch to a review-needed notification.
  • Run the complete pipeline to deliver all three outcomes.
Notify successful records

The true output carries orders that passed every quality check. Its notification confirms that each valid order reached storage successfully.

  • Drag from the true output of Route Valid Records to an empty area of the canvas.
  • Select HTTP Request from the node picker.

You now have an HTTP Request node attached to the valid branch.

  • Rename the new node Notify Valid Order.
  • Set Method to POST.

The node is now ready to publish an HTTP message for each valid item.

  • Enter https://ntfy.sh/<your-private-topic> in the URL field.
  • Turn on Send Headers.

The request now points to the private topic from earlier. Keep the exact topic name private because anyone who knows it can publish or subscribe.

  • Add Title with the value Order pipeline success.
  • Add Tags with the value heavy_check_mark.

These headers give successful notifications a recognizable title plus a status tag.

  • Turn on Send Body.
  • Choose Raw as the body type.

The raw body carries the order-specific message that appears in ntfy.

  • Set Content Type to text/plain.
  • Enter Order {{$json.order_id}} passed validation and was stored. in the Body field.

The expression reads the current order's order_id. Each valid item therefore produces its own message.

Notify records needing review

The false output carries orders with validation evidence. This branch sends the order identity plus the reason that a person needs to review it.

  • Drag from the false output of Route Valid Records to an empty area of the canvas.
  • Select HTTP Request from the node picker.

A second HTTP Request node now gives the invalid branch its own delivery path.

  • Rename the new node Notify Invalid Order.
  • Set Method to POST.

The review branch is ready to publish one message for each invalid item.

  • Enter https://ntfy.sh/<your-private-topic> in the URL field.
  • Turn on Send Headers.

Both branches now publish to the same subscribed topic.

  • Add Title with the value Order pipeline review needed.
  • Add Tags with the value warning.

The review title makes this outcome distinct from the success path.

  • Turn on Send Body.
  • Choose Raw as the body type.

This raw body can include the validation evidence from the current item.

  • Set Content Type to text/plain.
  • Enter Order {{$json.order_id}} needs review: {{$json.validation_errors}} in the Body field.

The body identifies the affected order. It also carries the exact validation evidence produced earlier.

Why use two HTTP Request nodes?

Each branch represents a different operational outcome. Separate nodes give each outcome its own message.

The valid branch confirms successful storage. The invalid branch tells a reviewer what needs attention.

Run the complete notification path

The final pair of HTTP Request nodes forms a replaceable notification adapter. A canvas note preserves that boundary for a future delivery channel.

  • Add a Sticky Note beside the two HTTP Request nodes.
  • Set its title to Notification Adapter.

The note now labels the delivery boundary beside both notification nodes.

  • Enter HTTP delivery is isolated here. Replace these nodes later with email, Slack, or another approved channel. in the note.

Your canvas now shows where another approved notification channel can replace ntfy.

✔️ Awesome, I've got everything!

Your true branch has its success request. Your false branch has its review request.

The Notification Adapter note marks both nodes as a replaceable delivery boundary.

ⓧ I'd like to double check the full code

This step uses n8n node settings. Compare your notification branches with this complete configuration.

  • The true output of Route Valid Records connects to Notify Valid Order.
  • The false output of Route Valid Records connects to Notify Invalid Order.
  • Both nodes use POST with https://ntfy.sh/<your-private-topic>.
  • Both nodes have Send Headers enabled.
  • Both nodes have Send Body enabled.
  • Both nodes use Raw with text/plain.
  • The valid request uses Title value Order pipeline success.
  • The valid request uses Tags value heavy_check_mark.
  • The valid request body is Order {{$json.order_id}} passed validation and was stored..
  • The invalid request uses Title value Order pipeline review needed.
  • The invalid request uses Tags value warning.
  • The invalid request body is Order {{$json.order_id}} needs review: {{$json.validation_errors}}.
  • The Notification Adapter note says HTTP delivery is isolated here. Replace these nodes later with email, Slack, or another approved channel..
  • Switch back to the ntfy web app from earlier to confirm the subscribed topic is visible.
  • Return to the n8n editor.

Before you run the workflow, how many success messages should the true branch send? How many review messages should the false branch send?

  • Select Execute Workflow to run the complete pipeline.

You will see two items follow the true path into Notify Valid Order. One item follows the false path into Notify Invalid Order.

  • Inspect the execution path to confirm both notification nodes completed.
  • Return to order_pipeline_results in the Data tables tab to check what the complete execution stored.

Each full run inserts the evaluated records. Repeated runs can create duplicate order rows because storage still uses Insert.

  • Switch back to the ntfy web app to inspect the delivered outcome messages.

You will see two notifications titled Order pipeline success. They report that ORD-1001 plus ORD-1003 passed validation.

You will also see one notification titled Order pipeline review needed for ORD-1002. Its message includes email must contain @.

That completes the feedback loop. Every evaluated order now produces a visible outcome for the person responsible for it.

Missing a notification?

  • Compare the URL in both HTTP Request nodes with the private topic currently shown in ntfy.
  • Confirm Notify Valid Order connects to true.
  • Confirm Notify Invalid Order connects to false.
  • Help me troubleshoot missing ntfy notifications from my n8n workflow.

Secret mission

Prevent Duplicate Rows with Upsert

Repeated test runs currently add another copy of every order. Replace insert-only storage with an upsert so each order keeps one row while every run still sends notifications.

Clean Up Your Resources

Clean Up Your Resources

Your self-hosted n8n resources use the instance you already had before this project. The ntfy topic adds no incremental cost.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The unpublished Mock Order Quality Pipeline workflow in n8n.
  • The order_pipeline_results Data Table containing three distinct order rows.
  • Your subscription to <your-private-topic> in the ntfy web app.

Keep everything running

No action is needed. This option keeps the finished pipeline available for future connector practice.

  • The Mock Order Quality Pipeline workflow stays unpublished for on-demand runs.
  • The order_pipeline_results table keeps its three distinct order rows.
  • The subscription to <your-private-topic> stays active for future notification tests.

Pause - I'll come back to this later

The workflow is already unpublished, so it does not run automatically. Pausing only requires you to stop the ntfy subscription while preserving the n8n resources.

  • Keep the Mock Order Quality Pipeline workflow in its current unpublished state.
  • Keep the order_pipeline_results table for your next session.
  • Unsubscribe from <your-private-topic> using its topic menu in the ntfy web app tab from earlier.
  • Enter <your-private-topic> in the ntfy web app when you return.
  • Select Subscribe to resume notifications.

Delete - I don't want to use this again

Remove all project resources if you want to start fresh.

Deletion is permanent. You can recreate the same mock project by following the steps again.

  • Delete Mock Order Quality Pipeline from the n8n project workflow list using the workflow's options menu.
  • Confirm the workflow deletion when n8n asks.
  • Delete order_pipeline_results from the Data tables tab using the table's options menu.
  • Confirm the table deletion when n8n asks.
  • Unsubscribe from <your-private-topic> using its topic menu in the ntfy web app tab from earlier.

The ntfy server caches messages for 12 hours by default. Your cached mock notifications expire without another action.

Nice Work!

Nice Work!

You did it! Your n8n order-quality pipeline now turns messy mock orders into validated results with matching browser notifications.

You've learned how to:

  • Built an on-demand workflow that retrieves three reproducible mock orders. Its cleaning stage converts every item into a stable downstream schema.
  • Created a validation gate that catches the blank email on ORD-1002. The n8n Data Table preserves every evaluated record with its validation evidence.
  • Routed each result through its matching validation path. Each path delivers a browser notification through ntfy. Sticky Notes mark replaceable adapter boundaries for future Postgres or Google Sheets connections.
  • Secret Mission: Upgraded insert-only storage to an idempotent upsert keyed by order_id. Two complete runs now leave exactly three distinct orders.

Ready to quiz yourself?