Skip to content
Tier 2 Schema

Parse Bank & Card Transaction Emails

Turn Chase, Bank of America, Wells Fargo, Capital One, Citi, USAA, PNC, Revolut, Monzo, and Starling transaction alerts into typed JSON — transaction type, amount, balance, merchant, posted date, and account mask. Schema-first.

Every modern bank and card issuer sends a real-time alert when money moves on your account: a debit-card tap at a coffee shop, a wire out to a vendor, a payroll deposit, an ATM withdrawal, a recurring charge, a fraud hold. The emails come from Chase, Bank of America, Wells Fargo, Capital One, Citi, USAA, PNC, TD Bank, Discover, American Express, HSBC, Barclays, Santander, Revolut, Monzo, Starling, and dozens of regional banks and credit unions. Each one writes the alert in its own template, but the fields you actually want are the same handful: what type of transaction, how much, in which currency, on which account, who the merchant or counterparty is, when it posted, and what the running balance is now.

MailFrame extracts those fields from any bank or card transaction alert into typed, schema-validated JSON you can route straight into a personal-finance tracker (YNAB, Monarch, Copilot Money, Lunch Money, Firefly III), an expense or AP pipeline (Brex, Ramp, Expensify, a custom reconciliation job), or a fraud-monitoring queue. Match on the message body and the bank-specific wording (“a charge of”, “has been authorized on your card”, “your deposit has posted”, “you withdrew”) rather than the From address alone — banks frequently relay alerts through tenant-specific, white-labeled, or notification-platform domains (no-reply@alert.chase.com, alerts@bofa.com, noreply@capitalone.com, transaction@wise.com, alerts@revolut.com) and the sender domain drifts over time. The transaction type, amount, currency, merchant, account mask, posted date, and running balance all normalize into one consistent shape regardless of which bank sent the message. PDF, image, and calendar-attachment input are on the roadmap; inbox forwarding is planned.

Fields MailFrame extracts

FieldTypeExampleNotes
transaction_typeenumpurchaseOne of purchase, withdrawal, deposit, transfer, payment, refund, fee, interest, chargeback, fraud_hold, authorization, unknown
directionenumdebitOne of debit, creditdebit for money leaving the account (purchase, withdrawal, fee, payment), credit for money arriving (deposit, refund, interest)
amountnumber42.17Transaction amount as a decimal; positive in both directions (sign comes from direction)
currencystringUSDISO 4217 currency code on the alert; USD, EUR, GBP, CAD, AUD, JPY, etc.
balancenumber4312.55Account balance immediately after the transaction posted, when the bank includes it
account_maskstring••••1234Last 4 (sometimes 5) digits of the card or account number, with or without the leading bullets — the dedup key for a cardholder’s accounts
account_typeenumcredit_cardOne of checking, savings, credit_card, debit_card, line_of_credit, investment, unknown
merchant_namestringBlue Bottle CoffeeThe counterparty as printed on the alert — the merchant for a purchase, the payer for a deposit, the recipient for a transfer
merchant_categorystring5814Merchant Category Code (MCC) when the bank includes one; blank string when not present
merchant_locationstringSan Francisco, CACity / state / country from the bank when it includes transaction location data
authorization_codestring048221Bank or card-network authorization code on the alert, when present
posted_datestring2026-07-03Date the transaction posted to the account, normalized to ISO 8601
transaction_datestring2026-07-03T09:12:00-04:00Date/time the transaction was initiated (often slightly earlier than posted_date), ISO 8601
payment_methodenumcardOne of card, ach, wire, check, cash, transfer, unknown
card_brandenumvisaOne of visa, mastercard, amex, discover, diners, jcb, unionpay, unknown
reference_idstringCHK-20260703-018421Bank’s internal reference / confirmation number, when present
notesstringRecurring monthly chargeFree-text note or memo line from the alert, when present
alert_urlstringhttps://...Link to the bank’s transaction-detail page when the alert includes one
datestring2026-07-03Date the email was sent, normalized to ISO 8601

Sample input

A typical Chase credit-card purchase alert looks like this:

From: Chase Alerts <no-reply@alert.chase.com>
Subject: A charge of $42.17 was made on your credit card
Date: Thu, 03 Jul 2026 09:12:00 -0400
To: jordan.rivera@example.com

A charge of $42.17 was made on your Chase credit card ending in 1234
on July 3, 2026 at Blue Bottle Coffee in San Francisco, CA.

Your current available balance is $4,312.55.
Authorization code: 048221

Manage alerts: https://chase.com/online-banking/alert/1234

The same shape is produced for direct deposits, ATM withdrawals, transfers, fees, refunds, fraud holds, and card authorizations from every major US and EU bank — transaction_type distinguishes a purchase from a withdrawal, deposit, transfer, payment, refund, fee, interest, chargeback, fraud_hold, or authorization — and the rest of the fields normalize to the same JSON keys regardless of which bank or card issuer sent the message.

Structured JSON output

{
  "transaction_type": "purchase",
  "direction": "debit",
  "amount": 42.17,
  "currency": "USD",
  "balance": 4312.55,
  "account_mask": "••••1234",
  "account_type": "credit_card",
  "merchant_name": "Blue Bottle Coffee",
  "merchant_category": "",
  "merchant_location": "San Francisco, CA",
  "authorization_code": "048221",
  "posted_date": "2026-07-03",
  "transaction_date": "2026-07-03T09:12:00-04:00",
  "payment_method": "card",
  "card_brand": "visa",
  "alert_url": "https://chase.com/online-banking/alert/1234",
  "date": "2026-07-03"
}

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 merchant_category to be a 4-digit string when you reconcile to MCC data, or restrict transaction_type to {"purchase", "deposit", "withdrawal"} if you only care about cash flow on a personal checking account:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "bank_transaction",
  "type": "object",
  "required": [
    "transaction_type",
    "direction",
    "amount",
    "currency",
    "account_type"
  ],
  "properties": {
    "transaction_type": {
      "type": "string",
      "enum": [
        "purchase",
        "withdrawal",
        "deposit",
        "transfer",
        "payment",
        "refund",
        "fee",
        "interest",
        "chargeback",
        "fraud_hold",
        "authorization",
        "unknown"
      ]
    },
    "direction": {
      "type": "string",
      "enum": ["debit", "credit"]
    },
    "amount":      { "type": "number", "minimum": 0 },
    "currency":    { "type": "string", "minLength": 3, "maxLength": 3 },
    "balance":     { "type": "number" },
    "account_mask":{ "type": "string" },
    "account_type": {
      "type": "string",
      "enum": [
        "checking",
        "savings",
        "credit_card",
        "debit_card",
        "line_of_credit",
        "investment",
        "unknown"
      ]
    },
    "merchant_name":     { "type": "string" },
    "merchant_category": { "type": "string" },
    "merchant_location": { "type": "string" },
    "authorization_code":{ "type": "string" },
    "posted_date":       { "type": "string", "format": "date" },
    "transaction_date":  { "type": "string", "format": "date-time" },
    "payment_method": {
      "type": "string",
      "enum": ["card", "ach", "wire", "check", "cash", "transfer", "unknown"]
    },
    "card_brand": {
      "type": "string",
      "enum": [
        "visa",
        "mastercard",
        "amex",
        "discover",
        "diners",
        "jcb",
        "unionpay",
        "unknown"
      ]
    },
    "reference_id":  { "type": "string" },
    "notes":         { "type": "string" },
    "alert_url":     { "type": "string", "format": "uri" },
    "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:

curl https://api.mailframe.ai/v1/parse \
  -H "Authorization: Bearer ${MAILFRAME_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_id": "bank_transaction",
    "raw_mime": "From: Chase Alerts <no-reply@alert.chase.com>\r\nTo: jordan.rivera@example.com\r\nSubject: A charge of $42.17 was made on your credit card\r\nDate: Thu, 03 Jul 2026 09:12:00 -0400\r\n\r\nA charge of $42.17 was made on your Chase credit card ending in 1234\r\non July 3, 2026 at Blue Bottle Coffee in San Francisco, CA.\r\n\r\nYour current available balance is $4,312.55.\r\nAuthorization code: 048221\r\n\r\nManage alerts: https://chase.com/online-banking/alert/1234"
  }'

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 — append the transaction to a personal-finance spreadsheet, drop a deposit onto a cash-flow dashboard, or page on a fraud_hold while it is still actionable.

Routing on the result

The most useful splits are by direction and transaction_type. The first is the broad bucket: credit (deposits, refunds, interest) goes to a cash-flow or income lane; debit (purchases, withdrawals, fees) goes to a spending lane. Inside those, transaction_type lets you route by intent: a deposit typically deserves its own alerting channel (payroll landed, refund cleared, dividend posted); a fraud_hold or chargeback is high-signal and should page; a fee or interest entry is a low-priority ledger update. Use account_mask plus posted_date as the dedup key — the same transaction can show up twice in your inbox (an authorization email when the merchant pre-auths, then a purchase email when it actually posts) and you want one row per real movement, not two. When account_mask is absent, fall back to (merchant_name, amount, currency, posted_date).

Signed webhook delivery

Prefer asynchronous delivery? Signed webhook delivery — where MailFrame POSTs the extraction result to your endpoint with an HMAC-SHA256 signature in the MailFrame-Signature header and exponential-backoff retries — is available during early access alongside the synchronous API. Inbox forwarding — pointing a Gmail or Outlook filter at a unique inbox address MailFrame assigns you — is on the roadmap.

Working with related email types? See the Parse Subscription Renewal Emails schema, the Parse Invoice & Payment-Due Emails schema, and the email-to-JSON API guide for a full walkthrough of POST /v1/parse. For a step-by-step tutorial that wires this schema into a real /v1/parse request, see Parse Bank & Card Transaction Emails to JSON.

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