How to Export a Daily Wholesale Backorder Report from NetSuite to FTP and Slack

Build a Spojit workflow that runs every weekday morning, pulls open backordered sales-order lines from NetSuite, turns them into a CSV report, drops the file on your operations partner's FTP server, and posts a short count to Slack.

What This Integration Does

Wholesale operations teams live and die by backorders: lines on open sales orders where the customer has ordered more than you can currently ship. Pulling that list by hand from NetSuite every morning, formatting it into a spreadsheet, and emailing it around is slow and error-prone. This Spojit workflow does it on a schedule: it queries NetSuite for the exact backordered lines, builds a clean CSV your warehouse or 3PL partner can open directly, uploads it to a shared FTP directory, and posts a one-line summary to a Slack channel so everyone knows the report has landed and how many lines need attention.

The workflow is driven by a Schedule trigger on a weekday cron, so nothing else has to kick it off. Each run queries NetSuite fresh, so the report always reflects the current open backorder position. The CSV is written to a dated path on the FTP server (one file per run), and the Slack message reports the row count. Re-runs are safe and idempotent in effect: because each file name carries the run date, a re-run for the same day simply overwrites that day's file rather than piling up duplicates. No state is left behind in Spojit between runs.

Prerequisites

  • A NetSuite connection in Spojit with permission to run SuiteQL queries against sales-order transaction data. See Connections -> Add connection -> NetSuite.
  • An FTP connection pointing at your operations partner's server, with write access to the target upload directory.
  • A Slack connection, plus the channel ID of the channel where the summary should be posted (for example C0123ABCD).
  • Knowledge of which NetSuite saved field or formula marks a line as backordered. Most accounts can compute it from the ordered, shipped, and billed quantities on the transaction line.
  • The IANA timezone your business runs in (for example Australia/Sydney) so the schedule fires at the right local hour.

Step 1: Add a Schedule trigger for weekday mornings

Start a new workflow and set the Trigger node type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone. To run at 7:00 AM Monday through Friday in Sydney, use:

cron:     0 7 * * 1-5
timezone: Australia/Sydney

A single Schedule trigger can hold multiple schedules if you later want a second run, but one weekday-morning entry is enough here. The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} when naming the file.

Step 2: Query NetSuite for open backordered lines

Add a Connector node in Direct mode on the netsuite connector and choose the run-suiteql tool. In the query field, write a SuiteQL statement that returns only the transaction lines still backordered on open sales orders. A typical query joins the transaction and transaction-line tables and keeps lines where the ordered quantity exceeds what has shipped:

SELECT t.tranid           AS order_number,
       t.trandate         AS order_date,
       e.entityid         AS customer,
       i.itemid           AS item,
       tl.quantity        AS qty_ordered,
       tl.quantityshiprecv AS qty_shipped,
       (tl.quantity - tl.quantityshiprecv) AS qty_backordered
FROM   transaction t
       JOIN transactionline tl ON tl.transaction = t.id
       JOIN item i  ON i.id = tl.item
       JOIN entity e ON e.id = t.entity
WHERE  t.type = 'SalesOrd'
  AND  t.status IN ('SalesOrd:B', 'SalesOrd:D', 'SalesOrd:E')
  AND  tl.quantity > tl.quantityshiprecv
  AND  tl.mainline = 'F'
ORDER BY t.trandate, t.tranid

Adjust the column and status references to match your account: status keys and the exact backorder fields vary between NetSuite configurations. Set limit (max 1000 rows per page, default 100) and use offset if your daily backorder list can exceed one page. The tool returns the matching rows as a JSON array you can reference as {{ netsuite_backorders.items }} (rename the node output to suit your variables).

Step 3: Build the CSV report file

Add a Connector node in Direct mode on the csv connector and choose the from-json tool. Map the rows from the previous step into data, and pin the column order with columns so the partner always receives a stable layout:

data:      {{ netsuite_backorders.items }}
columns:   ["order_number","order_date","customer","item","qty_ordered","qty_shipped","qty_backordered"]
delimiter: ","
header:    true

Leave header on so the first row carries the column names. The tool returns the assembled CSV as a string; reference it downstream as {{ backorder_csv.result }}. If you need to sort or de-duplicate first, you can chain sort or dedupe from the same csv connector before from-json, but for a NetSuite-ordered query that is usually unnecessary.

Step 4: Capture the row count for the summary

Add a Connector node in Direct mode on the array connector and choose the length tool, pointing it at the NetSuite rows so you have an exact backordered-line count for Slack:

array: {{ netsuite_backorders.items }}

Reference the result downstream as {{ backorder_count.length }}. You can also build a dated file name here with a Connector node on the date connector using format against {{ trigger.scheduledAt }}, for example producing 2026-06-21 to embed in the FTP path.

Step 5: Upload the CSV to the operations partner's FTP server

Add a Connector node in Direct mode on the ftp connector and choose the upload-file tool. Put the CSV string into content and write to a dated path in the shared directory:

path:     /backorders/wholesale-backorder-{{ report_date.result }}.csv
content:  {{ backorder_csv.result }}
encoding: utf8

Use utf8 encoding because the CSV is plain text. The tool overwrites the file if it already exists, which is exactly what you want for a same-day re-run. If the /backorders directory may not exist yet, add a Connector node on the ftp connector with create-directory ahead of the upload, or have your partner create it once.

Step 6: Post the summary count to Slack

Add a Connector node in Direct mode on the slack connector and choose the send-message tool. Set channel to your operations channel ID and compose a short summary in text:

channel: C0123ABCD
text:    Daily wholesale backorder report uploaded. {{ backorder_count.length }} backordered line(s) for {{ report_date.result }}. File: /backorders/wholesale-backorder-{{ report_date.result }}.csv

Keep the message to a single line with the count and the file path so the channel stays scannable. If you would rather email the same summary instead of (or in addition to) Slack, add a Send Email node, which sends from Spojit's built-in mail service with no extra connection.

Step 7: Connect the nodes and save

Wire the nodes in sequence: Schedule trigger -> NetSuite run-suiteql -> CSV from-json -> the count and date helper nodes -> FTP upload-file -> Slack send-message. You can scaffold the whole chain quickly by asking Miraxa, the intelligent layer across your automation, something like "Build a workflow with a Schedule trigger that runs a NetSuite SuiteQL query, converts the rows to CSV, uploads the file with the ftp connector, and posts the row count to Slack," then fine-tune each node's fields in the properties panel.

Tips

  • Keep the SuiteQL focused: select only the columns the partner needs. Narrow queries run faster and produce a CSV that opens cleanly in any spreadsheet tool.
  • Embed the run date in the file name (Step 4) so the FTP directory becomes a self-documenting archive of one report per business day.
  • If your backorder list can grow past 1000 rows, loop the run-suiteql call with increasing offset and merge the pages with the csv connector's merge tool before uploading.
  • Use a dedicated, low-traffic Slack channel for these operational drops so the daily message is easy to find and does not get buried.

Common Pitfalls

  • Timezone drift. The cron fires in the IANA timezone you set, not the viewer's local time. Confirm Australia/Sydney (or your zone) so the report lands before staff start, especially around daylight-saving changes.
  • Account-specific backorder logic. NetSuite status keys and quantity fields differ between accounts. Validate your SuiteQL against a known backordered order before trusting the count.
  • Binary vs text encoding on FTP. A CSV is text, so use utf8 in upload-file. Choosing base64 here would corrupt the file for the partner.
  • Empty-result days. When there are no backorders, from-json still produces a header-only file and the Slack message reads 0 backordered line(s). If you would rather skip the upload entirely, add a Condition node on {{ backorder_count.length }} before the FTP step.

Testing

Before enabling the schedule, run the workflow once with the Run button (or temporarily attach a Manual trigger) and watch the execution log step by step. Confirm the SuiteQL returns the rows you expect, open the generated CSV string in the CSV node output to check the column order, then point upload-file at a test path such as /backorders/test/ and verify the file lands on the FTP server with the right contents. Post the Slack message to a private test channel first. Once each step checks out, switch the FTP path back to the live directory and the Slack channel to your operations channel, then enable the workflow so the weekday Schedule trigger takes over.

Learn More

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