← Mikaeel Faraz

Reading bank SMS when banks have no API

October 2026

Mifolio, my personal finance tracker, has no bank connection. It works from the messages banks already send when a card is used. This is how those messages get in, how they are read, and how one purchase that arrives three times ends up counted once.

Getting the messages in

Each source takes a different route to the same endpoint. For SMS, an iPhone Shortcuts automation runs when a message containing the bank’s wording arrives and posts the text. Card emails are picked up by a small Google Apps Script that checks Gmail every five minutes. Apple Wallet taps come from the Shortcuts “Transaction” automation, which sends the card, merchant and amount.

The email script also backfills history, one month at a time. Apps Script stops any run after six minutes, so the backfill schedules itself to carry on every ten minutes until it reaches today.

Reading them

Every format has its own parser, written as plain functions with no network or database access. The same code runs on the server, in a tester on the setup page where I can paste a message and see what it becomes, and in the tests.

A few things the parsers have to handle: one bank’s SMS leaves out the year, so the parser picks the most recent date that is not in the future. Declined transactions are ignored. The first alert from a card teaches Mifolio its last four digits, and later alerts for a different card from the same bank go to the right account or are ignored. Foreign currencies pegged to the dirham convert exactly; the rest are converted at a rough rate and marked as estimates.

Store first, then parse

Each message is saved as it arrived before anything tries to read it, keyed on its message id. That makes retries harmless, and when a bank changes its wording, the messages that failed are still there and can be retried once the parser is fixed. Messages that could not be read show up in the app as needing a look, rather than disappearing.

One purchase, counted once

Click through the messages below to see the checks each one goes through. The rules are the real ones; the messages are made up.

An Apple Wallet tap arrives first

{ card: "Card ending 4821", merchant: "Starbucks", amount: 23.00 }
  • noAlready received this exact message?
  • noSame card, amount and currency in the last 20 minutes?

New purchase. Wallet sends no timestamp, so the time is when the message arrived. That gets corrected if a better source turns up.

What Mifolio stores

08:41 (approx.)Starbucks
from Wallet
WalletAED 23.00

Purchases counted: 1

Inside the 20-minute window, so it is merged into the first coffee and counted once. This is the trade-off of matching on card and amount: two identical purchases this close together look like one.

What I would tell someone building the same thing

Keep the raw messages. Write the parsers so they can be tested against real examples without a server. And make a failed parse something you can see, because banks change their wording without telling anyone.