How to Generate a Weekly Best-Seller Restock Report from Shopify and NetSuite

Build a Spojit workflow that runs every week, measures which products sold fastest in Shopify, checks current stock-on-hand in NetSuite, and emails an agent-prioritized restock recommendation to your operations team.

What This Integration Does

Replenishment decisions usually mean exporting a sales report from your storefront, opening a separate stock report from your ERP, and reconciling the two by hand in a spreadsheet. This workflow does that reconciliation automatically: it pulls last week's paid orders from Shopify, calculates sales velocity per product, looks up the matching item's quantity available in NetSuite, and asks an Agent-mode Connector node to rank the items most at risk of stocking out. The result lands in your inbox as a ready-to-act restock list, so buyers spend their time placing purchase orders instead of building the report.

A Schedule trigger fires the workflow on a weekly cron. Each run reads the most recent seven days of Shopify orders, derives a per-SKU sold quantity, enriches each line with NetSuite stock-on-hand, and passes the combined dataset to a Connector node in Agent mode for prioritization. The final report is delivered by a Subworkflow node that wraps a reusable Send Email step, so any workflow in your workspace can reuse the same delivery logic. The workflow is read-only against both systems: it never changes orders or inventory, so re-running it is always safe and simply produces a fresh snapshot for the current week.

Prerequisites

  • A Shopify connection with read access to orders and products. See the Shopify connector article for setup.
  • A NetSuite connection with read access to items and SuiteQL queries. See the NetSuite connector article.
  • A shared SKU between the two systems: the Shopify variant SKU must match the NetSuite item's itemid (item name) so the workflow can join the two records.
  • Recipient addresses for the report added to your org allowlist under Settings > General > Email recipients, since the report is delivered through Spojit's built-in mail service.
  • A second, reusable workflow that accepts a subject and body and sends an email (built in Step 6 below), so the report can be delivered through a Subworkflow node.

Step 1: Add a Schedule trigger

Start a new workflow in the Workflow Designer and add a Trigger node, then set its type to Schedule. A Schedule trigger uses a 5-field Unix cron expression plus an IANA timezone. For a Monday 7am report, set the cron to 0 7 * * 1 and the timezone to your operations region, for example Australia/Sydney. The trigger output is {{ scheduledAt }}, which you can use later to stamp the report with its run date. A single Schedule trigger can hold more than one schedule, so add a second entry if you also want a mid-week refresh.

Step 2: Pull last week's paid orders from Shopify

Add a Connector node in Direct mode, choose your Shopify connection, and select the list-orders tool. This tool accepts a Shopify search query and returns paginated results. Restrict it to paid orders from the last seven days by setting the query field with both a status filter and a date filter, and raise first toward its maximum of 250 to reduce paging:

query: financial_status:paid created_at:>2026-06-13
first: 250

If your store regularly exceeds 250 orders a week, capture the returned after cursor and wrap this node in a Loop node (While mode) that keeps calling list-orders until no further cursor is returned, accumulating the line items as you go. Store the result in a variable such as {{ orders }}.

Step 3: Calculate sales velocity per SKU

Add a Transform node to flatten the orders into a per-SKU sold quantity. Walk every order's line items, group by the variant SKU, and sum the quantities so you end up with one row per product. The output should be a compact array your later steps can iterate, for example:

[
  { "sku": "TS-BLK-M", "title": "Black Tee (M)", "soldQty": 142 },
  { "sku": "MUG-CER-01", "title": "Ceramic Mug", "soldQty": 97 }
]

If your grouping logic is more involved, use a Connector node with the built-in Code Runner connector and its execute-javascript tool to reduce {{ orders }} into the same shape. Save this as {{ velocity }}. Sorting by soldQty here lets you cap the report to your top movers and keep the NetSuite lookups small.

Step 4: Enrich each product with NetSuite stock-on-hand

Add a Connector node in Direct mode using your NetSuite connection and the run-suiteql tool. SuiteQL is a SQL-like query against NetSuite, which is ideal for reading quantity available across many items in one call instead of fetching them one at a time. Pass the SKUs you collected in {{ velocity }} into an IN clause so you only retrieve the items that actually sold this week:

SELECT itemid, quantityavailable, quantityonorder
FROM item
WHERE itemid IN ('TS-BLK-M', 'MUG-CER-01')

Set the limit field high enough to cover your top movers (max 1000 rows per page). Then add a Transform node that joins the SuiteQL rows back onto {{ velocity }} by matching the Shopify SKU to the NetSuite itemid, producing one combined record per product with soldQty, quantityavailable, and quantityonorder. Save this as {{ restockData }}. If you prefer per-item reads over a single query, you can loop over the SKUs and call the NetSuite get-item tool instead, at the cost of more calls.

Step 5: Prioritize the restock list with Agent mode

Add a Connector node in Agent mode. Agent mode lets the agent reason over the combined dataset and decide which items are most urgent, rather than you hard-coding a single threshold. Feed it {{ restockData }} and a prompt that explains the goal, and attach a Response Schema so the output is reliable structured JSON instead of prose. A prompt like the following works well:

You are preparing a weekly restock report. For each product you are given
its units sold in the last 7 days (soldQty), its NetSuite quantity available
(quantityavailable), and quantity already on order (quantityonorder).
Estimate weeks of cover (quantityavailable / soldQty), flag any item with
less than 2 weeks of cover as HIGH priority, and recommend an order quantity
that restores roughly 4 weeks of cover. Return the items sorted by priority.

A matching Response Schema might return an array of objects with fields such as sku, title, weeksOfCover, priority, and recommendedOrderQty. Save the result as {{ recommendations }}. Because Agent mode costs AI credits, keeping the input limited to your real movers from Step 3 keeps each run inexpensive.

Step 6: Build a reusable email Subworkflow and deliver the report

Create a separate workflow named something like Send Report Email with a Manual trigger and a single Send Email node. In that child workflow, map the Recipients, Subject, and Body fields to values passed in through the trigger input, for example {{ input.recipients }}, {{ input.subject }}, and {{ input.body }}. Set If sending fails to Fail the workflow so a delivery problem surfaces clearly. This is now a reusable delivery block any workflow can call.

Back in your restock workflow, add a Transform node that renders {{ recommendations }} into a readable email body (a short header line with {{ scheduledAt }} followed by one line per product), then add a Subworkflow node. Pick the Send Report Email workflow in its Workflow field and pass the rendered values in its Input:

recipients: ops-team@yourcompany.com
subject: Weekly Restock Report - {{ scheduledAt }}
body: {{ reportBody }}

The parent pauses while the child runs through its own trigger and Send Email node, then returns control. The child run appears as its own entry in execution history, which keeps your delivery logic auditable and lets you reuse it from other reports without rebuilding it. To learn more about this pattern, see the reusable subworkflow library tutorial.

Tips

  • Use the scheduledAt value from the trigger to compute your date filter dynamically with the built-in Date & Time Tools connector, so the created_at:> window always covers the previous seven days instead of a hard-coded date.
  • Cap the report to your top movers by sorting {{ velocity }} in Step 3 and slicing the first 50 rows. Fewer SKUs means a smaller SuiteQL query and a cheaper Agent mode call.
  • Keep the Agent mode prompt focused on ranking and quantities only. Anything deterministic, like the weeks-of-cover math, can be done in a Transform node so the AI step stays small and predictable.
  • Because the workflow is read-only, you can run it on demand from the Run button any time a buyer asks for a fresh snapshot, not only on its schedule.

Common Pitfalls

  • SKU mismatch: the join in Step 4 only works when the Shopify variant SKU equals the NetSuite item itemid. Items present in one system but not the other will silently drop out, so log unmatched SKUs in your Transform node.
  • Timezone drift: a Schedule cron runs in the IANA timezone you set, but the Shopify created_at filter is evaluated in store time. Confirm both align so a "last 7 days" window does not clip a day at the boundary.
  • Pagination: list-orders returns at most 250 orders per call. High-volume stores must follow the after cursor in a loop, or the velocity numbers will undercount.
  • Recipient allowlist: the Send Email node inside your subworkflow can only reach external addresses that are on the org allowlist. Add the report's recipients first, or the child run will fail.

Testing

Before enabling the schedule, validate the pipeline on a small scope. Temporarily narrow the Shopify query to a single recent day and a low first value, then trigger the workflow with the Run button. Step through the execution history to confirm {{ velocity }} contains the SKUs you expect, that the SuiteQL step returns matching quantityavailable values, and that {{ recommendations }} is well-formed JSON. Point the Subworkflow at your own address first so you can read the rendered report end to end. Once the output looks right, restore the seven-day query, confirm the recipient list, and let the Schedule trigger take over.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.