n8n Daily Sales Report Automation: PostgreSQL + Resend + Code Node โ€” Complete Step-by-Step Workflow Documentation

๐Ÿค– n8n Automation

Daily Sales Report Workflow

Automatically query yesterday’s sales from Neon PostgreSQL, aggregate KPIs, identify top products, and email a fully formatted HTML report โ€” every morning at 8 AM.

n8n Workflow Automation
Neon PostgreSQL
Resend Email API
Code Node (JavaScript)
โ— Overview

What This Workflow Does

Zero-touch daily sales intelligence, delivered to your inbox every morning.

๐Ÿ“Œ Business Purpose Every morning at 8 AM, the workflow automatically queries the previous day’s sales data from a Neon PostgreSQL database, aggregates totals by category, identifies the top 5 selling products, and emails a fully formatted HTML sales report to the sales manager โ€” with zero manual effort.
Email Report โ€” 4 KPI Cards
Total Orders
8
Unique Customers
4
Gross Revenue
โ‚น2,33,500
Avg Order Value
โ‚น29,188
Key Features
  • Fully automated โ€” runs every day at 8 AM via Schedule Trigger
  • 3 parallel PostgreSQL queries โ€” clean, no data duplication
  • Formatted HTML email with KPI cards, category table & top products table
  • Built on free tools โ€” Neon PostgreSQL + Resend free tier
Tools & Services
ToolPurposeCost
n8nWorkflow automation platformFree / Self-hosted
Neon PostgreSQLCloud database for sales dataFree tier
ResendEmail delivery serviceFree โ€” 3,000/month
Code Node (n8n)Build dynamic HTML email from query dataBuilt-in
Final Workflow Architecture
โฐ Schedule Trigger
โ†’
๐Ÿ—„ Query_CategoryRevenue
โ€–
๐Ÿ—„ Query_TopProducts
โ€–
๐Ÿ—„ Query_OverallTotals
โ†’
๐Ÿ”— Merge
โ†’
โš™๏ธ Build Report
โ†’
๐Ÿ“ง Send Report
#Node NameNode TypePurpose
1Schedule TriggerSchedule TriggerFires every day at 8 AM
2Query_CategoryRevenuePostgreSQLRevenue breakdown by product category
3Query_TopProductsPostgreSQLTop 5 best-selling products
4Query_OverallTotalsPostgreSQLHeadline KPIs: orders, revenue, avg value
5MergeMerge (Combine)Combines all 3 query results cleanly
6Build ReportCode (JS)Generates formatted HTML email body
7Send ReportResendEmails the report to the sales manager
โ— Pre-Setup

Set Up Neon PostgreSQL (Free Cloud DB)

Create your free database and save the connection credentials before building the workflow.

  • Go to neon.tech โ†’ Click “Sign Up” (free)
  • After login โ†’ Click “New Project”
  • Project Name: hr-demo
  • Region: Choose closest to you
  • Click “Create Project”
  • You’ll see a Connection String like:
postgresql://username:password@ep-xxx.neon.tech/neondb

Save these details:

FieldValue
Hostep-xxx.neon.tech
Databaseneondb
Usernameyour username
Passwordyour password
Port5432
โ— Step 1

Set Up the Database in Neon

Open your Neon dashboard โ†’ Click SQL Editor โ†’ Run the SQL statements below in order.

1A โ€” Create Tables
CREATE TABLE products (
  id       SERIAL PRIMARY KEY,
  name     VARCHAR(100),
  category VARCHAR(50),
  price    NUMERIC(10,2)
);

CREATE TABLE customers (
  id        SERIAL PRIMARY KEY,
  full_name VARCHAR(100),
  email     VARCHAR(100)
);

CREATE TABLE orders (
  id           SERIAL PRIMARY KEY,
  customer_id  INT REFERENCES customers(id),
  product_id   INT REFERENCES products(id),
  quantity     INT,
  total_amount NUMERIC(10,2),
  order_date   DATE DEFAULT CURRENT_DATE,
  status       VARCHAR(20) DEFAULT 'Completed'
);
1B โ€” Insert Sample Products & Customers
INSERT INTO products (name, category, price) VALUES
  ('Laptop Pro',     'Electronics', 85000),
  ('Office Chair',   'Furniture',   12000),
  ('Standing Desk',  'Furniture',   25000),
  ('Wireless Mouse', 'Electronics',  1500),
  ('Notebook Pack',  'Stationery',    500),
  ('USB Hub',        'Electronics',  2000),
  ('Whiteboard',     'Stationery',   3500);

INSERT INTO customers (full_name, email) VALUES
  ('Rahul Sharma', 'rahul@example.com'),
  ('Priya Mehta',  'priya@example.com'),
  ('Amit Patel',   'amit@example.com'),
  ('Sneha Joshi',  'sneha@example.com');
1C โ€” Insert Sample Orders (Dated Yesterday)
INSERT INTO orders (customer_id, product_id, quantity, total_amount, order_date, status) VALUES
  (1, 1, 1,  85000, CURRENT_DATE - 1, 'Completed'),
  (2, 2, 2,  24000, CURRENT_DATE - 1, 'Completed'),
  (3, 4, 3,   4500, CURRENT_DATE - 1, 'Completed'),
  (4, 5, 5,   2500, CURRENT_DATE - 1, 'Completed'),
  (1, 3, 1,  25000, CURRENT_DATE - 1, 'Completed'),
  (2, 6, 2,   4000, CURRENT_DATE - 1, 'Completed'),
  (3, 7, 1,   3500, CURRENT_DATE - 1, 'Completed'),
  (4, 1, 1,  85000, CURRENT_DATE - 1, 'Completed');
โ„น๏ธ Why CURRENT_DATE – 1? All orders are inserted with yesterday’s date so the workflow query picks them up immediately during testing without waiting for the next day.
โ— Steps 2 โ€“ 5

Create & Configure the Workflow in n8n

Build the automation canvas step by step โ€” from the schedule trigger to the merge node.

STEP 2Create the Workflow in n8n
  • Open n8n โ†’ Click “New Workflow”.
  • Name it: “Daily Sales Report”.
STEP 3Add Schedule Trigger Node
  • Click “+” โ†’ Search “Schedule Trigger” โ†’ Select it.
  • Configure as follows:
FieldValue
Trigger IntervalDays
Days Between Triggers1
Trigger at Hour8
Trigger at Minute0
๐Ÿ’ก Testing Tip You do not need to wait until 8 AM to test. Click “Test Workflow” at any time to run the workflow immediately on demand.
STEP 4Add 3 Parallel PostgreSQL Nodes

All 3 Postgres nodes must branch directly from the Schedule Trigger โ€” NOT chained one after another. This prevents data duplication.

  • Click “+” from Schedule Trigger โ†’ Add Postgres Node โ†’ Rename to Query_CategoryRevenue.
  • Go back to Schedule Trigger โ†’ Click “+” again โ†’ Add Postgres Node โ†’ Rename to Query_TopProducts.
  • Go back to Schedule Trigger โ†’ Click “+” again โ†’ Add Postgres Node โ†’ Rename to Query_OverallTotals.
๐Ÿ’ก How to Rename a Node Right-click the node on the canvas โ†’ Click Rename โ†’ Type the new name โ†’ Press Enter.

Use your existing Neon credential in all 3 nodes. Set Operation โ†’ Execute Query in each.

Query_CategoryRevenue โ€” SQL
SELECT
  p.category,
  COUNT(o.id)         AS total_orders,
  SUM(o.quantity)     AS total_units,
  SUM(o.total_amount) AS total_revenue
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.order_date = CURRENT_DATE - 1
  AND o.status = 'Completed'
GROUP BY p.category
ORDER BY total_revenue DESC;
Query_TopProducts โ€” SQL
SELECT
  p.name              AS product_name,
  p.category,
  SUM(o.quantity)     AS units_sold,
  SUM(o.total_amount) AS revenue
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE o.order_date = CURRENT_DATE - 1
  AND o.status = 'Completed'
GROUP BY p.name, p.category
ORDER BY revenue DESC
LIMIT 5;
Query_OverallTotals โ€” SQL
SELECT
  COUNT(DISTINCT o.id)          AS total_orders,
  COUNT(DISTINCT o.customer_id) AS unique_customers,
  SUM(o.total_amount)           AS gross_revenue,
  AVG(o.total_amount)           AS avg_order_value
FROM orders o
WHERE o.order_date = CURRENT_DATE - 1
  AND o.status = 'Completed';
STEP 5Add Merge Node โ€” Combine All 3 Query Results
  • Click “+” โ†’ Search “Merge” โ†’ Select it.
  • Configure: Mode โ†’ Combine, Combination Mode โ†’ Multiplex.
  • Connect: Query_CategoryRevenue โ†’ Input 1  |  Query_TopProducts โ†’ Input 2  |  Query_OverallTotals โ†’ Input 3
โ„น๏ธ Why Merge Instead of Chaining? Chaining Postgres nodes causes each node to pass all previous items downstream, multiplying the data. Running them in parallel and merging keeps each query result isolated and clean.
โ— Step 6

Code Node โ€” Build HTML Report

JavaScript that assembles the dynamic HTML email body from all three query results.

  • Click “+” after Merge โ†’ Search “Code” โ†’ Select it.
  • Set Language โ†’ “JavaScript” โ†’ Paste the following code:
// Pull results from each named node
const categories  = $('Query_CategoryRevenue').all();
const topProducts = $('Query_TopProducts').all();
const totals      = $('Query_OverallTotals').all();
const t = totals[0].json;

// Format yesterday's date
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dateStr = yesterday.toLocaleDateString('en-IN', {
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
});

// Currency formatter (Indian Rupees)
const formatCurrency = (val) =>
  'โ‚น' + Number(val).toLocaleString('en-IN', { maximumFractionDigits: 0 });

// Build category & product rows, then return HTML + subject
const categoryRows = categories.map(c => `<tr>...</tr>`).join('');
const productRows  = topProducts.map((p, i) => `<tr>...</tr>`).join('');

return [{ json: {
  html: `... full HTML template ...`,
  subject: `Daily Sales Report โ€” ${'$'}{dateStr}`
}}];
โš ๏ธ Important โ€” Node Name References The names Query_CategoryRevenue, Query_TopProducts, and Query_OverallTotals in the Code node must exactly match the names you gave your Postgres nodes on the canvas.
โ— Steps 7 โ€“ 9

Send Report, Test & Activate

Wire up the Resend node, validate all scenarios, then go live.

STEP 7Add Resend Node โ€” Email the Report
FieldValue
Fromonboarding@resend.dev
Toyour-email@example.com
Subject{{ $json.subject }}
Email TypeHTML
Body{{ $json.html }}
STEP 8Test the Workflow
  • Click “Test Workflow” in n8n.
  • Verify all 7 nodes show green checkmarks. โœ…
  • Check your inbox โ€” report should arrive with 4 KPI cards, category table, and Top 5 Products table (no duplicates).
STEP 9Activate the Workflow
  • Toggle the “Active” switch in the top right corner of n8n.
  • The workflow is now live โ€” runs every day at 8 AM automatically. โœ…
โœ… Workflow Live Every morning at 8 AM the workflow queries PostgreSQL, builds the HTML report, and delivers it to the sales manager’s inbox.
Email Report Structure
SectionContent
HeaderDark navy banner with report title and date
KPI Cards (4)Total Orders โ”‚ Unique Customers โ”‚ Gross Revenue โ”‚ Avg Order Value
Category TableCategory / Orders / Units Sold / Revenue โ€” sorted by revenue desc
Top 5 ProductsRank / Product Name / Category / Units / Revenue
FooterAutomated report disclaimer
Test Scenarios
Test CaseHow to TestExpected Result
Normal reportRun workflow with sample data insertedFull HTML email with all 3 sections
Top 5 no repetitionCheck Top Products section in emailExactly 5 rows โ€” no duplicates
No orders yesterdayDelete all rows from orders, re-runEmpty tables in email โ€” no crash
Add more ordersInsert extra rows for CURRENT_DATE – 1, re-runUpdated totals and rankings in email
Different categoriesInsert orders for new product categoriesNew categories appear in category table
โ— Architecture Decision

Why Parallel Queries Beat Chained Queries

Understanding why 3 branches + Merge is the correct pattern โ€” not 3 nodes in a chain.

AspectChained Nodes (Old)Parallel + Merge (Current)
Data isolationQueries bleed into each otherEach query result fully isolated
Duplication bugTop 5 Products repeats 3ร—Each section appears exactly once
PerformanceSequential โ€” one waits for anotherAll 3 queries run simultaneously
ScalabilityAdding a 4th query multiplies the bugJust add another parallel branch
DebuggabilityHard to tell which node has what dataEach node clearly named and isolated
๐Ÿš€ For Production Upgrade
  • Replace onboarding@resend.dev with a verified company domain email.
  • Add more queries (e.g. regional breakdown, returns) as additional parallel branches.
  • Connect to a real production PostgreSQL database instead of Neon.

Scroll to Top