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:
- Accepts a raw Shopify order confirmation email (MIME or plain text)
- Sends it to MailFrame’s
/v1/parseendpoint with theshopify_orderschema - Receives typed, schema-validated JSON with order number, line items, totals, and fulfillment status
- 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:
| Field | Type | Example | Purpose |
|---|---|---|---|
order_number | string | #1001 | Human-readable order identifier |
reference_id | string | gid://shopify/Order/5678901234 | Shopify internal GID for API lookups (when present in the email) |
customer_email | string | alex@example.com | Email address on the order |
customer_name | string | Alex Rivera | Full name as entered at checkout |
total_cents | integer | 8997 | Order total in minor units (cents) |
currency | string | usd | ISO 4217 currency code |
line_items | array | see below | Each item: title, quantity, price_cents |
shipping_status | enum | shipped | pending, shipped, delivered, or returned |
fulfillment_status | enum | fulfilled | unfulfilled, fulfilled, partial, or cancelled |
date | string | 2026-05-21 | Order 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:
- Place a test order on any Shopify store (or use your own store’s test mode to generate a confirmation email).
- Open the email in your email client and choose “Show original” or “Download .eml” — this gives you the raw RFC 822 MIME source.
- 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 thedataobject 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
- Browse the full schema library for other email types — Stripe receipts, Amazon orders, PayPal payments, and more.
- Read about signed webhook delivery for asynchronous processing with retries, dead-letter queues, and replay.
- Try the free in-browser Stripe receipt parser to see the extraction concept in action without an API key.