Softechinfra
Productivity

n8n + Sheets + WhatsApp: A Saturday Sales Roll-Up That Replaces Monday Standup

A 14-node n8n workflow that pulls weekend sales from Google Sheets and pushes a WhatsApp summary to founders before Monday breakfast. Real node JSON, a lean all-in cost, and the one cron mistake we hit.

Hrishikesh BaidyaHrishikesh Baidya
November 2, 202513 min read
n8n + Sheets + WhatsApp: A Saturday Sales Roll-Up That Replaces Monday Standup

A 22-person retail SMB in Indore was holding a 45-minute Monday standup just to recap weekend sales. Founders flying in. Three department heads. One badly typed Excel sheet on a projector. We replaced it with a 14-node n8n workflow that runs at 7:50 am Monday, pulls Saturday and Sunday rows from Google Sheets, and pushes a structured WhatsApp summary to four numbers. The standup is gone. The recap takes 90 seconds to read.

14
n8n nodes (incl. error branch)
90 sec
Read time vs 45-min standup
7:50 am
Cron trigger every Monday IST

TL;DR — what this n8n workflow actually does

It runs on a Monday morning Cron node, reads weekend rows from a Google Sheet that the shop floor already fills daily, computes 6 numbers (gross sales, returns, top SKU, worst day, payment mix, average ticket), formats a WhatsApp template, and posts it to a broadcast list of 4 founder numbers via the WhatsApp Cloud API. If anything fails, an error node DMs the operations head and writes the run to a logs sheet.

Why this matters now — November 2025

Two changes pushed this off the backlog. WhatsApp Cloud API rolled out marketing template categories with India-specific pricing in May 2025 — a "utility" category template is now billed per delivered message in India, so a four-recipient summary costs next to nothing each Monday. And n8n shipped v1.62 in October 2025 with a stabilised Google Sheets node that handles range references without the v0.x quirks. The cost-of-doing-this dropped at roughly the same time the cost-of-not-doing-this (a 45-minute meeting × 7 people × 4 weeks) became visible to the founder during a quarterly review.

A reddit thread from r/n8n in October 2025 ("anyone else replacing standups with n8n?") shows the same pattern across three small Indian shops, including a Surat textile trader who runs the equivalent flow on a Hetzner box.

The 4-block workflow

CRON
Block 1: Schedule Trigger
Runs every Monday at 07:50 IST. We pad 10 minutes vs the 8 am founder breakfast routine — gives a buffer for retries.
SHEETS
Block 2: Sheets read + filter
Pulls last 7 rows from the daily sheet, filters where date is Saturday or Sunday. Returns 4-6 rows depending on shop hours.
CALC
Block 3: Compute + format
A single Code node runs 6 reductions over the rows. We chose Code instead of stacked Set nodes because debugging one JS function is simpler than 6 wired Set nodes.
WA
Block 4: WhatsApp send + log
Loops over 4 phone numbers, sends a pre-approved utility template, appends a row to a log sheet for each delivery.

The actual n8n node JSON (3 critical nodes)

This is exactly what lives in the workflow JSON. Open n8n, paste the node objects into a new workflow, then wire them up.

Node 1 — Schedule Trigger

json
{
    "parameters": {
      "rule": {
        "interval": [
          {
            "field": "cronExpression",
            "expression": "50 7 * * 1"
          }
        ]
      },
      "timezone": "Asia/Kolkata"
    },
    "name": "Monday 7:50 IST",
    "type": "n8n-nodes-base.scheduleTrigger",
    "typeVersion": 1.2,
    "position": [240, 300]
  }

Two things to notice. The cron expression is 50 7 1 — minute 50, hour 7, every month, every day, Monday only. The timezone field is critical — without it, n8n uses the server timezone (UTC if you are on Hetzner) and your Monday 7:50 IST run goes off at 1:20 pm. We made that mistake on the second client and had to dig through the logs to figure out why nothing fired on Monday.

Node 2 — Google Sheets read

json
{
    "parameters": {
      "authentication": "serviceAccount",
      "operation": "read",
      "documentId": {
        "__rl": true,
        "mode": "id",
        "value": "1xJ_REDACTED_SHEET_ID"
      },
      "sheetName": {
        "__rl": true,
        "mode": "name",
        "value": "Daily_Sales"
      },
      "options": {
        "rangeDefinition": "specifyRange",
        "range": "A2:H8"
      }
    },
    "name": "Read last 7 days",
    "type": "n8n-nodes-base.googleSheets",
    "typeVersion": 4.5,
    "position": [460, 300]
  }

We use a service-account credential, not OAuth2 — the workflow runs unattended and OAuth2 token-refresh failures during a long weekend would silently break the Monday job. Service-account creds need the sheet shared with the bot's email (looks like yourbot@yourproject.iam.gserviceaccount.com). The range A2:H8 reads exactly 7 rows after the header — keeps the payload tiny.

Node 3 — Code node (the 6-reduction)

json
{
    "parameters": {
      "jsCode": "const rows = items.map(i => i.json);\nconst weekend = rows.filter(r => {\n  const d = new Date(r.date).getDay();\n  return d === 0 || d === 6;\n});\nconst gross = weekend.reduce((s, r) => s + Number(r.amount || 0), 0);\nconst returns = weekend.reduce((s, r) => s + Number(r.returns || 0), 0);\nconst byDay = weekend.reduce((m, r) => { m[r.date] = (m[r.date]||0) + Number(r.amount); return m; }, {});\nconst worstDay = Object.entries(byDay).sort((a,b) => a[1]-b[1])[0];\nconst skuCount = weekend.reduce((m, r) => { m[r.top_sku] = (m[r.top_sku]||0) + Number(r.qty); return m; }, {});\nconst topSku = Object.entries(skuCount).sort((a,b) => b[1]-a[1])[0];\nconst tickets = weekend.reduce((s, r) => s + Number(r.tickets||0), 0);\nconst avgTicket = tickets ? Math.round(gross / tickets) : 0;\nconst upi = weekend.reduce((s, r) => s + Number(r.upi_pct||0), 0) / weekend.length;\nreturn [{\n  json: {\n    gross: gross.toLocaleString('en-IN'),\n    net: (gross - returns).toLocaleString('en-IN'),\n    returns: returns.toLocaleString('en-IN'),\n    worstDay: worstDay ? worstDay[0] + ' (₹' + worstDay[1].toLocaleString('en-IN') + ')' : 'n/a',\n    topSku: topSku ? topSku[0] + ' (' + topSku[1] + ' units)' : 'n/a',\n    avgTicket: avgTicket.toLocaleString('en-IN'),\n    upi: Math.round(upi) + '%'\n  }\n}];"
    },
    "name": "Compute 6 numbers",
    "type": "n8n-nodes-base.code",
    "typeVersion": 2,
    "position": [680, 300]
  }

The output is a single item with 7 fields — gross, net, returns, worstDay, topSku, avgTicket, upi. We picked these after watching 4 weeks of standups and noting which numbers founders actually argued about.

Setting up the credentials (15 minutes, do this first)

1
Google Cloud — service account + sheet share
In Google Cloud Console, create a project, enable the Google Sheets API, create a service account, generate a JSON key. Open the target sheet, click Share, paste the service-account email, give Editor access. In n8n, paste the JSON key into a new "Google Service Account" credential.
2
Meta Business — WhatsApp Cloud API token
Create a Meta Business app at developers.facebook.com, add the WhatsApp product, generate a permanent system-user access token (not a 24-hour temp one). Note the Phone Number ID. In n8n, create a "WhatsApp Business Cloud" credential with the token and ID.
3
Approve the message template
Templates with variables need pre-approval. Submit a "weekend_sales_rollup" template under category Utility (not Marketing — utility is cheaper and not bound to user opt-in). Approval takes 2-24 hours. We submit Friday afternoon and ship Monday.
4
Recipient opt-in
Each founder phone number needs a recorded opt-in message (anything from them to the business number works). Without opt-in, the first message bounces with code 131047. We log opt-ins in the same Google Sheet for audit trail.
Service-account JSON keys leak fastest. The.json file Google gives you contains a private key. Drop it directly into n8n's credentials UI — never commit it to git, never paste it into Slack. We have seen exactly one leaked key in a public repo cause an unexpected GCP charge in a weekend. Rotate every 90 days.

The WhatsApp template — the exact body

n8n's WhatsApp Business Cloud node uses Meta's template syntax. Submit this in the Meta Business Manager template editor:

code
Header (TEXT): Weekend roll-up — Indore store
  
  Body:
  Hi {{1}},
  Sat-Sun gross: ₹{{2}}
  Net (after returns): ₹{{3}}
  Returns: ₹{{4}}
  Worst day: {{5}}
  Top SKU: {{6}}
  Avg ticket: ₹{{7}}
  UPI share: {{8}}
  
  — Auto from n8n at 7:50 IST. Reply STOP to mute.
  
  Footer: Softechinfra workflow

The template needs 8 variables. n8n's WhatsApp node maps them by index — {{1}} is the recipient first name, {{2}} is gross, etc. Keep the template under 1,024 chars or it will fail Meta review.

The cost — from one month of running

Monthly run cost — Oct 2025 Hetzner CX22 share (1/8th of the box) WhatsApp utility templates (16 sends) Google Sheets API — free tier (~200 reads/mo)

The Hetzner box runs 7 other client workflows in parallel — we apportion 1/8th. WhatsApp sends are cheap because Utility templates in India are the low-cost category. If you bumped this to a Marketing template (e.g., a sales push to customers), you would pay about 7x more per message. Categorise correctly.

The pre-flight checklist (we run this every time)

  • Cron expression timezone is set to Asia/Kolkata, not server-default
  • Google service-account JSON pasted into n8n, sheet shared with bot email
  • WhatsApp template approved with category Utility (not Marketing)
  • Recipient phone numbers verified opted-in (test message returns code 131000)
  • Code node returns valid JSON for an empty weekend (we test by clearing rows)
  • Error workflow set on the main workflow's Settings panel — points to a "send-to-ops" sub-flow
  • Log sheet has columns date, recipient, status, message_id, error
  • Workflow is saved, activated, and a manual test run completed in the last 24 hours
  • Backup of workflow JSON exported to git the day of go-live
  • Founders briefed on STOP keyword and how to mute

Common mistakes — symptoms first, fixes second

Symptom: "the workflow ran but nothing arrived." Cause: template not approved yet, or wrong category. Fix: log into Meta Business Manager, check the template status. If it shows Pending, wait. If Rejected, read the rejection reason — usually a banned word like "free" or "act now". Resubmit under Utility, not Marketing.

Symptom: "Monday at 7:50 nothing happens, nothing in logs." Cause: cron timezone is the server default (UTC), not IST. Fix: open the Schedule Trigger, set the timezone field explicitly to Asia/Kolkata. Save and re-activate.

Symptom: "first message goes through, others bounce with 131047." Cause: 24-hour opt-in window. Recipient sent a message to your business number more than 24 hours ago. Fix: use a template message (not a freeform one) — templates bypass the 24-hour rule for utility category. Our flow uses templates exclusively.

Symptom: "the Code node returns NaN for everything." Cause: Sheets returns empty cells as the string "", not as zero. Number("") is 0 but Number(undefined) is NaN. Fix: the || 0 fallback in the Code node above guards both cases. We left it in because we got bitten on a public-holiday weekend.

Symptom: "founders complain that Monday after a public holiday is wrong." Cause: long weekends include Monday in the dates we should have summed. Fix: change the filter to "last 3 days" if today is Tuesday after a Monday holiday, or maintain a simple holidays sheet and exclude business days. We added a 3-row "exceptions" tab in the same Google Sheet.

The mini case study — Indore retail SMB, 11 weeks live

The first install ran at the Indore client from August 11, 2025. Eleven Mondays later: zero missed runs, two template-rejection incidents (both fixed within 4 hours), one opt-in bounce we caught in the log sheet on day 4. Founders dropped the Monday standup permanently in week 3 — recouped 4 hours per week of senior team time. We extended the same workflow to a Coimbatore D2C client in October; their version has 17 nodes because they wanted a separate breakdown by sales channel (Shopify vs. retail vs. WhatsApp orders).

For more context on how we structure these flows, see our n8n receptionist build and the broader AI automation services page. The Indore project is one of several recent SMB engagements where the Softechinfra team has replaced a recurring meeting with a workflow.

When not to build this

Skip this workflow if (a) your weekend sales data is not already in a structured Sheet — building the data-entry hygiene takes 4x longer than the n8n piece, (b) your team is under 5 people and standups already take under 10 minutes — there is no time-saving to capture, or (c) your founders prefer face-to-face — automation does not fix a meeting that exists for political reasons. We turned down two clients in 2025 for reason (c). The workflow was technically easy; the human change wasn't.

For a similar small-team setup we shipped, see the Radiant Finance lead pipeline — same n8n stack, different use case.

FAQ

How long to build and deploy this n8n workflow end-to-end?

A first-time install takes 2 working days for us — half a day on Meta Business Manager template approval, half a day on the n8n flow, one day on testing across edge cases (empty weekend, public holiday, founder opt-out). For a self-build, budget 5-7 days including time spent on first-time WhatsApp Cloud API setup.

Can I run this on n8n Cloud instead of self-hosted?

Yes. n8n Cloud Starter handles 2,500 executions, well above what this single weekly workflow needs. The trade-off: cloud counts every WhatsApp status webhook (delivered, read) as an execution, so a busier installation can hit limits fast. Self-host if you plan to run more than 3-4 chatty workflows.

What if WhatsApp template review is rejected?

Most rejections come from words flagged as marketing — "discount", "free", "limited time". Strip those, add the explicit phrase "transactional update", resubmit. Approval is usually within 4 hours on the second submission. We have a checklist of safe phrases shared on request.

How do I test the workflow without spamming founders?

Use Meta's test phone number (provided in your WhatsApp Cloud API setup) for the first 5 runs. It accepts any inbound and shows the rendered message in your developer console. Switch to real numbers only after the template renders correctly twice in a row.

What is the right error-handling pattern for a Monday-only workflow?

Set an Error Workflow on the main workflow's settings — point it to a 3-node sub-flow that posts to a Slack channel and writes a row to an "errors" sheet. We do not retry the main flow automatically — Monday-only flows that fail benefit from human triage; an unattended retry could send the wrong week's numbers if the Sheet was being edited mid-run.

Why Google Sheets and not Airtable or Postgres?

Because the shop floor already filled the Sheet daily. Whatever data lives where the team naturally enters it — that is what your workflow reads. Migrating data entry to a "better" tool is a separate, much harder project. For a small retail team, Sheets wins because it is the path of least resistance.

Can this run on the Indian-region n8n cloud?

n8n Cloud regions are EU and US. Latency from India is 200-280 ms — fine for a once-a-week workflow. For latency-sensitive flows (real-time receptionists, live payment alerts) we self-host on Hetzner Singapore or AWS Mumbai instead.

Want this exact n8n workflow built and deployed?

We ship the weekend roll-up flow — n8n on your Hetzner box, Sheets read, WhatsApp template approval, founder onboarding — in 7 working days. Suitable for any retail or D2C SMB with 5+ founders or department heads in a recurring Monday meeting.

Book a 20-min Call

For the founder-perspective on why we automate small recurring meetings before complex workflows, see Vivek's writeup.

Tags:
n8nWhatsApp Cloud APIGoogle SheetsWorkflow AutomationIndian SMBProductivityTutorial
Share this post:
Hrishikesh Baidya

Hrishikesh Baidya

CTO at Softechinfra specializing in Python, system architecture, and building secure, scalable software solutions.