How to Create Shopify Orders from PO PDFs Emailed to a Mailhook

Give your order desk a mailhook address, parse incoming purchase order PDFs with AI, create the matching Shopify order, and alert your team by email when stock on hand cannot cover it.

What This Integration Does

Wholesale and B2B customers love sending purchase orders as PDF attachments. This workflow gives the order desk its own mailhook address, for example orders-x7k2m9qf4a3vbn8c@mailhook.spojit.com. Every email that arrives starts a run within seconds: the PO PDF is parsed by AI to pull out the line items and quantities, stock levels are checked for every SKU, and the matching order is created in Shopify. If any line item would take you below zero, the order is tagged as a backorder and a Resend email goes to your procurement team listing exactly what is short.

There is no mailbox to connect and nothing to poll. Customers either email the mailhook address directly, or you keep your public orders@yourcompany.com alias and add a forwarding rule that sends everything to the mailhook, so nothing changes on the customer side.

The document parsing uses the Knowledge node in Transient mode: the PDF is embedded into a single-run collection, queried with a structured response schema to extract a typed PO object, and cleaned up automatically when the run finishes. Nothing from one customer's PO persists in your knowledge base.

Prerequisites

  • A Shopify connection with permission to read products and inventory and to create orders.
  • A Resend connection (or any verified sending domain) for the backorder alerts.
  • Customer PO SKUs must match your Shopify variant SKUs, or you need a small alias mapping for the customers that use their own part numbers.

Step 1: Mailhook Trigger for the Order Desk

Drop a Trigger node, set its type to Mailhook, enter orders as the address prefix, and click Generate email address. Copy the address and either share it with customers or set up a forwarding rule from your existing orders@ alias.

Add a from allowlist with your customers' domains (e.g. @acme.com, @globex.com) so stray mail to the address never reaches the workflow. The run starts within seconds of each matching email arriving, with the parsed message available as {{ input }}.

Step 2: Guard on the PDF Attachment

Add a Condition node that checks {{ input.attachments.length > 0 }} and that the first attachment's content type is application/pdf. Route the false branch to a notification so a human can look at odd messages. On the true branch, the PDF bytes are available as {{ input.attachments[0].content }} (Base64-encoded).

Step 3: Embed the PDF into a Transient Knowledge Collection

Add a Knowledge node and configure:

  • Mode: Embed
  • Collection: Transient - a single-run collection that's automatically cleaned up at the end of the workflow.
  • Document Type: PDF
  • Document Input: {{ input.attachments[0].content }}

Step 4: Extract Line Items and Quantities with a Response Schema

Add a second Knowledge node:

  • Mode: Query
  • Collection: Transient - matches the collection from Step 3 within the same run.
  • Prompt: Extract the purchase order number, the buyer's company name and email, and every line item (SKU, description, quantity, unit price).
  • Response Schema: define the JSON shape so downstream nodes get typed data.
{
  "type": "object",
  "properties": {
    "poNumber":      { "type": "string" },
    "customerName":  { "type": "string" },
    "customerEmail": { "type": ["string", "null"] },
    "confidence":    { "type": "string", "enum": ["high", "medium", "low"] },
    "lineItems": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "sku":         { "type": "string" },
          "description": { "type": "string" },
          "quantity":    { "type": "number" },
          "unitPrice":   { "type": "number" }
        },
        "required": ["sku", "quantity"]
      }
    }
  },
  "required": ["poNumber", "customerName", "lineItems", "confidence"]
}

The structured output gives you a typed po object for the rest of the workflow. The sender's address is also on hand as {{ input.from }} if the PDF doesn't carry a usable buyer email.

Step 5: Check Stock for Every Line Item

Add a Loop node (ForEach over {{ po.lineItems }}). Inside the loop body:

  • A Connector node calling shopify list-products with a search query on the line item's SKU. This resolves the Shopify variant, including its id and current inventoryQuantity.
  • For multi-location stores, follow up with shopify get-inventory-levels on the variant's inventory item id and use the available quantity, not on_hand - units already committed to other orders are not yours to promise.
  • A Transform node that emits the variant id plus a shortfall flag: {{ item.quantity }} greater than the available quantity.

After the loop completes you have an enriched line item list; a Transform node can filter it down to a shortages array (items where shortfall is true).

Step 6: Create the Order in Shopify

Add a Connector node calling shopify raw-graphql with a draftOrderCreate mutation: one line item per resolved variant id with the PO quantity, the buyer's email as the customer, and the PO number in the note (e.g. PO {{ po.poNumber }} from {{ po.customerName }}). Tag the draft mailhook-po, and add a backorder tag when the shortages array is non-empty so fulfilment can see at a glance which orders are waiting on stock.

Creating a draft order is deliberate: it gives your team a final review point in Shopify admin, and completing it (draftOrderComplete) is one click or one more workflow step once you trust the pipeline.

Step 7: Send the Backorder Alert via Resend

Add a Condition node on {{ shortages.length > 0 }}. On the true branch, a Connector node calls resend send-email:

  • to: your procurement or operations list (e.g. procurement@yourcompany.com)
  • subject: Backorder: PO {{ po.poNumber }} from {{ po.customerName }}
  • html: a table of the short items - SKU, description, quantity ordered, quantity available - plus a link to the draft order in Shopify admin.

The false branch needs no action: the draft order is already in Shopify, fully covered by stock on hand. Mailhooks never reply to the sender on their own; if you also want to confirm receipt to the customer, add one more send-email step addressed to {{ input.replyTo }}.

Step 8 (Optional): Human Review for Low-Confidence Parses

Before Step 6, add a Condition node on {{ po.confidence == "low" }} and route the true branch through a Human approval node. AI extraction is reliable on clean digital POs, but scans and unusual layouts deserve a second pair of eyes before they become orders.

Tips

  • Keep your public alias, forward to the mailhook. Customers keep emailing orders@yourcompany.com; a forwarding rule delivers it to the mailhook. You can rotate the mailhook address any time without telling anyone.
  • Always use Transient collections for single-PO processing. A persistent collection would pour every customer's PO into one searchable bucket.
  • Check available, not on_hand. Stock that is committed to other unfulfilled orders will not be there when you pick.
  • Keep the backorder email actionable. Include the quantity gap per SKU, not just "insufficient stock", so procurement can raise the right purchase order immediately.
  • Make replays idempotent. Before creating, search existing orders for the PO number (e.g. shopify list-orders with a query on the note or tag) and skip creation on a match.

Common Pitfalls

  • Forgetting the from allowlist. The mailhook address is unguessable, but a forwarding rule can pass spam along with the real POs. Allowlist your customers' domains so junk never becomes a draft order.
  • Scanned PDFs without a text layer. The Knowledge node reads text; a pure image scan extracts poorly. Pin the confidence field in the schema and route low confidence to human review.
  • SKU mismatches. Customers often send their internal part numbers. Maintain an alias map for high-volume accounts and resolve before the stock check.
  • Stock changing between check and creation. The stock check is a snapshot; a concurrent sale can still oversell. The backorder tag plus draft-order review is the safety net, not a reservation.
  • Multiple POs in one email. Loop over input.attachments filtered to PDFs instead of indexing [0] if your customers batch their POs.

Testing

Email a real (small) PO PDF to the mailhook address from an allowlisted account. Watch the run in the execution log and verify, in order: the extracted line items and quantities match the PDF, each SKU resolved to the right Shopify variant, the draft order contains the right lines, and a deliberately oversized quantity on one line produces the backorder tag and the Resend alert with the correct shortfall numbers.

Related Articles

Learn More

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