automations.help

Reporting & dashboards

Send a daily revenue digest to Slack

Every morning starts the same way: open Shopify, wait for the dashboard to load, read yesterday's number, close the tab. This does it for you. Once a day it pulls yesterday's orders from Shopify, totals net sales, order count, AOV, and units, compares it to the same weekday last week so the number actually means something, and posts a clean digest to a Slack channel before you've finished your coffee. Build it yourself with the steps below, or we build it on your own accounts, wire the math and the comparison, and hand you the keys so you own it with no monthly fee. Free teardown of what you check by hand every morning first.

Difficulty
Intermediate
Time to build
2-4 hrs
Updated
  • Shopify
  • Slack

The manual way today

Someone — usually the founder — opens Shopify first thing, lands on the analytics overview, and eyeballs yesterday's sales, order count, and maybe AOV. Then they do the comparison in their head: is that good for a Tuesday, or just okay? On a busy week the same numbers get read out loud in Slack so the rest of the team has them, which means the founder has now become a human reporting tool, copying figures from one screen into another every morning.

It's five minutes a day, so it never feels worth fixing — but it's five minutes gated behind one specific person logging into one specific tool, which means on the days they're traveling, sick, or just slow to start, nobody else has yesterday's numbers. The deeper cost is that a raw figure with no baseline doesn't actually inform anything: '$12k yesterday' only means something next to last Tuesday, and a glance at the dashboard rarely gives you that. So the ritual gets done daily and tells you almost nothing — the worst kind of busywork, the kind that feels like staying on top of the business while quietly not.

What running looks like

Every morning, before anyone logs in, a digest lands in a Slack channel: yesterday's net sales with the change versus the same weekday last week, the order count, AOV, units sold, refunds, the top few products, and the new-versus-repeat split — with a link to the Shopify dashboard for anyone who wants to dig. It posts once a day, in your store's timezone, reconciles with what Shopify's own Analytics shows, and posts even on a zero-sales day or an API hiccup so an empty channel never gets mistaken for a quiet morning.

01The build

How to build it

  1. Decide the numbers, the window, and the baseline — on paper first

    Needs a human

    Name the three-to-five metrics that would actually change a decision: net sales, order count, AOV, units, refunds, maybe the new-versus-repeat split. Then define 'yesterday' as a precise window in your store's timezone, and pick the comparison baseline — for DTC that is almost never yesterday, because sales are weekly-seasonal and a Monday only makes sense against last Monday. Finally, write down which Shopify sales figure you're reporting: gross, net (gross minus discounts and returns), or total (net plus shipping and tax). Pinning that down now is what lets the finished digest reconcile with Shopify Analytics instead of being a fourth number nobody trusts.

  2. Schedule the run for early morning in your store's timezone

    Runs itself

    Use an n8n Schedule Trigger (cron) set to fire once, early — say 07:00 store time. Build yesterday's window from that: yesterday 00:00:00 through 23:59:59 in the store's timezone, formatted as ISO 8601 with the offset (for example 2026-06-28T00:00:00-07:00 to 2026-06-28T23:59:59-07:00). Don't cut the day on UTC midnight, or a chunk of last night's sales lands in the wrong digest. Derive the offset from the timezone rather than hardcoding it, so the window doesn't drift by an hour twice a year when daylight saving flips.

  3. Pull yesterday's orders from Shopify — all of them

    Runs itself

    Call the Admin API with an HTTP Request node: GET /orders.json?status=any&created_at_min=<start>&created_at_max=<end>&limit=250 (or the GraphQL orders connection with query: "created_at:>='<start>' created_at:<'<end>'"). Use status=any so you can decide later what to include rather than letting Shopify pre-filter it. The part everyone forgets: paginate. A good day blows past 250 orders, and Shopify hands the rest back behind a cursor — the Link header's page_info on REST, or pageInfo.endCursor on GraphQL — so loop until there's no next page. Skip this and your digest silently caps at 250 and undercounts every busy day, which is the worst way to be wrong. Pull total_price, subtotal_price, total_discounts, total_tax, financial_status, cancelled_at, test, line_items[].quantity, customer.orders_count, and source_name.

  4. Compute the metrics so they reconcile with Shopify

    Runs itself

    Sum across the orders you pulled: order count, net sales, AOV (net ÷ count), units (the total of line-item quantities), discounts given, and first-time versus repeat (read the customer's order count — 1 means a new customer). Decide explicitly what to drop: exclude test:true orders so dev and QA orders never inflate the number, and decide whether cancelled orders count toward the total. Report the same figure Shopify's Analytics headlines — its net sales is gross minus discounts minus returns — and if your total doesn't match the dashboard, you're summing a different field, not chasing a bug. Reconciling once now is what makes the team trust the digest forever.

  5. Decide how refunds count — the timing trap

    Needs a human

    A refund processed yesterday almost always belongs to an order placed days ago, so it never shows up in yesterday's order list. That mismatch is the single most common reason a homemade digest never lines up with the dashboard. Pick one basis and label it: placement basis (sales from orders placed yesterday, refunds left out) or cash basis (yesterday's orders minus refunds actually processed yesterday, pulled from the refunds endpoint and filtered on processed_at). Don't blend them. Then put the words 'net of refunds' or 'before refunds' right in the Slack message so no one ever has to guess which number they're reading.

  6. Add the comparison that makes a number mean something

    Runs itself

    A bare '$12,430' tells you nothing on its own. Pull the same weekday last week using the exact same window logic, and show the delta with direction — ▲ 8% vs last Monday. Optionally add month-to-date against the same day-count last month, so a soft morning inside a strong month reads correctly instead of setting off alarms. This step is the whole difference between a number you glance at and a number you can act on — and it's the part the Shopify mobile app won't put in front of you at a glance.

  7. Build and post the Slack digest

    Runs itself

    Assemble a Block Kit message and post it to a dedicated channel like #daily-numbers — via a Slack incoming webhook for the simple case, or chat.postMessage if you want threading or @-mentions. Lead with the date and the headline net sales plus its delta, then a compact row (orders · AOV · units · refunds), then the top three products by revenue and the new-versus-repeat split, and a button linking straight to the Shopify Analytics overview for anyone who wants the full picture. Keep it skimmable on purpose: the goal is reading it in five seconds, not rebuilding the dashboard inside Slack.

  8. Make it reliable — idempotent, self-healing, loud on $0 and errors

    Runs itself

    A scheduled job that fails silently is worse than no job, because an empty channel looks exactly like a quiet sales day. Store the last date you posted (n8n's static workflow data, a cell in a Sheet, or a dedupe key) and skip if it already ran, so a retry or a restart can't double-post. If a morning got missed because n8n was down, backfill the gap on the next run or at least flag it. Wrap the Shopify calls so an API error posts 'couldn't pull yesterday's numbers' to the channel instead of dying quietly, and post '0 orders yesterday' explicitly on a genuine zero day. The entire reason you built this is to stop checking by hand — so the absence of a message must never be allowed to mean something.

02The stack

What it’s built on

Shopify
System of record for the numbers. The Admin API serves yesterday's orders (and refunds, if you go cash-basis), and Shopify's own Analytics is the figure your digest has to reconcile against so the team trusts it. Shopify Flow now has a scheduled trigger, but it can't loop over yesterday's orders, paginate them, and do the math — so this one needs real glue rather than Flow alone.
Slack
Where the digest lands so the whole team has yesterday's numbers without anyone logging in. A free incoming webhook covers the daily post; Block Kit gives you the skimmable card with the delta and the top products; chat.postMessage adds threading or @-mentions if you want them.Incoming Webhooks and the Slack API are free on every plan, including the free tier — there's no upcharge to receive this post.
n8n
The glue that does all the work: a Schedule node fires it each morning, paginated HTTP Request nodes pull the orders, a Code/Set node runs the totals and the same-weekday comparison, and a Slack node posts the card — plus the idempotency guard and the error-and-zero handling.n8n is open-source and self-hostable for free, which covers this end to end; their cloud tier just saves you running the server yourself. This is a once-a-day, low-volume workflow, so it sits comfortably inside the smallest plan either way.

03Questions

Before you build

Can't I just check Shopify's analytics or the mobile app?

You can — and that's exactly the daily login this removes. The difference is push versus pull: the digest comes to you in the channel the team already lives in, on a schedule, with the same-weekday comparison and top products laid out, so nobody has to open a tool and do the mental math. The Shopify dashboard is still there for the day you want to dig; the button in the message links straight to it. This just means most mornings you don't have to.

Why compare to the same weekday last week instead of to yesterday?

Because DTC sales are weekly-seasonal, so yesterday is the wrong yardstick. A Monday against a Sunday, or a Friday against a Thursday, is apples to oranges — you'll read a normal weekday swing as a crisis or a win that isn't real. The same weekday last week strips the day-of-week pattern out, so the delta you see is an actual change in the business rather than the calendar. Month-to-date versus last month is a useful second line for the same reason.

Will the digest match my Shopify dashboard exactly?

It will, if you do two things: sum the same field Shopify headlines (its net sales is gross minus discounts minus returns) and pick a single refund basis instead of blending placement and cash. The usual cause of a mismatch isn't a bug — it's reporting 'sales from orders placed yesterday' while the dashboard you're comparing to nets out refunds processed yesterday, or summing total_price when Shopify is showing net. Reconcile once against a known day and label your number, and it lines up from then on.

What timezone does 'yesterday' use, and why does it matter?

Your store's timezone, set explicitly in the query window — not UTC. If you cut the day on UTC midnight, late-night orders fall on the wrong side of the line and get counted in the next day's digest, so your numbers never match what Shopify shows. Build the window in store-local time with the correct offset, and derive that offset from the timezone so it stays right through daylight-saving changes rather than drifting an hour twice a year.

What happens on a day with no sales, or if the pull fails?

It still posts. A silent channel is ambiguous — did you make $0, or did the job break? — and that ambiguity defeats the whole purpose, because the thing you stopped doing was checking. So the build posts '0 orders yesterday' explicitly on a genuine zero day, and wraps the Shopify calls so an API error posts a clear 'couldn't pull yesterday's numbers' instead of failing quietly. The absence of a message is never the signal.

Can I build this myself, or should you build it?

Every node, field, window, and edge case is written out above on purpose — if you've got a few hours and some API comfort, build it yourself and own it outright. If you'd rather not own the pagination, the timezone window, the refund basis, and the idempotency guard, we'll build it on the Shopify, Slack, and n8n accounts you already pay for, reconcile it against your dashboard, document every piece, and hand you the keys. You own the workflow — no monthly retainer, and nothing stops working if you walk away. It starts with a free 20-minute teardown of what you check by hand each morning, not a contract.

Reporting & dashboards

Automate a daily ad spend vs revenue report

To know if yesterday's ad spend paid off, someone opens Ads Manager for the spend, opens Shopify for the revenue, and does the division in their head — across two timezones and two definitions of a 'sale.' This does it for you. Once a day it pulls yesterday's spend from Meta, yesterday's revenue from Shopify, lines the two day-windows up so they're actually comparable, computes blended ROAS (and shows Meta's own attributed ROAS next to it so the gap is visible), and posts one clean line to Slack. Build it yourself from the steps below, or we build it on your own accounts, reconcile the windows and the currency, and hand you the keys so you own it with no monthly fee. Free teardown of how you check whether ads are working first.

  • Meta Ads
  • Shopify
  • Slack

Intermediate3-5 hrs

Reporting & dashboards

Post a daily Stripe payouts and fees summary to Slack

Stripe deposits money to your bank on a rolling schedule, nets out refunds and disputes, skims its fee, and tells you almost none of it unless you log in and dig. This closes that gap. When a payout lands, it pulls the exact transactions that make up the deposit, totals the gross, the fees, the refunds, and the net, works out your effective fee rate, and posts a clean summary to Slack — so the number in your bank account finally has a story attached. Build it yourself with the steps below, or we build it on your own Stripe and Slack accounts, reconcile it to the cent against your dashboard, and hand you the keys so you own it with no monthly fee. Free teardown of what you reconcile by hand first.

  • Stripe
  • Slack

Intermediate3-5 hrs

Reporting & dashboards

Build a real-time sales dashboard in Google Sheets

Every week you export a Shopify CSV, paste it into the same spreadsheet, fix the columns, and rebuild the same pivot — to land on a number you could have had automatically. This wires it up instead: each paid order appends itself to a raw log tab in Google Sheets within a minute or two, a scheduled reconcile job catches anything the webhook missed, and a dashboard tab built on QUERY formulas recalculates the moment a row lands. Build it yourself with the steps below, or we build it on your own Shopify and Google accounts, reconcile it against your Shopify Analytics, and hand you the keys so you own it with no monthly fee. Free teardown of the report you rebuild by hand first.

  • Shopify
  • Google Sheets
  • n8n

Intermediate3-5 hrs

Reporting & dashboards

Auto-alert on a sudden drop in sales

When checkout breaks or an ad set gets paused, orders don't error out — they just stop, and the store looks fine until the next morning's numbers come in low. This watches the order flow in near-real-time, learns what a normal Tuesday 2pm looks like for your store, and pings a Slack channel within the hour when sales fall off a cliff against that baseline — with a triage checklist so someone can go look before the lost revenue stacks up. Build it yourself with the steps below, or we build it on your own accounts — the baseline, the sustained-breach logic, the cooldown so it never spams you — and hand you the keys so you own it with no monthly fee. Free teardown of where your store actually goes quiet first.

  • Shopify
  • Slack

Advanced4-8 hrs