How to Sync Emailed Supplier Stock Updates into WooCommerce with a Mailhook
Let suppliers email a stock-level CSV to a dedicated Spojit mailhook address and have each row update your WooCommerce product inventory automatically, with no portal logins or manual re-keying.
What This Integration Does
Many suppliers send daily or weekly stock availability as a CSV attached to an email: one row per product, usually keyed by SKU, with a current quantity. Keying those numbers into WooCommerce by hand is slow and error prone, and a stale on-hand count means you oversell items that are actually out of stock. This tutorial builds a Spojit workflow that receives the supplier email at a unique mailhook address, reads the attached CSV, matches each row to the right WooCommerce product by SKU, and writes the new stock quantity back to your store.
The run model is push based: the moment a supplier emails the mailhook address, Spojit starts a run (seconds later, no mailbox polling and no OAuth). The Mailhook trigger exposes the message as {{ input }}, an Attachment node pulls the CSV bytes, the csv connector turns it into rows, a Loop walks the rows, and a Connector node updates each WooCommerce product. The workflow is async, so there is no reply to the sender unless you add a Send Email step. Each message is deduplicated, so a forwarded or retried copy of the same email will not double-process. Re-runs are idempotent in effect because you write an absolute quantity (set stock to N), not a delta.
Prerequisites
- A WooCommerce connection in Spojit (Connections → Add connection → WooCommerce) with REST API credentials that have read and write access to products.
- Your supplier feed must include a value that maps to your WooCommerce
skufield, plus a stock quantity column. Confirm the exact column headers (for exampleskuandquantity) before you build. - Products in WooCommerce that have Manage stock enabled and a populated
sku, so quantity writes actually take effect. - A supplier (or an internal forwarding rule) willing to send the CSV to a fixed address that you provide. The csv connector is built in and needs no connection.
Step 1: Create the workflow and add the Mailhook trigger
Create a new workflow, then on the trigger node set Trigger Type to Mailhook. Optionally set an Address prefix (1 to 24 characters, default mh) such as supplier-stock so the address is recognizable. Click Generate email address to mint a unique address of the form supplier-stock-<random16>@mailhook.spojit.com, then copy it and give it to your supplier. To reduce noise, add a From allowlist containing the supplier's sending domain or address, and optionally a Subject regex such as stock|inventory. The trigger fires whether the address is in To, Cc, or Bcc, and the parsed message is available downstream as {{ input }} with fields like {{ input.from }}, {{ input.subject }}, and {{ input.attachments }}.
Step 2: Fetch the CSV with an Attachment node
Add an Attachment node directly after the trigger. This node only works in Mailhook workflows: it fetches the actual bytes of an attachment the trigger referenced. Set Mode to Single so you get one object back, set a Filename pattern glob that matches the supplier file (for example *.csv or stock-*.csv), and optionally set the Content type filter to text/csv. Turn on Fail if no attachment matches so a stock email that arrives without its CSV stops the run loudly instead of silently doing nothing. In Single mode the output looks like:
{
"filename": "stock-2026-06-21.csv",
"contentType": "text/csv",
"size": 18244,
"content": "<base64-encoded bytes>"
}
The base64 content field is what the next step decodes.
Step 3: Decode the CSV bytes to text
The Attachment node returns the file as base64, but the csv connector expects a plain CSV string. Add a Connector node in Direct mode, choose the encoding connector, and select the base64-decode tool. Map its input to the attachment content:
{
"input": "{{ attachment.content }}"
}
This yields the decoded CSV text (call its output variable csv_text). If your supplier always sends UTF-8 plain text, this single decode is all the preparation you need.
Step 4: Parse the CSV into rows
Add another Connector node in Direct mode, choose the csv connector, and select the to-json tool. Pass the decoded text as the csv field and keep header set to true so the first row is treated as column names:
{
"csv": "{{ csv_text }}",
"header": true,
"delimiter": ","
}
The tool returns { items, count }, where items is an array of row objects keyed by header (for example { "sku": "ABC-100", "quantity": 42 }) and count is the number of rows parsed. Name the output variable parsed so you can iterate {{ parsed.items }} next.
Step 5: Loop over rows and find each product by SKU
Add a Loop node set to ForEach over {{ parsed.items }}. Inside the loop body, add a Connector node in Direct mode using the woocommerce connector and the list-products tool, filtering by the row's SKU so you resolve the WooCommerce product ID:
{
"sku": "{{ item.sku }}",
"per_page": 1
}
This returns the matching product (or none). Add a Condition node that checks whether a product was found before you attempt to write, so unknown SKUs are skipped rather than failing the whole run. Keep the matched product's ID handy as {{ product.id }} for the write step. If your supplier feed already contains the WooCommerce product ID rather than the SKU, you can skip this lookup and use that ID directly.
Step 6: Write the new stock quantity back to WooCommerce
Still inside the loop (on the true branch of your Condition), add a final Connector node in Direct mode using the woocommerce connector. Stock quantity is written through the raw-api-request tool, which lets you set fields like stock_quantity that the simpler product tools do not expose. Send a PUT to the product path with the new on-hand count from the CSV row:
{
"method": "PUT",
"path": "/products/{{ product.id }}",
"body": {
"manage_stock": true,
"stock_quantity": {{ item.quantity }},
"stock_status": "instock"
}
}
Because you write an absolute quantity, re-processing the same file leaves the store in the same state. Optionally add a Send Email node after the loop that summarizes how many rows were applied and how many SKUs were unmatched, sending to {{ input.replyTo }} so the supplier or your team gets a receipt.
Tips
- Use Direct mode for every connector step here. The work is deterministic single-tool calls (decode, parse, look up, update), so you avoid AI credit cost and get predictable behavior. Reserve Agent mode for steps that need judgment.
- If suppliers send several CSVs in one email, set the Attachment node to
Multiplemode and add an outer Loop over{{ attachment.attachments }}, decoding and parsing each file in turn. - Keep attachments within the limits: 10 MB per attachment and 25 MB per run by default. For very large catalogs, ask the supplier to split the feed or send a delta file.
- If your suppliers all use one consistent format, ask Miraxa, the intelligent layer across your automation, to scaffold the canvas: for example "Build a workflow that watches a mailhook, fetches the CSV attachment, parses it, and updates WooCommerce stock by SKU." Then fine-tune field mappings in the properties panel.
Common Pitfalls
- Forgetting to decode. The Attachment node hands you base64, not text. Feeding
{{ attachment.content }}straight intoto-jsonproduces garbage rows. Always decode first (Step 3). - Column header drift. The
to-jsontool keys rows by the CSV's actual header text. If a supplier renamesquantitytoqty, your{{ item.quantity }}reference goes empty. Pin the expected headers and validate them. - Products without Manage stock. Writing
stock_quantityhas no visible effect unlessmanage_stockis true on the product. Set it in the sameraw-api-requestbody, as shown. - Unmatched SKUs. A SKU in the feed that does not exist in WooCommerce will return no product. Without the Step 5 Condition guard, the write step errors. Skip or log these instead of failing the entire batch.
Testing
Before pointing your real supplier at the address, send a test email to the generated mailhook address yourself, attaching a small CSV with two or three known SKUs and obviously test quantities. Open the run in execution history and inspect each step output in order: confirm the Attachment node returned the right filename, that csv_text is readable text, that {{ parsed.count }} matches your row count, and that the WooCommerce lookup resolved a product ID. Verify the new quantities in your store admin, then widen the From allowlist and hand the address to the supplier. If you ever need to cut off the old address, use Regenerate address on the trigger, which retires the previous one instantly.
Learn More
- Mailhook trigger reference in the Spojit developer docs.
- Attachment node reference for fetching attachment bytes.
- WooCommerce connector reference for the full tool catalog.
- CSV tools reference for parsing and converting CSV data.
- Setting Up a Mailhook Trigger for trigger setup details.
- How to Create Shopify Orders from PO PDFs Emailed to a Mailhook for a related mailhook plus attachment pattern.
- How to Schedule a Daily Inventory Sync for a scheduled alternative.