Skip to content
invoices accounts-payable schema email-to-json tutorial

Parse Invoice & Payment-Due Emails to JSON

Turn AP/invoice emails into typed JSON: extract vendor, amount due, currency, dates, and payment status with a JSON Schema and one POST to /v1/parse.

MailFrame Team

Accounts Payable (AP) and expense workflows face a constant challenge: vendors send invoices and payment reminders in every layout imaginable—whether from QuickBooks, FreshBooks, Xero, or manually typed out. Despite the endless visual formats, the core data you need is always the same: who is billing you, how much, and when it is due. Parsing this text manually or with rigid regex rules is fragile and error-prone.

This post explains how to extract those fields from an invoice email into a typed, schema-validated JSON object that you can route straight to your ledger or AP queue—using a JSON Schema and a single POST to MailFrame’s /v1/parse endpoint. If you just want the ready-made schema, check out the Parse Invoice & Payment-Due Emails page in our schema library.

The fields you actually need

To build a robust AP pipeline, you want to identify the vendor, the amount, the currency, and the payment timeline. The fields worth extracting are:

FieldWhy you want it
invoice_numberMatch against your ledger and prevent duplicate payments
po_numberReconcile the invoice against the original purchase order
vendor_nameIdentify the billing party
amount_due_centsBalance due in minor units, preventing floating-point errors
currencyStandardized ISO 4217 code
issue_date / due_dateTrack aging and avoid late fees
payment_statusFilter out fully paid receipts (due, paid, overdue, partial)

Note: MailFrame currently parses the text of the invoice or payment-reminder email body. We know many invoices arrive as PDF or image attachments—support for PDF/image input, as well as direct inbox forwarding, are on our roadmap.

The JSON Schema

MailFrame extracts data according to the exact shape you declare. You provide a JSON Schema; successful data is validated against the schema, and violations are surfaced in validation_errors.

Here is a compact schema for invoice emails. Note how constraints like amount_due_cents being a minimum of 0 and payment_status having an enum ensure you only get valid data:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "invoice",
  "type": "object",
  "required": ["invoice_number", "vendor_name", "amount_due_cents", "currency", "payment_status"],
  "properties": {
    "invoice_number": { "type": "string", "minLength": 1 },
    "po_number": { "type": "string" },
    "vendor_name": { "type": "string", "minLength": 1 },
    "vendor_domain": { "type": "string", "format": "hostname" },
    "amount_due_cents": { "type": "integer", "minimum": 0 },
    "currency": { "type": "string", "minLength": 3, "maxLength": 3 },
    "issue_date": { "type": "string", "format": "date" },
    "due_date": { "type": "string", "format": "date" },
    "customer_email": { "type": "string", "format": "email" },
    "payment_url": { "type": "string", "format": "uri" },
    "payment_status": { "type": "string", "enum": ["due", "paid", "overdue", "partial"] }
  }
}

This is the same schema provided under the invoice id in our schema library.

Send the email to /v1/parse

The most direct way to integrate MailFrame is synchronously: POST the raw RFC 822 MIME text of the email—exactly as your system received it—along with the schema reference.

Here is how you can use curl to send an invoice email against the built-in invoice schema:

curl -X POST https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer $MAILFRAME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_id": "invoice",
    "raw_mime": "From: billing@acmesupplies.com\r\nTo: ap@buyerco.com\r\nSubject: Invoice INV-2026-0481 from Acme Supplies — due June 30\r\n\r\nHi Buyer Co.,\r\n\r\nPlease find invoice INV-2026-0481 below.\r\nIssue date: June 15, 2026\r\nDue date: June 30, 2026\r\nAmount due: $1,250.00 USD\r\nStatus: Due\r\n\r\nThank you for your business."
  }'

The response wraps the extracted data in a data object, along with any validation errors if the email could not satisfy your strict schema rules:

{
  "id": "parse_b41f7e",
  "status": "completed",
  "validation_errors": [],
  "data": {
    "invoice_number": "INV-2026-0481",
    "vendor_name": "Acme Supplies",
    "vendor_domain": "acmesupplies.com",
    "amount_due_cents": 125000,
    "currency": "usd",
    "issue_date": "2026-06-15",
    "due_date": "2026-06-30",
    "customer_email": "ap@buyerco.com",
    "payment_status": "due"
  }
}

Validate and Route

In your application, check validation_errors before trusting the payload. If the array is empty, your data strictly conforms to the types and enums you defined.

interface ParseResult {
  id: string;
  status: "completed" | "failed";
  validation_errors: unknown[];
  data: InvoiceData;
}

interface InvoiceData {
  invoice_number: string;
  vendor_name: string;
  amount_due_cents: number;
  currency: string;
  payment_status: "due" | "paid" | "overdue" | "partial";
  due_date?: string;
  po_number?: string;
}

async function processInvoiceEmail(rawMime: string): Promise<void> {
  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: "invoice", raw_mime: rawMime }),
  });

  const result: ParseResult = await res.json();

  if (result.status !== "completed" || result.validation_errors.length > 0) {
    await flagForManualReview(result);
    return;
  }

  const invoice = result.data;

  if (invoice.payment_status === "paid") {
    await archiveReceipt(invoice);
    return;
  }

  await insertIntoAPQueue(invoice);
}

By relying on the schema validations, you ensure that fields like amount_due_cents are safely mapped as an integer rather than an inconsistent currency string, avoiding manual parsing steps entirely.

Next steps

For teams integrating payment emails, you can also explore our schemas for Stripe Receipts or PayPal Receipts. For a higher-level look at integrating email ingestion with MailFrame, check out our email-to-JSON API guide.

Ready to start building? Check the full request and response formats in the API docs, and review our pricing to see how it fits your needs. If you would like to test the API with your own invoice examples, contact us to get early access.

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