Parsing shipping notification emails
Your supplier ships. The carrier emails a tracking number. Somebody copies it into the order so the customer can be told. This is the smallest of the four jobs on this site and the one that runs cleanest — the parse below came back with no flags at all.
Measured on 30 August 2026 against the live service at
https://mailmint.app.mintapis.com, from a free account created the same morning.
The message and schema are printed in full; rerun it yourself.
Tracking mail into order status #
You want six things out of a despatch mail: the tracking number, who is carrying it, when it left, when it is promised, which order it belongs to, and the link to hand the customer. Get those and the order status updates itself and the "where is my parcel" mail never gets written.
The problem is that these mails come from carriers, from freight forwarders, from suppliers' own systems and from marketplaces, and no two of them lay the information out the same way.
Why it is not a regex #
Tracking numbers look regular enough that everybody starts with a pattern, and the pattern is where it goes wrong:
- The formats overlap. UPS
1Znumbers are distinctive; DHL, DPD, GLS, Hermes and Royal Mail are runs of 10 to 20 digits that also match order numbers, invoice numbers, customer numbers and phone numbers sitting in the same mail. - The number appears more than once. Subject line, body, tracking link, sometimes a returns label with a different number. A pattern takes the first match; the first match is sometimes the return.
- "Estimated delivery" is written eleven ways and at least three of them are relative — "in 2–4 business days" has no date in it at all.
- Carrier names are inconsistent. UPS, United Parcel Service, a logo with alt text, or nothing but the tracking URL's domain.
Asking for a described field rather than a pattern moves all of that from your maintenance burden into the parse — and every value comes back with the substring it was read from, so when it is wrong you can see why in one glance rather than debugging a regex against a mail you no longer have.
The schema #
[
{ "name": "tracking_number", "type": "string", "description": "the carrier tracking number" },
{ "name": "carrier", "type": "string", "description": "which carrier is delivering" },
{ "name": "shipped_date", "type": "date", "description": "date the parcel left the warehouse" },
{ "name": "estimated_delivery", "type": "date", "description": "expected delivery date" },
{ "name": "order_reference", "type": "string", "description": "the order this shipment belongs to" },
{ "name": "tracking_url", "type": "string", "description": "the link to the carrier's tracking page" }
]
Six fields, six descriptions, no templates and nothing positional. The descriptions do real work: they
are how order_reference and tracking_number stay apart in a mail that contains
both.
A real carrier mail, really parsed #
A despatch notification with a tracking number in the subject and the body, an order reference,
a weight, a piece count and a tracking URL. Back in 2,908 ms — the fastest of
the four jobs on this site — with flags: [] and needs_review: false:
| Field | Value | Conf. | Source | Evidence in the mail |
|---|---|---|---|---|
tracking_number | 1Z999AA10123456784 | 0.97 | rule+llm | Your parcel is on its way - tracking 1Z999AA10123456784 |
carrier | UPS | 0.97 | rule+llm | Carrier: UPS |
shipped_date | 2026-08-29 | 0.93 | llm | 29 August 2026 |
estimated_delivery | 2026-09-02 | 0.97 | rule+llm | Estimated delivery: 2 September 2026 by end of day |
order_reference | A7-449021 | 0.93 | llm | order A7-449021 |
tracking_url | https://www.ups.com/track?tracknum=1Z999AA10123456784 | 0.93 | llm | the same URL, verbatim |
Three things in that table are worth a second look. The tracking number and the order reference are
both alphanumeric identifiers in the same short mail and neither leaked into the other's field.
2 September 2026 by end of day became the date 2026-09-02 without the trailing
words. And rule+llm on four of the six fields means a deterministic rule and the model
independently produced the same value — that agreement is what the 0.97 is; the model alone cannot
raise a confidence, it can only lower one.
Joining it back to the order #
The despatch mail is only useful once it finds its order, and order_reference is the join
key. Two practical notes:
- If the reference is missing from the mail entirely — some carrier notifications carry only the
tracking number — the field comes back
nullrather than guessed, and you join on the tracking number you stored when you booked the shipment instead. - The full message object always carries the sender, the subject, and SPF, DKIM and DMARC results. "This despatch mail is really from the carrier" is a question worth asking before a tracking link goes out to a customer, and the answer travels with the parse.
Then either the webhook posts it to your order service as it arrives, or you poll
GET /v1/events, or an n8n workflow moves it — n8n-nodes-mailmint on
n8n Cloud or self-hosted n8n, with an HTTP Request node against
POST /v1/parse as the no-community-node route.
When not to use this #
- You booked the shipment yourself. Then you already have the tracking number from the carrier's API, and you should be using their tracking API for status too — it tells you about delays, which an email sent once at despatch never will.
- You only need to know a parcel exists. A filter rule in the mailbox is free.
- One supplier, one template. A regular expression is fine and it will not cost you $9 a month. Come back when the second supplier arrives with a different layout.
- Live tracking events. MailMint parses the despatch mail. It does not follow the parcel; "out for delivery" and "delivery attempted" come from the carrier, not from us.
What we cannot claim #
MailMint has paying customers, but no customer logo, testimonial or case study is quoted without permission. There is no published uptime history, SLA or SOC 2 report. One region, one instance. This is one carrier mail, not a benchmark across carriers — and the clean run above is the easiest of the four jobs on this site, which is exactly why the invoice page shows a field that came back at 0.25 instead. Relative dates ("in 2–4 business days") are not resolved to a calendar date. PDF attachment contents are not read.
Try it on your own worst email #
Not a demo message — the one your current parser gets wrong. Paste its subject and body into
POST /v1/parse and read the confidence and the evidence span on each field.
/v1/parse stores nothing, needs no inbound address and no DNS record, and the free plan is
300 parsed emails a month with no card, which is enough to run a real low-volume
workflow rather than only to look at one.
The other three #
Comparisons instead: what an email parsing API has to get right · vs Mailparser · vs Parseur · vs Docparser · vs Zapier Parser