Recipes: e-commerce & retail
Two morning pipelines for Shopify merchants (WooCommerce, Square and Stripe
slot in the same way). Auth is a Shopify bearer credential whose token is
an Admin API access token (shpat_…), referenced by name.
Low-stock reorder alert
Each morning, scan variant inventory and flag anything running low — as a Slack alert and a restock task — so you reorder before you sell out.
The shape:
inventory-scan (shopify, daily)
└─ low-stock (code) → low-stock-gate (filter) → reorder-alert (slack) + reorder-task (clickup)Pull products with their variant inventory via a GraphQL query:
# jobs/shop/inventory.yaml
id: inventory-scan
name: Inventory scan
group: shop
type: shopify
credential: shopify
shop: '{{ $vars.shopDomain }}'
operation: custom
query: |
query {
products(first: 100, query: "status:active") {
edges { node { title variants(first: 25) { edges { node { sku inventoryQuantity } } } } }
}
}
schedule:
- { kind: daily, time: '07:00' }A code transform keeps only the variants at or under your threshold:
# jobs/shop/low-stock.yaml
id: low-stock
name: Find low stock
group: shop
type: code
script: |
const threshold = Number($vars.lowStockThreshold || 5);
const edges = ((($parent.json || {}).data || {}).products || {}).edges || [];
const low = [];
for (const p of edges) {
for (const v of (((p.node || {}).variants || {}).edges || [])) {
const q = Number(v.node.inventoryQuantity);
if (Number.isFinite(q) && q <= threshold) {
low.push((p.node.title) + ' — ' + (v.node.sku || 'no SKU') + ': ' + q + ' left');
}
}
}
return { count: low.length, list: low.join('\n') };
schedule:
- { kind: afterParent, parent: inventory-scan, on: succeeded }# jobs/shop/low-stock-gate.yaml
id: low-stock-gate
name: Anything low?
group: shop
type: filter
input: '{{ $parent.json.count }}'
operator: gt
value: '0'
schedule:
- { kind: afterParent, parent: low-stock, on: succeeded }# jobs/shop/reorder-alert.yaml
id: reorder-alert
name: Low-stock alert
group: shop
type: slack
credential: slack_bot
channel: '#ops'
message: |
:package: {{ $parent.json.count }} variant(s) low on stock:
{{ $parent.json.list }}
schedule:
- { kind: afterParent, parent: low-stock-gate, on: succeeded, branch: pass }# jobs/shop/reorder-task.yaml
id: reorder-task
name: Restock task
group: shop
type: clickup
credential: clickup
operation: createTask
listId: '{{ $vars.opsListId }}'
taskName: 'Reorder low-stock items ({{ $parent.json.count }})'
description: '{{ $parent.json.list }}'
schedule:
- { kind: afterParent, parent: low-stock-gate, on: succeeded, branch: pass }Daily sales digest
Every morning, turn today’s paid orders into a plain-English summary in Slack — no dashboard to open.
The shape:
sales-orders (shopify, daily) → sales-summary (claude-api) → sales-post (slack)# jobs/shop/orders.yaml
id: sales-orders
name: Paid orders
group: shop
type: shopify
credential: shopify
shop: '{{ $vars.shopDomain }}'
operation: listOrders
first: 100
searchQuery: 'created_at:>={{ $now.date }} financial_status:paid'
schedule:
- { kind: daily, time: '08:00' }A one-shot claude-api step summarizes the orders in words:
# jobs/shop/sales-summary.yaml
id: sales-summary
name: Summarize sales
group: shop
type: claude-api
credential: anthropic
maxTokens: 500
system: You are a concise retail analyst. Report revenue, order count, top products, and anything notable.
prompt: |
Summarize today's paid Shopify orders in 4-6 lines for a busy owner:
{{ $parent.json.data.orders.edges }}
schedule:
- { kind: afterParent, parent: sales-orders, on: succeeded }# jobs/shop/sales-post.yaml
id: sales-post
name: Post sales digest
group: shop
type: slack
credential: slack_bot
channel: '#sales'
message: |
:chart_with_upwards_trend: *Sales so far today*
{{ $parent.text }}
schedule:
- { kind: afterParent, parent: sales-summary, on: succeeded }Multi-channel. Add
stripegetBalanceandsquarelistPaymentsjobs on the same schedule and post each summary to the same channel for a full picture across your sales rails.