Skip to content
shopify ecommerce orders fulfillment schema email-to-json tutorial

Parse Shopify Order Emails to JSON

Turn Shopify order confirmations and shipping notifications into typed JSON — order number, line items, totals, fulfillment status, and customer details. Schema-first tutorial.

MailFrame Team

Every Shopify store sends order confirmation and shipping notification emails, but the HTML layout changes with every theme, every app, and every template customization. Your code needs the order number, the line items, the total, and the fulfillment status — not a wall of styled markup.

MailFrame turns those emails into a single typed JSON object you can write straight to your order-management system, analytics pipeline, or inventory tracker.

The Shopify Orders schema page covers the full field reference; this tutorial walks through parsing a real Shopify order email end to end.

What you’ll build

By the end of this tutorial you’ll have a working integration that:

  1. Accepts a raw Shopify order confirmation email (MIME or plain text)
  2. Sends it to MailFrame’s /v1/parse endpoint with the shopify_order schema
  3. Receives typed, schema-validated JSON with order number, line items, totals, and fulfillment status
  4. Handles the response — both successful parses and validation errors

Prerequisites

  • A MailFrame API key (request early access from the home page)
  • The raw MIME or plain-text content of a Shopify order confirmation email
  • curl (or your preferred HTTP client)

Step 1 — Understand the Shopify order schema

The shopify_order schema extracts the fields that matter for order processing:

FieldTypeExamplePurpose
order_numberstring#1001Human-readable order identifier
reference_idstringgid://shopify/Order/5678901234Shopify internal GID for API lookups (when present in the email)
customer_emailstringalex@example.comEmail address on the order
customer_namestringAlex RiveraFull name as entered at checkout
total_centsinteger8997Order total in minor units (cents)
currencystringusdISO 4217 currency code
line_itemsarraysee belowEach item: title, quantity, price_cents
shipping_statusenumshippedpending, shipped, delivered, or returned
fulfillment_statusenumfulfilledunfulfilled, fulfilled, partial, or cancelled
datestring2026-05-21Order date in ISO 8601 format

The schema enforces types and required fields at parse time — if order_number or total_cents is missing from the extraction, MailFrame returns a validation_errors array alongside the partial data so you can decide how to handle it.

Step 2 — Get a real Shopify order email

To test the parser, you need the raw content of a Shopify order confirmation email. The easiest way to get one:

  1. Place a test order on any Shopify store (or use your own store’s test mode to generate a confirmation email).
  2. Open the email in your email client and choose “Show original” or “Download .eml” — this gives you the raw RFC 822 MIME source.
  3. Save the content to a file, for example shopify-order.eml.

If you don’t have a real email handy, here’s a representative sample you can use for testing:

From: orders@myawesomestore.myshopify.com
Subject: Order confirmation #1001 - My Awesome Store
Date: Wed, 21 May 2026 14:22:00 -0500
To: alex@example.com

Hi Alex,

Thank you for your purchase! We're getting your order ready.

Order #1001
Placed on May 21, 2026

Items ordered:
  Classic Logo Tee (Size M, Black) x2    $29.99 each
  Canvas Tote Bag x1                     $14.99

Subtotal:  $74.97
Shipping:  $4.99 (Standard)
Taxes:     $9.99
Total:     $89.97 USD

Ship to:
Alex Rivera
123 Maple St
Austin, TX 78701

Payment: Visa ending in 5678

Step 3 — Send the email to MailFrame

With your API key set as an environment variable and the email content in a file, make the parse request:

export MAILFRAME_API_KEY="mf_live_xxxxxxxxxxxxxxxx"

curl https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer $MAILFRAME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_id": "shopify_order",
    "raw_mime": "From: orders@myawesomestore.myshopify.com\nSubject: Order confirmation #1001 - My Awesome Store\nDate: Wed, 21 May 2026 14:22:00 -0500\nTo: alex@example.com\n\nHi Alex,\n\nThank you for your purchase! We'\''re getting your order ready.\n\nOrder #1001\nPlaced on May 21, 2026\n\nItems ordered:\n  Classic Logo Tee (Size M, Black) x2    $29.99 each\n  Canvas Tote Bag x1                     $14.99\n\nSubtotal:  $74.97\nShipping:  $4.99 (Standard)\nTaxes:     $9.99\nTotal:     $89.97 USD\n\nShip to:\nAlex Rivera\n123 Maple St\nAustin, TX 78701\n\nPayment: Visa ending in 5678"
  }'

If you saved the email to a file, you can inline it with jq:

RAW_MIME=$(cat shopify-order.eml | jq -Rs .)
curl https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer $MAILFRAME_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"schema_id\": \"shopify_order\", \"raw_mime\": $RAW_MIME}"

Step 4 — Read the response

A successful parse returns HTTP 200 with the typed data:

{
  "id": "parse_8f2a1c",
  "status": "completed",
  "validation_errors": [],
  "data": {
    "order_number": "#1001",
    "customer_email": "alex@example.com",
    "customer_name": "Alex Rivera",
    "total_cents": 8997,
    "currency": "usd",
    "line_items": [
      { "title": "Classic Logo Tee (Size M, Black)", "quantity": 2, "price_cents": 2999 },
      { "title": "Canvas Tote Bag", "quantity": 1, "price_cents": 1499 }
    ],
    "shipping_status": "pending",
    "fulfillment_status": "unfulfilled",
    "date": "2026-05-21"
  }
}

Key things to check in the response:

  • status"completed" means the extraction ran and the result passed schema validation. Other values include "failed" (the extraction itself errored).
  • validation_errors — an empty array means every required field passed validation. If any required field is missing or a type doesn’t match, the errors are listed here — but the data object is still returned so you can inspect the partial result.
  • data — the typed JSON payload, already validated against the schema. You can write it straight to your database without additional type checks.

Step 5 — Handle the response in your application

Here’s how to integrate the parse result into a typical order-processing pipeline:

// TypeScript — fetch and process
const res = await fetch("https://api.mailframe.ai/v1/parse", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MAILFRAME_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    schema_id: "shopify_order",
    raw_mime: rawEmailText,
  }),
});

if (!res.ok) {
  throw new Error(`MailFrame error ${res.status}: ${await res.text()}`);
}

const { data, status, validation_errors } = await res.json();

if (status === "completed" && validation_errors.length === 0) {
  // Write straight to your database — already typed and validated
  await db.orders.insert({
    orderNumber: data.order_number,
    customerEmail: data.customer_email,
    totalCents: data.total_cents,
    currency: data.currency,
    lineItems: data.line_items,
    fulfillmentStatus: data.fulfillment_status,
    orderDate: data.date,
  });
  console.log(`Order ${data.order_number} recorded`);
} else {
  console.warn("Parse issues:", { status, validation_errors, data });
}
# Python — fetch and process
import os
import requests

res = requests.post(
    "https://api.mailframe.ai/v1/parse",
    headers={"Authorization": f"Bearer {os.environ['MAILFRAME_API_KEY']}"},
    json={
        "schema_id": "shopify_order",
        "raw_mime": raw_email_text,
    },
)
res.raise_for_status()
body = res.json()

if body["status"] == "completed" and not body.get("validation_errors"):
    order = body["data"]
    # Write straight to your database — already typed and validated
    db.orders.insert({
        "order_number": order["order_number"],
        "customer_email": order["customer_email"],
        "total_cents": order["total_cents"],
        "currency": order["currency"],
        "line_items": order["line_items"],
        "fulfillment_status": order.get("fulfillment_status"),
        "order_date": order["date"],
    })
    print(f"Order {order['order_number']} recorded")
else:
    print("Parse issues:", body["status"], body.get("validation_errors"))

Real-world integration tips

Watch for template variations

Shopify stores use heavily customized email templates. The same store may send different layouts for order confirmations, shipping notifications, and fulfillment updates. Test against all three variants before going to production.

Handle the reference_id for API lookups

When present, reference_id contains the Shopify GraphQL GID (gid://shopify/Order/5678901234). You can use it to fetch additional order details from the Shopify Admin API — for example, to pull tax breakdowns or discount allocations that aren’t in the email.

Track fulfillment state transitions

Shopify sends separate emails as an order moves through fulfillment: confirmation → shipped → delivered. By parsing each email against the same shopify_order schema, you can track fulfillment_status changes over time and trigger downstream actions — like sending a customer notification when status moves to shipped.

Currency is always in minor units

All monetary values (total_cents, price_cents) are integers representing the amount in the smallest currency unit (cents for USD, pence for GBP, etc.). Convert to display format in your UI layer, not before storing — this avoids floating-point rounding issues in calculations.

Line items may be empty

An order with zero parsed line items may indicate a Shopify template the parser hasn’t seen before. Gate downstream processing on line_items.length > 0 and flag empty arrays for manual review.

What’s next

Turn your inbox into an API

MailFrame parses raw email into typed JSON against your own schema — PDF and image input are planned. Request early developer access.

Request early access