How to Sell Creator Merch Drops on Shopify with a Mailhook Pre-Order Intake
Forward a supplier or fan pre-order spreadsheet to a Spojit mailhook address and let the workflow pull the CSV, map the rows, create the products on Shopify, stage their pre-order stock, and ping your team in Slack.
What This Integration Does
When you run a merch drop as a creator, the inputs usually arrive as a spreadsheet by email: a supplier sends a line sheet of the items they can produce, or you collect fan demand into a pre-order tally and export it as a CSV. Turning that file into a live storefront by hand means retyping every title, price, and quantity into Shopify, which is slow and error-prone the night a drop goes live. This Spojit workflow gives you a single email address to forward that spreadsheet to, and it stages the whole drop for you: each row becomes a Shopify product, and the pre-order count becomes that product's starting inventory at your fulfilment location.
The run model is push-based. A Mailhook trigger fires within seconds of any email landing on your generated address. The Attachment node fetches the CSV bytes, the csv connector parses them into rows, and an Agent-mode Connector node normalizes each row into a clean product shape. A Condition node gates the run so an empty or malformed file does not create junk products, then Shopify create-product and adjust-inventory stage the merch, and a Slack send-message drops a heads-up in your team channel. Products are created with status set to DRAFT by default, so nothing is publicly buyable until you review and publish: re-running on the same file would create duplicate draft products, so each forwarded spreadsheet should represent one drop.
Prerequisites
- A Shopify connection with permission to manage products and inventory. See the Shopify connector overview.
- A Slack connection and the channel ID (or name) where drop alerts should post. See the Slack connector overview.
- Your Shopify fulfilment
locationIdand the per-productinventoryItemIdvalues (the second is returned when each product is created, so you will read it from the create step). - A consistent CSV column layout from your supplier or pre-order export, for example
title,price,vendor, andqty. - An AI model selected for the Agent-mode Connector node (this step costs AI credits).
Step 1: Start the workflow with a Mailhook trigger
Create a new workflow and add a Trigger node. Set Trigger Type to Mailhook, give it an Address prefix such as merch, then click Generate email address. Spojit creates a unique address like merch-9f2a1c0b4d7e6512@mailhook.spojit.com. Copy it and use it as the forwarding target for your supplier line sheets or pre-order exports. To keep stray mail out, set an optional From allowlist (your supplier domain and your own address) and a Subject regex such as (?i)pre-?order|drop. The Mailhook trigger is always asynchronous and exposes the email as {{ input }} with fields including from, subject, text, replyTo, and attachments. For the full setup, see Setting Up a Mailhook Trigger.
Step 2: Pull the spreadsheet with an Attachment node
Add an Attachment node. This node only saves on a Mailhook workflow because it fetches the actual bytes of the attachments referenced by the trigger. Set Mode to Single so you get one file as an object, set the Content type filter to text/csv, and set a Filename pattern like *.csv so a forwarded PDF cover note is ignored. Turn on Fail if no attachment matches so a spreadsheet-less email stops cleanly instead of running on empty. The Single-mode output is:
{
"filename": "summer-drop.csv",
"contentType": "text/csv",
"size": 4821,
"content": "<base64 of the CSV bytes>"
}
The content field is base64. See the mailhook PO intake tutorial for a related attachment pattern.
Step 3: Parse the CSV into rows
Add a Connector node in Direct mode on the csv connector and pick the parse tool. The csv connector reads text, so first decode the attachment: add a Connector node on the encoding connector using base64-decode with {{ attachment.content }} as the input, then feed that decoded text into parse. Enable the header option so the first row becomes column keys. The result is an array of row objects you can reference downstream, for example {{ csv_result.data }}, with each row shaped like:
{ "title": "Summer Tour Tee", "price": "32.00", "vendor": "Acme Prints", "qty": "75" }
Step 4: Map rows with an Agent-mode Connector node
Supplier and fan-export spreadsheets rarely share one column scheme, so use judgment here rather than a fixed mapping. Add a Connector node in Agent mode and give it the parsed rows. The agent reads each row and returns a normalized product list. Turn on Response Schema to force structured JSON so the rest of the workflow is deterministic. A prompt and schema like this work well:
Prompt: Normalize each row in {{ csv_result.data }} into a merch product.
Map any title/name column to "title", any price/cost column to a number
"price", any supplier/brand column to "vendor", and any quantity/preorder
column to an integer "qty". Drop rows missing a title or qty.
Response Schema:
{
"type": "object",
"properties": {
"products": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"price": { "type": "number" },
"vendor": { "type": "string" },
"qty": { "type": "integer" }
},
"required": ["title", "qty"]
}
}
},
"required": ["products"]
}
The agent is the in-workflow AI that does the mapping; reference its output as {{ map_result.products }}. To understand when to reach for Agent mode over a fixed mapping, see How to Choose Between Agent Mode and Direct Mode.
Step 5: Gate the run with a Condition node
Add a Condition node so a blank or unmappable file never reaches Shopify. Check that {{ map_result.products }} has at least one item, for example test that {{ map_result.products.length }} is greater than 0. Wire the true branch into the product-creation steps below. On the false branch, add a Send Email node addressed to {{ input.replyTo }} with a short subject like No products found in your drop file so the sender knows their spreadsheet did not parse, then let that branch end without touching Shopify.
Step 6: Create products and stage stock on Shopify
On the true branch, add a Loop node set to ForEach over {{ map_result.products }}. Inside the loop body, add a Connector node in Direct mode on the shopify connector with the create-product tool. Map the fields:
title: {{ product.title }}
vendor: {{ product.vendor }}
status: DRAFT
tags: ["preorder", "merch-drop"]
Products default to DRAFT, so the drop stays unpublished until you review it. Then add a second Direct-mode Connector node on shopify with adjust-inventory to set the pre-order quantity. Map inventoryItemId from the create step's returned product, set locationId to your fulfilment location, and set delta to the row quantity (a positive number adds stock):
inventoryItemId: {{ create_result.data.inventoryItemId }}
locationId: {{ your_location_id }}
delta: {{ product.qty }}
reason: restock
Confirm the exact field path to the inventory item ID in the create step's output preview before wiring it. See Using Connector Nodes in Direct Mode for mapping details.
Step 7: Post a heads-up to Slack
After the loop, add a Connector node in Direct mode on the slack connector with the send-message tool. Set the channel to your drop channel and template the message body from the run so your team can review and publish the drafts:
channel: C0123MERCH
text: New merch drop staged from {{ input.from }}. {{ map_result.products.length }} draft products created on Shopify with pre-order stock set. Review and publish when ready.
Because these products are drafts, this Slack note is the cue for a human to do the final publish. For richer routing, see How to Set Up Multi-Channel Notifications.
Tips
- If a drop arrives as several files (a supplier sheet plus a fan tally), set the Attachment node to
Multipleand Loop over{{ attachment.attachments }}, parsing each one before mapping. - Keep products at
status: DRAFTand publish in Shopify so you control the exact go-live moment for the drop, rather than products appearing live as the file is processed. - Scaffold this workflow fast by describing it to Miraxa, the intelligent layer across your automation, with a prompt like "Build a workflow that watches a mailhook, pulls the CSV attachment, maps rows with an agent, and creates Shopify products", then fine-tune each node in the properties panel.
- Add the
preordertag increate-productso you can filter the drop later with the Shopifylist-productstool when it is time to fulfil.
Common Pitfalls
- Forwarding the same spreadsheet twice creates a second set of draft products: Mailhook deduplicates identical messages, but a re-forwarded copy is a new message, so treat one email as one drop.
- The
pricecolumn often arrives as a string with a currency symbol; let the Agent-mode node coerce it to a number, or clean it with a text connector step before mapping. adjust-inventoryneeds both a validinventoryItemIdand alocationId; a missing location is the most common reason stock does not appear, so confirm your fulfilment location ID first.- Attachments are capped (10 MB per file, 25 MB per run by default), so a very large image-heavy export may not pull. Keep the intake file to plain CSV rows.
Testing
Before pointing real supplier mail at the address, forward a small CSV with two or three rows to your mailhook address from an allowlisted account. Open the run in the execution history and check each step: the Attachment node should show the right filename and a non-zero size, the csv parse and Agent-mode steps should show the normalized products array, and the Condition node should take the true branch. Confirm the draft products and their inventory in Shopify admin, then verify the Slack message landed in the channel. Once a small file behaves end to end, switch the real forwarding rule on and run a full drop.