Skip to content
Tier 2 Schema

Parse Shipping & Delivery Notification Emails

Turn FedEx, UPS, USPS, and DHL shipment and delivery notifications into typed JSON — carrier, tracking number, status, origin, destination, and ETA.

Carriers send a separate email for almost every meaningful change to a package — the label is created, the package is picked up, it arrives at a sort hub, an exception is filed, it’s out for delivery, it’s delivered. The fields you actually want (which carrier, which tracking number, what status, when, and where) are buried in inconsistent HTML layouts that change per carrier and per event type. MailFrame extracts those fields into a typed, schema-validated JSON object you can route to a customer notification, a tracking dashboard, or a support tool that needs to know whether a package is moving.

It works on the plain-text and HTML parts of the notification emails FedEx, UPS, USPS, and DHL send for shipment creation, in-transit updates, out-for-delivery, delivery, exceptions (delays, address issues, lost parcel), and delivery attempts. Tracking numbers, status, the scheduled or estimated delivery date, and the most recent event are normalized into one consistent shape regardless of which carrier sent the message.

Fields MailFrame extracts

FieldTypeExampleNotes
carrierenumfedexOne of fedex, ups, usps, dhl
tracking_numberstring9205123456789012345678Carrier’s tracking number, as printed
statusenumout_for_deliveryOne of label_created, picked_up, in_transit, out_for_delivery, delivered, exception, delivery_attempt_failed, returned_to_sender
status_detailstringAddress corrected - delivery rescheduledCarrier’s free-text status line when present
origin_locationstringMemphis, TNOrigin city / sort facility when present
destination_locationstringBrooklyn, NY 11201Destination city / postal area when present
estimated_delivery_datestring2026-06-30Carrier’s ETA, normalized to ISO 8601
actual_delivery_datestring2026-06-29Actual delivery date when status is delivered
last_event_atstring2026-06-29T08:14:00-04:00Timestamp of the most recent carrier event, ISO 8601 with offset
last_event_locationstringBrooklyn, NY 11201Location the last event was recorded at
service_levelstringGroundService level when printed (Ground, 2-Day, Priority, Express, etc.)
weight_lbnumber3.2Package weight in pounds when present
tracking_urlstringhttps://www.fedex.com/fedextrack/?trknbr=9205123456789012345678Carrier’s direct tracking page
reference_numberstringPO-5567Shipper’s reference, PO, or order number when present
datestring2026-06-29Notification date normalized to ISO 8601

Sample input

A typical FedEx delivery notification email looks like this:

From: tracking@fedex.com
Subject: FedEx delivery notification - 9205123456789012345678
Date: Mon, 29 Jun 2026 08:14:00 -0400
To: jordan@example.com

Your package has been delivered.

Tracking number: 9205123456789012345678
Service:        FedEx Ground
Status:         Delivered
Delivered to:   Brooklyn, NY 11201
Delivered on:   June 29, 2026 at 08:14 AM
Signed by:      J. RIVERA

Track at: https://www.fedex.com/fedextrack/?trknbr=9205123456789012345678

The same shape is produced for UPS Quantum View Notify messages, USPS Informed Delivery delivery alerts, and DHL On Demand Delivery notifications — carrier distinguishes them and the rest of the fields normalize to the same JSON keys.

Structured JSON output

{
  "carrier": "fedex",
  "tracking_number": "9205123456789012345678",
  "status": "delivered",
  "status_detail": "Signed for by J. RIVERA",
  "origin_location": "Memphis, TN",
  "destination_location": "Brooklyn, NY 11201",
  "actual_delivery_date": "2026-06-29",
  "last_event_at": "2026-06-29T08:14:00-04:00",
  "last_event_location": "Brooklyn, NY 11201",
  "service_level": "FedEx Ground",
  "weight_lb": 3.2,
  "tracking_url": "https://www.fedex.com/fedextrack/?trknbr=9205123456789012345678",
  "reference_number": "PO-5567",
  "date": "2026-06-29"
}

JSON Schema definition

Every field is validated against the schema before MailFrame returns it. You can copy this as a starting point and tighten it for your own use case — for example, require tracking_number if every email in your flow should carry one, or add an enum to status if you only care about a subset of events:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "shipping_notification",
  "type": "object",
  "required": ["carrier", "tracking_number", "status"],
  "properties": {
    "carrier": {
      "type": "string",
      "enum": ["fedex", "ups", "usps", "dhl"]
    },
    "tracking_number": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S+"
    },
    "status": {
      "type": "string",
      "enum": [
        "label_created",
        "picked_up",
        "in_transit",
        "out_for_delivery",
        "delivered",
        "exception",
        "delivery_attempt_failed",
        "returned_to_sender"
      ]
    },
    "status_detail":     { "type": "string" },
    "origin_location":   { "type": "string" },
    "destination_location": { "type": "string" },
    "estimated_delivery_date": { "type": "string", "format": "date" },
    "actual_delivery_date":    { "type": "string", "format": "date" },
    "last_event_at":      { "type": "string", "format": "date-time" },
    "last_event_location": { "type": "string" },
    "service_level":      { "type": "string" },
    "weight_lb":          { "type": "number", "minimum": 0 },
    "tracking_url":       { "type": "string", "format": "uri" },
    "reference_number":   { "type": "string" },
    "date":               { "type": "string", "format": "date" }
  }
}

Parse via the API

POST the raw email (MIME or plain text) to /v1/parse with the schema you want to extract against:

# The page above documents the normalized shipping-notification shape.
# The engine currently ships per-carrier schemas (fedex-tracking, ups-tracking,
# usps-tracking, dhl-tracking); the example below targets the FedEx one to
# match the Sample input above.
curl https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer $MAILF...KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_id": "fedex-tracking",
    "raw_mime": "From: tracking@fedex.com\r\nSubject: FedEx delivery notification - 9205123456789012345678\r\n\r\nYour package has been delivered."
  }'

The call is synchronous: /v1/parse validates the extraction against your schema and returns the typed JSON in the HTTP response, so you can act on it inline — push a delivery notification, mark an order fulfilled in your OMS, or trigger a follow-up workflow on exception.

Routing on the result

The most useful split is between terminal and non-terminal status: any status in {"label_created", "picked_up", "in_transit", "out_for_delivery"} is a normal lifecycle event you can fold into a tracking view, while delivered triggers a fulfillment update and any value in {"exception", "delivery_attempt_failed", "returned_to_sender"} is worth surfacing to a support queue. Use the tracking_number as the deduplication key — carriers often send one message per event, so a single shipment produces several notifications, and the same carrier + tracking_number pair is the right join.

Signed webhook delivery

Signed async webhook delivery — where MailFrame POSTs a signed envelope to your endpoint when a parse completes — is available during early access alongside the synchronous API. Each delivery carries an HMAC-SHA256 signature in the MailFrame-Signature header for verification, with exponential-backoff retries on failure. Inbox forwarding — pointing a Gmail or Outlook filter at a unique inbox address MailFrame assigns you — is on the roadmap.

Other schemas

Ship this schema in production

Define your fields once, then POST raw email to /v1/parse. MailFrame returns typed JSON in the HTTP response or via signed webhook delivery with retries, attempt history, dead-letter, and replay. (Inbox forwarding is on the roadmap.)

Request early access