Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Order Processing & Fulfilment
[YourCompany.com] · Fulfilment Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, configure, and hand over the Order Processing and Fulfilment automation. It covers the current-state step map with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. The owner side is responsible for confirming API access, courier rules, and Cin7 data accuracy before build begins. FullSpec handles all construction, integration wiring, testing, and deployment.
01Process snapshot
02Agent specifications
Monitors Shopify for new paid orders via webhook, extracts all line items and their SKUs, and queries Cin7 for current on-hand stock against each SKU. If all SKUs have sufficient stock, the validated order record is passed to the Fulfilment Dispatch Agent. If any SKU is short, the agent sends a Slack alert to the fulfilment channel with the order number, the affected SKU, and the stock shortfall quantity, then halts the order in a held state pending manual review. This agent must handle multi-line orders atomically: a single short SKU holds the entire order, not just that line. Estimated build time: 14 hours. Complexity: Moderate.
// Input
shopify.order {
order_id: string,
order_number: string,
customer.email: string,
customer.first_name: string,
customer.last_name: string,
shipping_address: { address1, address2, city, province, zip, country },
line_items: [{ sku: string, quantity: integer, price: decimal, title: string }],
total_price: decimal,
currency: string
}
// Stock check (per SKU)
cin7.stock_query -> GET /api/v2/inventory?sku={sku}
cin7.stock_response {
sku: string,
on_hand_quantity: integer,
location_id: string
}
// Output (stock confirmed)
validated_order {
order_id: string,
order_number: string,
customer: { email, first_name, last_name },
shipping_address: { address1, address2, city, province, zip, country },
line_items: [{ sku, quantity, price, title }],
total_price: decimal,
currency: string,
stock_status: 'confirmed'
}
// Output (stock short - Slack alert payload)
slack.message {
channel: '#fulfilment-alerts',
text: 'STOCK HOLD: Order {order_number} held. SKU {sku} has {on_hand_quantity} on hand, {quantity} required.',
order_id: string,
held_at: ISO8601 timestamp
}- Shopify plan must be on Basic or above to expose the orders/paid webhook endpoint. Confirm plan tier before build.
- Cin7 API access requires a Cin7 Core or Cin7 Omni subscription with the API module enabled. The operations manager must provision an API key scoped to inventory read and sales order write before this agent can be tested.
- Stock-check logic must iterate over all line_items in a single pass and collect all short SKUs before sending a Slack alert. Do not send one alert per short SKU.
- The agent must write the order_id and held_at timestamp to a dedicated error log table (Supabase: table 'held_orders') when a stock hold is triggered, so the fulfilment team can track and manually release held orders.
- Dedupe: Shopify may occasionally fire duplicate webhooks. The agent must check the error log and active pipeline for the order_id before processing. If the order_id already exists in either location, skip silently.
- The Slack alert channel name ('#fulfilment-alerts') must be confirmed with the operations manager before go-live. Slack workspace must have the automation platform's bot app installed and invited to that channel.
- Stock threshold logic: if on_hand_quantity >= quantity, treat as confirmed. Do not apply a buffer unless the client specifies one during discovery.
Receives the validated order record from the Order Intake Agent and executes the full dispatch chain without human input. It creates a draft sales invoice in Xero mapped from Shopify line items, pushes the order to StarShipIt to book the appropriate courier and retrieve the shipping label and tracking number, updates Shopify with the fulfilment status and tracking number, sends a personalised shipping confirmation email to the customer via Gmail using a pre-approved template, and posts a dispatch summary to the Slack fulfilment channel. Each step must complete successfully before the next begins. Any step failure triggers an error log entry and a Slack alert to the fulfilment channel with the step name and error message. Estimated build time: 22 hours. Complexity: Moderate.
// Input
validated_order {
order_id: string,
order_number: string,
customer: { email, first_name, last_name },
shipping_address: { address1, address2, city, province, zip, country },
line_items: [{ sku, quantity, price, title }],
total_price: decimal,
currency: string,
stock_status: 'confirmed'
}
// Step 1: Xero invoice creation
xero.invoice_payload {
Type: 'ACCREC',
Contact: { EmailAddress: customer.email, Name: '{first_name} {last_name}' },
LineItems: [{ Description: title, Quantity: quantity, UnitAmount: price, AccountCode: '200' }],
CurrencyCode: currency,
Reference: order_number
}
xero.invoice_response -> invoice_id: string
// Step 2: StarShipIt courier booking
starshipit.order_payload {
reference: order_number,
destination: { address: address1, suburb: city, state: province, post_code: zip, country: country },
packages: [{ weight_kg: derived_from_sku_lookup }]
}
starshipit.booking_response {
tracking_number: string,
carrier_name: string,
label_url: string
}
// Step 3: Shopify fulfilment update
shopify.fulfilment_payload {
order_id: order_id,
tracking_number: tracking_number,
tracking_company: carrier_name,
notify_customer: false
}
// Step 4: Gmail confirmation email
gmail.send_payload {
to: customer.email,
subject: 'Your order {order_number} has shipped!',
body_template: 'shipping_confirmation_v1',
template_vars: { first_name, order_number, tracking_number, carrier_name }
}
// Output: Slack dispatch notification
slack.message {
channel: '#fulfilment-dispatched',
text: 'DISPATCHED: Order {order_number} | {first_name} {last_name} | Courier: {carrier_name} | Tracking: {tracking_number}',
xero_invoice_id: invoice_id
}- Xero connection requires OAuth 2.0 with scopes: accounting.transactions, accounting.contacts. The tenant_id must be stored in the credential store and refreshed automatically. Confirm the Xero plan supports API access (Starter plan has invoice limits; Growing or above recommended).
- StarShipIt API key must be provisioned from the StarShipIt account settings. Confirm that courier service rules, rate cards, and package weight defaults are configured in StarShipIt before the dispatch agent is built. The agent relies on StarShipIt's internal rules to select the correct service; it does not implement its own routing logic.
- Shopify fulfilment: set notify_customer to false on the fulfilment API call to prevent Shopify's native shipping notification email firing alongside the Gmail confirmation. Review this with the client during discovery as Shopify may have this enabled by default.
- Gmail sending requires a Google Workspace account with Gmail API enabled. OAuth 2.0 scopes required: gmail.send. The sending address must be a verified Google account, not a generic alias, to avoid deliverability issues.
- The Gmail email template ('shipping_confirmation_v1') must be built and approved by the client before end-to-end testing begins. Store the template in the automation platform's template store, not hardcoded in the workflow.
- Xero AccountCode '200' is the default sales revenue account. Confirm this with the client's accountant before go-live. Different product categories may require different account codes.
- If any step in the dispatch chain fails (Xero, StarShipIt, Shopify, Gmail), log the failure to Supabase table 'dispatch_errors' with order_id, failed_step, error_message, and timestamp. Then send a Slack alert to '#fulfilment-alerts' immediately. Do not silently retry without logging.
- StarShipIt weight data: if the Cin7 product records include weight_kg per SKU, pull this during the Order Intake Agent stock check and pass it through the validated_order payload. If weight data is absent from Cin7, a default package weight must be agreed with the client and hardcoded as a fallback.
03End-to-end data flow
// ============================================================
// ORDER PROCESSING & FULFILMENT - END-TO-END DATA FLOW
// ============================================================
// TRIGGER
// Shopify fires order.paid webhook on new paid order
SHOPIFY.WEBHOOK -> order.paid {
order_id,
order_number,
customer.email,
customer.first_name,
customer.last_name,
shipping_address.address1,
shipping_address.address2,
shipping_address.city,
shipping_address.province,
shipping_address.zip,
shipping_address.country,
line_items[].sku,
line_items[].quantity,
line_items[].price,
line_items[].title,
total_price,
currency
}
// ============================================================
// AGENT HANDOFF 1: Shopify -> Order Intake Agent
// ============================================================
// ORDER INTAKE AGENT: Dedupe check
CHECK supabase.held_orders WHERE order_id = order_id
CHECK active_pipeline WHERE order_id = order_id
-> IF FOUND: skip (duplicate webhook)
-> IF NOT FOUND: proceed
// ORDER INTAKE AGENT: Stock check loop (per SKU)
FOR EACH line_item IN order.line_items:
CIN7.GET /api/v2/inventory?sku={line_item.sku} -> {
sku,
on_hand_quantity,
location_id
}
IF on_hand_quantity < line_item.quantity:
short_skus.APPEND { sku, on_hand_quantity, required: line_item.quantity }
// ORDER INTAKE AGENT: Route decision
IF short_skus.length > 0:
// Stock short path
SLACK.POST #fulfilment-alerts {
text: 'STOCK HOLD: Order {order_number} | Short SKUs: {short_skus}',
held_at: ISO8601_timestamp
}
SUPABASE.INSERT held_orders { order_id, order_number, short_skus, held_at }
STOP // human review required
ELSE:
// Stock confirmed path - build validated_order payload
validated_order = {
order_id, order_number,
customer: { email, first_name, last_name },
shipping_address: { address1, address2, city, province, zip, country },
line_items: [{ sku, quantity, price, title, weight_kg }],
total_price, currency,
stock_status: 'confirmed'
}
// ============================================================
// AGENT HANDOFF 2: Order Intake Agent -> Fulfilment Dispatch Agent
// ============================================================
// FULFILMENT DISPATCH AGENT: Step 1 - Xero invoice
XERO.POST /api.xro/2.0/Invoices {
Type: 'ACCREC',
Contact.EmailAddress: customer.email,
Contact.Name: '{first_name} {last_name}',
LineItems[].Description: line_item.title,
LineItems[].Quantity: line_item.quantity,
LineItems[].UnitAmount: line_item.price,
LineItems[].AccountCode: '200',
CurrencyCode: currency,
Reference: order_number
} -> { invoice_id }
// FULFILMENT DISPATCH AGENT: Step 2 - StarShipIt booking
STARSHIPIT.POST /orders {
reference: order_number,
destination.address: shipping_address.address1,
destination.suburb: shipping_address.city,
destination.state: shipping_address.province,
destination.post_code: shipping_address.zip,
destination.country: shipping_address.country,
packages[].weight_kg: line_item.weight_kg (or default fallback)
} -> {
tracking_number,
carrier_name,
label_url
}
// FULFILMENT DISPATCH AGENT: Step 3 - Shopify fulfilment update
SHOPIFY.POST /orders/{order_id}/fulfillments {
tracking_number: tracking_number,
tracking_company: carrier_name,
notify_customer: false
}
// FULFILMENT DISPATCH AGENT: Step 4 - Gmail customer email
GMAIL.SEND {
to: customer.email,
subject: 'Your order {order_number} has shipped!',
template: 'shipping_confirmation_v1',
vars: { first_name, order_number, tracking_number, carrier_name }
}
// FULFILMENT DISPATCH AGENT: Step 5 - Slack dispatch notification
SLACK.POST #fulfilment-dispatched {
text: 'DISPATCHED: Order {order_number} | {first_name} {last_name} | {carrier_name} | {tracking_number}',
xero_invoice_id: invoice_id
}
// ============================================================
// ERROR PATH (any step in dispatch chain)
// ============================================================
ON FAILURE AT step {step_name}:
SUPABASE.INSERT dispatch_errors {
order_id, order_number, failed_step: step_name,
error_message: error.message,
timestamp: ISO8601_timestamp
}
SLACK.POST #fulfilment-alerts {
text: 'ERROR: Order {order_number} failed at {step_name}. Details logged.',
timestamp: ISO8601_timestamp
}
STOP // do not proceed to subsequent steps04Recommended build stack
More documents for this process
Every document generated for Order Processing & Fulfilment.