To match receipts to bank transactions automatically, you need three things: the bank line, the order email that explains it, and a scoring rule that decides when the two are the same purchase. SpendifiAI does all three. It searches your connected Gmail, Outlook or IMAP inbox for the receipt behind a charge, has Claude read the email into a structured order, and scores it against the transaction on amount, date and merchant. A score of 0.80 or higher links the two and categorizes the charge from what you actually bought.
This article walks through how that works in the SpendifiAI codebase, including the part most receipt tools skip: what to do when the inbox search comes back empty even though the receipt is sitting right there. On Outlook that happens often enough that SpendifiAI now runs a three-phase search with two fallbacks, shipped on March 8, 2026 in commit 1701a22 along with per-user timezone support.
What Is Email Receipt Matching?
Email receipt matching is the job of pairing each card or bank charge with the order confirmation that produced it. The bank line tells you that you paid a merchant a certain amount on a certain day, often under a truncated or cryptic name. The receipt email tells you what that was: the items, their prices, tax, shipping and an order number. Neither is complete on its own.
Matching them matters for two practical reasons. First, categorization: a charge from a big-box store or a marketplace can be groceries, electronics, a gift or office supplies, and the bank line cannot tell you which. Second, records: when a purchase is a business or deductible expense, the itemized receipt is what shows what you bought, while the bank line only shows that you paid.
Does a bank statement count as a receipt?
Partly. The IRS guidance on what kind of records to keep lists account statements and credit card statements among the documents that support an expense, alongside receipts and invoices. A statement proves the payment, the payee and the date. It does not show what the payment bought, which is why an itemized receipt next to the bank line is the stronger record. Receipt matching gives you both in one place, attached to the same transaction.
How SpendifiAI Finds the Receipt for a Transaction
SpendifiAI imports your transactions from Plaid or from uploaded bank statements. When a charge is ambiguous, the app asks you about it on a question card. If you have an email account connected, that card carries a Search my emails button. Pressing it queues a background job (SearchTransactionEmails, up to 3 tries, 5-minute timeout) that runs this sequence:
- Check whether it is already solved. If the transaction was reconciled to an order in the meantime, the job marks the search as found and stops without touching your inbox.
- Build the queries. From the bank line, SpendifiAI cleans the merchant name (strips trailing numbers, symbols and bank suffixes), adds the real merchant name when the bank descriptor is cryptic, and always adds the exact amount, because the amount is the most reliable signal for finding one specific receipt.
- Search the inbox. Gmail gets a quoted-phrase query that excludes trash and starts from a date; IMAP gets a server-side text search; Outlook gets Microsoft Graph
$search. The window runs from 7 days before the charge to 2 days after it. - Fall back when Outlook misses the amount. If none of the Outlook results contain the dollar amount, the search moves to Phase 2a and, if that finds nothing, Phase 2b (both explained below).
- Put the likeliest email first. Emails whose body contains the exact amount are moved to the front of the queue, so the most relevant one is parsed first and fewer AI calls are spent.
- Read each email with Claude. The parser returns whether the email is a purchase at all, plus merchant, order number, order date, subtotal, tax, shipping, total and line items. Emails Claude judges not to be a purchase are recorded as skipped, not turned into orders.
- Reconcile and categorize. A reconciliation pass scores every unmatched transaction against every unmatched order. On a confident match, the transaction takes the category of its most expensive line item, and the open question is closed as “Auto-resolved via email receipt match”.
The search status (searching, found, no results) is stored on the server rather than only in the browser, so reloading the page mid-search shows the true state instead of resetting the button. Each parsed email is also checked against existing orders before a new one is created, so searching twice does not create duplicate orders.
Why Outlook Search Doesn’t Find the Amount in Receipt Emails
If you have ever typed a dollar amount into Outlook’s search box and gotten nothing back for an email you can see in your inbox, you have hit the problem this release works around. SpendifiAI’s Outlook integration runs on Microsoft Graph, and the code notes it plainly: Graph’s $search index does not reliably index amounts inside HTML email tables. Order confirmations from large retailers are exactly that, a long HTML table with the total near the bottom.
Graph adds a second constraint. It does not allow $search and $filter together on messages, so you cannot ask for “this amount, in this date range” in one request. Phase 1 therefore searches without a date filter, drops anything older than the window on the client side, and asks for more results for merchant searches (50) than for amount searches (20), tries the amount with and without a dollar sign, and stops at the first format that returns anything.
Phase 2a: search by known receipt senders
When none of the Phase 1 results contain the amount, or there are no results at all, SpendifiAI looks up the merchant in a map of known receipt sender addresses and queries those senders directly. Filtering on sender plus a date range is allowed by Graph, because the restriction only applies to combining $filter with $search. The map currently covers 12 merchants, including Amazon (three addresses: order confirmations, shipment tracking and digital orders), Walmart, Costco, Apple, Best Buy, Home Depot, Target, DoorDash, Uber, Google Play, Netflix and Spotify.
Phase 2a returns up to 15 messages per sender and keeps only those whose subject reads like an order or receipt (order, shipped, delivered, confirmed, receipt, invoice, purchase, payment, subscription, “thanks for your”). It deliberately does not require the exact amount. A charge can differ from the email total because of tax adjustments or a split shipment, so these emails are handed to the parser and the scoring step decides.
Phase 2b: scan the date range for the exact amount
If the merchant has no known sender, or the senders returned nothing, the last resort is a brute-force scan. SpendifiAI pages through every message received in the window, 50 at a time and up to 10 pages, strips the HTML and decodes entities (so $ becomes a dollar sign) and checks the full body text for the amount. It scans the raw body rather than the truncated version it later sends to Claude, because long retailer emails often put the total far down the page.

Graph $search
Phase 1, every search
- How it finds mail
- Microsoft’s search index
- Amounts in HTML tables
- Not reliably indexed
- Date range
- Filtered after the fact
- Messages per request
- 20 (amount) or 50 (merchant)
- When it runs
- Always first
Date-range body scan
Phase 2b, last resort
- How it finds mail
- Reads every message in the window
- Amounts in HTML tables
- Found in the stripped full body
- Date range
- Filtered by Graph in the request
- Messages per request
- 50 per page, up to 10 pages
- When it runs
- Only after Phase 1 and 2a miss
Cheap, targeted searches run first. The expensive scan only runs for a single transaction you asked about, and only when the exact amount has not turned up anywhere else.
How Automatic Receipt Matching Scores a Match
Finding a likely email is half the job. The other half is deciding whether that order and that charge are the same purchase. SpendifiAI’s ReconciliationService gives every transaction and order pair a score from 0 to 1 built from three signals, keeps the best-scoring order for each transaction (never reusing an order in the same run), and acts on the result.
Where a perfect 1.00 match score comes from
- 0.55 amount matches to the cent
- 0.30 same-day order date
- 0.15 same merchant name
Each signal also gives partial credit. That is what lets a charge match its receipt when the card posted a day late or the total moved by a few cents.

| Score | What SpendifiAI does | Example that lands here |
|---|---|---|
| 0.80 to 1.00 | Links the charge to the order, categorizes it from the priciest line item, closes the open question | Exact amount (0.55) and next-day date (0.27) |
| 0.60 to 0.79 | Stores a match candidate for you to confirm | Amount within $1 (0.35), same day (0.30) |
| Below 0.60 | No match; the charge stays unreconciled | Amount within $5 (0.10), 2 to 3 days apart (0.18), similar name (0.08) |
Why the amount carries the most weight
Merchant names are the least reliable signal. A bank descriptor is often truncated, prefixed by a payment processor or shows a parent company, which is why the merchant signal is a bonus worth at most 0.15 and matching never depends on it. An exact amount plus a date within one day is enough on its own to clear 0.80. The same logic drives the search: the exact amount is always one of the queries, and emails containing it are parsed first.
What happens when the receipt total doesn’t match the charge?
Small differences are expected. A total within $1 still earns 0.35: with a same-day date that is 0.65, a match candidate for you to confirm, and with an exact merchant name as well it reaches 0.80 and links automatically. Differences up to $5 earn only 0.10, so even a same-day order from the same merchant tops out at 0.55, just under the review threshold. That is deliberate: a partial shipment or a split charge should not be linked on a guess.
Why Timezones Matter in a Finance App
The same commit added a timezone to every user account. Your browser’s timezone is detected with the standard Intl API when you register or sign in with Google and saved to your profile (validated against the full list of timezone names), with a default of America/New_York when nothing is sent. Dates on the dashboard, question cards, subscription cards and store order lists are then formatted in that timezone instead of the server’s.
That sounds cosmetic until you are matching receipts by hand. A purchase made late in the evening can show up a day off when a date is rendered in the wrong zone, and then the receipt email and the transaction appear to disagree when they do not. Showing both in your own timezone keeps the dates you see consistent with the ones in your inbox.
The two fallback phases run only for Outlook and only for a single transaction you asked about; Gmail and IMAP rely on their own search. Amounts are matched as plain figures with two decimals, so a total written with a thousands separator, such as $1,234.56, will not match the text scan for 1234.56. The known-sender map covers 12 merchants, so other stores go straight from Phase 1 to the body scan, which stops at 500 messages.
Receipt Matching Across Gmail, Outlook and IMAP
SpendifiAI connects to email three ways, and each uses the search its provider supports best.
| Provider | How receipts are searched | Fallback |
|---|---|---|
| Gmail (OAuth) | Quoted merchant or amount query, trash excluded, from the start of the window; up to 20 results | Tries the amount with and without a dollar sign |
| Outlook (OAuth, Microsoft Graph) | $search with client-side date filtering; 20 or 50 results |
Phase 2a known senders, then Phase 2b body scan |
| Other mailboxes (IMAP) | Server-side text search | None beyond the provider search |
Outside single-transaction searches, SpendifiAI also runs a broader transaction-guided sync. It looks at unreconciled spending over the last 90 days, takes the 20 merchants you spent the most with (charges over $10), searches for each, and adds an amount search for any merchant with a single charge of $50 or more. That sync is what matches receipts without you pressing anything.
Who This Is For
Receipt matching is most useful when your bank lines hide what you bought: marketplace orders that cover several unrelated items, stores that sell everything, and anything you may need to prove later for taxes. It works alongside the rest of the app, including subscription tracking and charitable giving totals. For the full picture of the product, read the SpendifiAI platform overview.
SpendifiAI is built by Parkk Technology on Laravel 12 and PHP 8.3 with a React 19 and Inertia front end, PostgreSQL and Redis, with the Claude API doing the email parsing and categorization.
SpendifiAI
Source code for SpendifiAI, the AI-powered personal finance platform.
Frequently Asked Questions
What if the receipt total doesn't match the bank charge exactly?
A total within
Frequently Asked Questions
still scores 0.35 of the possible 0.55 for amount, and within scores 0.10. WithinFrequently Asked Questions
and on the same day, the pair lands in the review band for you to confirm, or links automatically if the merchant name also matches. A difference ofFrequently Asked Questions
to can reach at most 0.55, so it is never linked on its own.How do I match receipts to bank transactions automatically?
Connect the email account your receipts go to and let software pair each charge with its order email. SpendifiAI searches Gmail, Outlook or IMAP for the receipt, reads it with Claude, and scores it against the transaction on amount, date and merchant. Scores of 0.80 or higher are linked automatically; 0.60 to 0.79 are held for you to confirm.
Does a bank statement count as a receipt for taxes?
The IRS lists account and credit card statements among the documents that support an expense, together with receipts and invoices. A statement proves you paid and whom you paid, but not what you bought. Keeping the itemized receipt with the bank line is the stronger record, which is what receipt matching gives you.
How do I find receipts in my email?
Search for the merchant name and a word like order or receipt, then narrow to a few days around the charge date. Searching for the exact amount can work, but some search indexes, including Outlook’s via Microsoft Graph, do not reliably index amounts inside HTML order tables. SpendifiAI runs these searches for you from the transaction itself.
Why is Outlook search not finding emails with a dollar amount?
Receipt totals usually sit inside HTML tables, and SpendifiAI’s Outlook integration found that Microsoft Graph’s search index does not reliably index amounts there. Graph also cannot combine a search with a date filter in one request. SpendifiAI works around both by searching known receipt senders by date, then scanning message bodies in the date range for the exact amount.
What if the receipt total doesn’t match the bank charge exactly?
A total within $1 still scores 0.35 of the possible 0.55 for amount, and within $5 scores 0.10. Within $1 and on the same day, the pair lands in the review band for you to confirm, or links automatically if the merchant name also matches. A difference of $1 to $5 can reach at most 0.55, so it is never linked on its own.
Which email providers can SpendifiAI search for receipts?
Gmail and Outlook connect with OAuth, and other mailboxes connect over IMAP. All three are searched for receipts; the extra Outlook fallback phases exist because of how Microsoft Graph search behaves with amounts in HTML emails.
How far back does SpendifiAI search for a receipt?
For a single transaction, it searches from 7 days before the charge to 2 days after it. The background sync looks at unreconciled spending from the last 90 days.
Need matching logic that holds up on real data?
Parkk Technology builds products like SpendifiAI end to end, from inbox integrations to the AI that reads them. If your software has to reconcile messy records from several sources, let’s talk about it.