This email confirms revisions made to your original DoorDash order and reflects the final amount charged. The new total cost of your order is above and includes all taxes and fees. Payment processing adjustments to the original charge may take up to 5-7 business days to process.
This email confirms revisions made to your original DoorDash order and reflects the final amount charged. The new total cost of your order is above and includes all taxes and fees. Payment processing adjustments to the original charge may take up to 5-7 business days to process.
Our support team is happy to help with any concern you might have.
Contact Support
xid34d6b4ee-da8f-5029-8d14-bd359617c8e9
pGvlI2ANUbXFfyEOgxta1RMV082993
\ No newline at end of file
diff --git a/src/__tests__/fixtures/orders/real/ue-04.html b/src/__tests__/fixtures/orders/real/ue-04.html
new file mode 100644
index 0000000..19b6722
--- /dev/null
+++ b/src/__tests__/fixtures/orders/real/ue-04.html
@@ -0,0 +1,274 @@
+
+
Jul 7, 2026
8:08 pm
Jul 7, 2026 , 8:08 pm
Thanks for ordering, Molina
Here's your receipt for Hi Machan Preethipura - Wattala.
Total
LKR 3,783.20
1
Chicken Kochi Cutlets 5 Pcs
LKR 1,000.00
1
SPECIAL Chicken Rice or Noodles
LKR 2,500.00
Final 1.00 lb
Item subtotal
LKR 3,500.00
Tax
LKR 49.20
Delivery Fee
LKR 99.00
Service Fee
LKR 135.00
Payments
Siddharth
LKR 3,783.20
7/7/26 9:01 pm
Want to switch your payment method?
Switch
Download the receipt in a PDF format
Download PDF
Tax includes the applicable tax on meal fare collected on behalf of merchant partners and on any other fees as applicable
Order details
Uber Delivery
8:29 pm - Pick-up
261 Prithipura Rd, Wattala, Sri Lanka
9:00 pm - Delivery
460 colombo road , Uswetakiyawe, Wattala, Sri Lanka
8:29 pm - Pick-up
261 Prithipura Rd, Wattala, Sri Lanka
9:00 pm - Delivery
460 colombo road , Uswetakiyawe, Wattala, Sri Lanka
Delivered by Mary
Say thanks with a rating or tip for the courier or restaurant.
Our support team is happy to help with any concern you might have.
Contact Support
xid5fb05f0f-ee3b-5cd6-a8c7-22fc4b7c161e
pGvlI2ANUbXFfyEOgxta1RMV082993
\ No newline at end of file
diff --git a/src/__tests__/fixtures/orders/real/ue-05.html b/src/__tests__/fixtures/orders/real/ue-05.html
new file mode 100644
index 0000000..3b2f1ff
--- /dev/null
+++ b/src/__tests__/fixtures/orders/real/ue-05.html
@@ -0,0 +1,254 @@
+
+
7 July, 2026
6:17 pm
Refunded
7 July, 2026 , 6:17 pm
Just a quick update, Siddharth
We adjusted the total for your recent order from Coles (Wyndham Vale).
Our support team is happy to help with any concern you might have.
Contact Support
xidebb7e2a4-e597-44d0-996d-23d95d18aa94
pGvlI2ANUbXFfyEOgxta1RMV082993
\ No newline at end of file
diff --git a/src/__tests__/fixtures/orders/real/ue-09.html b/src/__tests__/fixtures/orders/real/ue-09.html
new file mode 100644
index 0000000..26082a4
--- /dev/null
+++ b/src/__tests__/fixtures/orders/real/ue-09.html
@@ -0,0 +1,274 @@
+
+
Jul 5, 2026
8:15 pm
Jul 5, 2026 , 8:15 pm
Thanks for ordering, Molina
Here's your receipt for GLOMARK Kandana.
Total
LKR 3,757.01
1
Highland Brewing MILK FULL CREAM U H T 900ML
LKR 440.00
Replaced Chello Pasturized Fresh Milk 1L
1
Sugar White - Bulk
LKR 21.70
Final 0.10 kg
1
Nescafe Coffee Classic Jar 100G
LKR 2,000.00
1
Colmans White Vinegar 500Ml
LKR 990.00
1
Margarita Naturals Mango Greek Yoghurt 150G
LKR 0.00
Sold out • Replacement not available
Item subtotal
LKR 3,451.70
Tax
LKR 51.31
Bag fee
LKR 10.00
Delivery Fee
LKR 99.00
Service Fee
LKR 145.00
Payments
Visa ••••8841
LKR 4,267.01
7/5/26 7:20 pm
Failed
Siddharth
LKR 3,757.01
7/5/26 9:02 pm
Want to switch your payment method?
Switch
Download the receipt in a PDF format
Download PDF
Tax includes the applicable tax on meal fare collected on behalf of merchant partners and on any other fees as applicable
Order details
Uber Delivery
8:49 pm - Pick-up
153 A3, Ja-Ela, Sri Lanka
9:02 pm - Delivery
460 colombo road , Uswetakiyawe, Wattala, Sri Lanka
8:49 pm - Pick-up
153 A3, Ja-Ela, Sri Lanka
9:02 pm - Delivery
460 colombo road , Uswetakiyawe, Wattala, Sri Lanka
Our support team is happy to help with any concern you might have.
Contact Support
xid1d47f75c-c1d0-5697-8eb4-0d22e7bbf29f
pGvlI2ANUbXFfyEOgxta1RMV082993
\ No newline at end of file
diff --git a/src/__tests__/fixtures/orders/real/ue-26.html b/src/__tests__/fixtures/orders/real/ue-26.html
new file mode 100644
index 0000000..f863aea
--- /dev/null
+++ b/src/__tests__/fixtures/orders/real/ue-26.html
@@ -0,0 +1,254 @@
+
+
Our support team is happy to help with any concern you might have.
Contact Support
xided33132d-926d-55e5-91ef-7ac66bb62168
pGvlI2ANUbXFfyEOgxta1RMV082993
\ No newline at end of file
diff --git a/src/__tests__/fixtures/orders/real/ue-meta.json b/src/__tests__/fixtures/orders/real/ue-meta.json
new file mode 100644
index 0000000..1ee95da
--- /dev/null
+++ b/src/__tests__/fixtures/orders/real/ue-meta.json
@@ -0,0 +1,205 @@
+[
+ {
+ "i": 0,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkeckxXAAAA",
+ "date": "2026-07-22T03:41:19Z",
+ "subject": "Your Wednesday afternoon order with Uber Eats",
+ "len": 57778
+ },
+ {
+ "i": 1,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkd32TzAAAA",
+ "date": "2026-07-21T04:25:09Z",
+ "subject": "Your Tuesday afternoon order with Uber Eats",
+ "len": 61532
+ },
+ {
+ "i": 2,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkd32TsAAAA",
+ "date": "2026-07-20T23:17:05Z",
+ "subject": "Your Tuesday morning order with Uber Eats",
+ "len": 59110
+ },
+ {
+ "i": 3,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkdK0voAAAA",
+ "date": "2026-07-20T04:23:40Z",
+ "subject": "Your Monday afternoon order with Uber Eats",
+ "len": 66211
+ },
+ {
+ "i": 4,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0aigAAAA",
+ "date": "2026-07-07T15:31:27Z",
+ "subject": "[Family] Your Tuesday evening order with Uber Eats",
+ "len": 80279
+ },
+ {
+ "i": 5,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0aicAAAA",
+ "date": "2026-07-07T08:58:43Z",
+ "subject": "Your Tuesday evening order with Uber Eats",
+ "len": 73592
+ },
+ {
+ "i": 6,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0aibAAAA",
+ "date": "2026-07-07T08:50:06Z",
+ "subject": "[Family] Your Tuesday afternoon order with Uber Eats",
+ "len": 78782
+ },
+ {
+ "i": 7,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0aiaAAAA",
+ "date": "2026-07-07T08:46:17Z",
+ "subject": "Your Tuesday evening order with Uber Eats",
+ "len": 65886
+ },
+ {
+ "i": 8,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0ah6AAAA",
+ "date": "2026-07-05T15:51:11Z",
+ "subject": "[Family] Your Sunday evening order with Uber Eats",
+ "len": 82383
+ },
+ {
+ "i": 9,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0ah5AAAA",
+ "date": "2026-07-05T15:33:01Z",
+ "subject": "[Family] Your Sunday evening order with Uber Eats",
+ "len": 88958
+ },
+ {
+ "i": 10,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0ahTAAAA",
+ "date": "2026-07-03T10:04:15Z",
+ "subject": "Your Friday evening order with Uber Eats",
+ "len": 61485
+ },
+ {
+ "i": 11,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkR0ahFAAAA",
+ "date": "2026-07-02T22:57:18Z",
+ "subject": "Your Friday morning order with Uber Eats",
+ "len": 60751
+ },
+ {
+ "i": 12,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkOIIcBAAAA",
+ "date": "2026-06-27T04:21:08Z",
+ "subject": "Your Saturday afternoon order with Uber Eats",
+ "len": 60720
+ },
+ {
+ "i": 13,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkH8zVTAAAA",
+ "date": "2026-06-22T10:19:49Z",
+ "subject": "Your Monday evening order with Uber Eats",
+ "len": 58303
+ },
+ {
+ "i": 14,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkH8zUwAAAA",
+ "date": "2026-06-20T12:28:26Z",
+ "subject": "Your Saturday afternoon order with Uber Eats",
+ "len": 60856
+ },
+ {
+ "i": 15,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkH8zURAAAA",
+ "date": "2026-06-18T18:43:06Z",
+ "subject": "Your Thursday evening order with Uber Eats",
+ "len": 57881
+ },
+ {
+ "i": 16,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAkGJuBoAAAA",
+ "date": "2026-06-15T19:32:17Z",
+ "subject": "Your Monday evening order with Uber Eats",
+ "len": 60838
+ },
+ {
+ "i": 17,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAj9_fg8AAAA",
+ "date": "2026-06-02T11:27:03Z",
+ "subject": "Your Tuesday evening order with Uber Eats",
+ "len": 55147
+ },
+ {
+ "i": 18,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAj7_L67AAAA",
+ "date": "2026-06-02T03:11:33Z",
+ "subject": "Your Tuesday afternoon order with Uber Eats",
+ "len": 60716
+ },
+ {
+ "i": 19,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAj66TeuAAAA",
+ "date": "2026-05-29T02:47:06Z",
+ "subject": "Your Friday afternoon order with Uber Eats",
+ "len": 55127
+ },
+ {
+ "i": 20,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAj19QCWNwAAAA==",
+ "date": "2026-05-23T03:00:14Z",
+ "subject": "Your Saturday afternoon order with Uber Eats",
+ "len": 65562
+ },
+ {
+ "i": 21,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjzoSJtAAAA",
+ "date": "2026-05-18T10:50:25Z",
+ "subject": "Your Monday evening order with Uber Eats",
+ "len": 68144
+ },
+ {
+ "i": 22,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjy8e6sAAAA",
+ "date": "2026-05-17T03:13:05Z",
+ "subject": "Your Sunday afternoon order with Uber Eats",
+ "len": 62439
+ },
+ {
+ "i": 23,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjw8ZyAAAAA",
+ "date": "2026-05-15T09:45:15Z",
+ "subject": "Your Friday evening order with Uber Eats",
+ "len": 55113
+ },
+ {
+ "i": 24,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjw8ZxwAAAA",
+ "date": "2026-05-14T15:02:24Z",
+ "subject": "Your Thursday afternoon order with Uber Eats",
+ "len": 58231
+ },
+ {
+ "i": 25,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjw8ZxtAAAA",
+ "date": "2026-05-14T11:24:43Z",
+ "subject": "Your Thursday evening order with Uber Eats",
+ "len": 60745
+ },
+ {
+ "i": 26,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjV0eF4AAAA",
+ "date": "2026-04-08T05:54:03Z",
+ "subject": "Your Tuesday evening order with Uber Eats",
+ "len": 68338
+ },
+ {
+ "i": 27,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjV0eFyAAAA",
+ "date": "2026-04-07T22:40:52Z",
+ "subject": "Your Tuesday afternoon order with Uber Eats",
+ "len": 52600
+ },
+ {
+ "i": 28,
+ "id": "AQMkADAwATM0MDAAMS1hYzQyLTVjZDktMDACLTAwCgBGAAADS6kDcxBxKkKwbCK1wU1QlwcA7Ex_hlaTTU0AgHQ16optl_AAAAIBDAAAAOxMfoZWk01NAIB0NeqKbZfgAAjV0eFcAAAA",
+ "date": "2026-04-06T05:23:40Z",
+ "subject": "Your Sunday evening order with Uber Eats",
+ "len": 59440
+ }
+]
\ No newline at end of file
diff --git a/src/lib/order-parse.ts b/src/lib/order-parse.ts
new file mode 100644
index 0000000..775c983
--- /dev/null
+++ b/src/lib/order-parse.ts
@@ -0,0 +1,433 @@
+/**
+ * Order receipt parsing, written against REAL captured emails.
+ *
+ * History: the first version of this parser was written against synthetic
+ * fixtures that were shaped to match the code rather than the mail. It invented
+ * a
Label
$X
layout that DoorDash does not
+ * send, derived order_reference from Math.random(), and read the order date
+ * from a `Date: YYYY-MM-DD` string that appears in no real message. All of it
+ * passed its tests. This version is built from 36 real DoorDash and 29 real
+ * Uber Eats receipts; see docs in memory-bank/order-ingestion-implementation.md.
+ *
+ * Governing rule: parse or throw. Never fabricate a value that the mail did not
+ * state (I9). A caller that gets a ParsedOrder back can trust every field in it.
+ */
+
+export interface LineItem {
+ qty: number;
+ description: string;
+ amount: number;
+ options?: string[];
+}
+
+export interface PaymentBreakdown {
+ credits_amount: number | null;
+ card_amount: number | null;
+ card_last4: string | null;
+ /** true when the mail states a payment method it does not fully disaggregate. */
+ ambiguous: boolean;
+}
+
+export interface OrderTotals {
+ subtotal: number | null;
+ taxes: number | null;
+ delivery_fee: number | null;
+ service_fee: number | null;
+ tip: number | null;
+ discounts: number | null;
+ total_charged: number;
+}
+
+export interface ParsedOrder {
+ order_reference: string;
+ platform: "doordash" | "ubereats" | "uber";
+ merchant_name: string;
+ order_datetime: string;
+ currency: string;
+ payment: PaymentBreakdown;
+ totals: OrderTotals;
+ line_items: LineItem[];
+ is_family: boolean;
+ flags: string[];
+}
+
+/** Everything the parser needs that lives on the message, not in the body. */
+export interface MessageMeta {
+ /** Provider message id. The only stable per-mail identity DoorDash offers. */
+ messageId: string;
+ subject: string;
+ /** ISO 8601. The authoritative order date — the body carries no reliable one. */
+ receivedAt: string;
+ sender?: string;
+}
+
+export class OrderParseError extends Error {
+ constructor(message: string, readonly messageId?: string) {
+ super(message);
+ this.name = "OrderParseError";
+ }
+}
+
+const stripTags = (s: string) => s.replace(/<[^>]+>/g, " ");
+
+const decodeEntities = (s: string) =>
+ s
+ .replace(/ /gi, " ")
+ .replace(/&/gi, "&")
+ .replace(/'|'/gi, "'")
+ .replace(/"/gi, '"')
+ .replace(/$/g, "$")
+ .replace(/…/gi, "…");
+
+const collapse = (s: string) => s.replace(/\s+/g, " ").trim();
+
+const money = (raw: string): number => Math.abs(parseFloat(raw.replace(/[$,]/g, "")));
+
+/**
+ * DoorDash renders each total as its own nested table:
+ *
Subtotal
$22.10
+ * Reading the pair structurally is what stops the label/value rebinding that
+ * flattening causes (I9) — flattened, "Discounts -$24.09 Total Charged $14.64"
+ * invites a regex to bind the wrong number to the wrong label.
+ */
+function tdPairValue(html: string, label: string): number | null {
+ const re = new RegExp(
+ `