A Google Apps Script that reads your bank transaction emails and SMS, extracts the details, and creates one-tap Cashew links to add them to your budget tracker. No manual data entry needed.
The script is highly configurable, supports automated email labeling, and integrates error reporting. It generates deep links for the Cashew app on both web and mobile, so you can add transactions to your accounts with a single tap.
-
You make a transaction. Your bank sends a transaction email or SMS.
-
Gmail labels it automatically. A Gmail filter tags the email with a label like
Txs/💳(for bank emails) orTxs/💬(for SMS forwarded as email). For SMS, the SmsForwarder app on your phone forwards the SMS to Gmail first. -
TPS runs on a time-driven trigger (e.g., every 15 minutes) or you trigger it manually by visiting the deployed web app URL.
-
Regex rules parse the amount, date, account, merchant, and category from each message.
-
You get a summary email with every transaction laid out in a table. Each row is color-coded:
- Orange = expense (debit)
- Green = income (credit, refund, salary, cashback)
- Red = duplicate (already exists in your sheet, excluded from batch URL)
- Blue = transfer (in the dedicated Transfers section)
The email includes two types of links. PC links open in a browser and take you to the Cashew web app where you can review and confirm. Mobile links open the Cashew mobile app directly (requires the app to be installed on your phone). Both link types have an Approve button (adds all non-duplicate transactions in one go) and an Edit button (opens the edit view so you can tweak before saving). Budget tags like
[Monthly Budget]appear next to the category when auto-assigned. -
Transfers get their own section with per-row PC and Mobile approve links. Self-linked transfers, ATM withdrawals, and NEFT/UPI transfers are detected automatically. Transfers to unknown external accounts (where the destination account is not in your
accountKeywordMap) appear in the main Transactions table as regular debits instead.
-
Errors notify you immediately when a message fails to parse, so you can fix the regex. The email gets labeled
Txs/🛠️(To Fix) and will be excluded from future runs until the rule is updated. See Reprocessing failed emails for steps to retry.
-
Everything is logged in a Google Sheet for analysis, reprocessing, and audit. Each row includes a single-transaction URL so you can approve individual transactions even outside the batch email. The sheet also holds your test cases.
- Automated processing of bank emails and SMS (forwarded via SmsForwarder)
- Transfer support for account-to-account, NEFT, and UPI transfers with separate Cashew transfer links
- Regex-based parsing with per-bank/per-message-type rules for date, amount, account, merchant, category
- Smart categorization using a keyword hierarchy (category > subcategory > keywords) with budget assignment
- Title normalization to map noisy raw titles to clean, stable names in Cashew (see below)
- Budget auto-assignment from categories/subcategories to Cashew budgets (see below)
- HTML body parsing for emails where the plain text is truncated or missing. Some emails render visible text using HTML/CSS (e.g., styled tables or divs) but their plain text version is empty or has only a short fallback like "View in browser". For these, add the Rule ID to
HTML_BODY_RULE_IDSand TPS will parse the full HTML instead. - Duplicate detection against existing sheet rows, with O(n) performance
- Gmail labeling per-email (not per-thread) so partially failed threads can be reprocessed. If one email in a thread succeeds and another fails, only the failed one is retried on the next run. See why this matters.
- Sanity test framework to validate your regex rules against expected outputs
- Configurable everything -- processing filters, error handling, test modes, rerun behavior
Bank transaction descriptions are often messy. You might see PYU*Instamart Grocery, AMAZON PAY IN GROCERY, or WALMART GROCERY for what is really just a grocery purchase. Title normalization maps these raw titles to a clean, consistent name in Cashew.
You configure this in titleMappingConfig in your userConfig. There are two types of mappings:
- Exact mappings: Raw title contains one of the listed strings, so it maps directly. For example,
["WALMART GROCERY", "AMAZON FRESH"]both map to"Groceries". - Contextual mappings: When the exact title is too generic (like
"PURCHASE"), TPS looks at the notes field for additional context. For example, if notes contain "WHOLE FOODS", the title becomes"Groceries"instead of the generic"Misc Purchase".
You will want to come back and update these mappings as you notice new raw titles appearing in your Cashew transactions that could use a cleaner name.
If you use Cashew's "Added Only" budgets to track spending limits (like a "Monthly Bills" or "Consumables" budget), TPS can automatically assign the right budget to each transaction based on its category or subcategory.
Configure this in categoryToBudgetMap. The lookup checks subcategory first (more specific), then category (broader). Budget names must exactly match the budget names in your Cashew app. Transactions without a matching budget are left unassigned.
- Create a Google Apps Script project
- Upload the 5 script files in order (constants, userConfig, main, utils, test)
- Add the Moment.js library and enable the Gmail API
- Copy
2-sampleUserConfig.gs, customize the regex rules for your banks - Store your credentials via
storeSecrets()and delete them from the file - Set up a Google Sheet and configure a time-driven trigger
Full step-by-step instructions: Setup Guide
Once setup is done, this is the day-to-day flow:
- You make a transaction (swipe card, UPI payment, online purchase, etc.).
- Your bank sends an email or SMS.
- If it's an SMS, SmsForwarder on your phone forwards it to Gmail as an email.
- Gmail's filter auto-labels the email (e.g.,
Txs/💳for bank emails,Txs/💬for SMS). - TPS runs at the next scheduled trigger (or you visit the
/execURL to run it now). - TPS processes all unread, labeled emails and sends you a summary.
- Success? Open the Approve link from the email (PC or Mobile) to add transactions to Cashew. Done.
- Error? The failing email gets labeled
Txs/🛠️and you receive an error email with the message body and what went wrong. Fix the regex in your userConfig, then reprocess usingtest.gs. - Duplicate? The row shows up in red in the summary but is excluded from the Approve URL. If you still want it, use the single-transaction URL from the Google Sheet row.
| Label | Applied by | Meaning |
|---|---|---|
Txs/💳 |
Gmail filter | Bank transaction email, waiting to be processed |
Txs/💬 |
Gmail filter | Single SMS forwarded as email via SmsForwarder |
Txs/🛜 |
Gmail filter | Offline SMS batch (legacy, used with Automate app) |
Txs/🧪 |
You (manual) | Test case email for sanity tests |
Txs/❌ |
You (manual) | Ignored email, skipped by TPS even if other labels match |
Txs/✅ |
TPS | Successfully processed, will not be picked up again |
Txs/🛠️ |
TPS | Failed to process, needs a regex fix. Excluded from future runs until fixed and reprocessed. |
TPS handles SMS through SmsForwarder, an Android app that forwards bank SMS and app notifications to your Gmail as emails. TPS then processes them the same way it processes regular bank emails.
This gives you coverage for transaction types that banks only report via SMS (UPI, debit card POS, instant transfers) without any changes to TPS's email-based pipeline.
Full setup walkthrough with rule examples: SMS Setup Guide
- In Apps Script, go to
Deploy > New Deployment, selectWeb App, set access to "Anyone with the link", and save the URL. - The
/execendpoint runs the last deployed version. The/devendpoint runs the latest saved code without redeploying (useful for testing). - For automated runs, go to
Triggers > Add Triggerand set up a time-driven trigger (e.g., every 15 minutes or hourly).
Full deployment and dev guide: Development Guide
Once TPS is set up and running, you will regularly need to add new rules or tweak existing ones as your banks change formats or you start using new payment methods. Here are the most common tasks:
- Add the account key and keywords to
baseAccountIdsMap(e.g.,"NEWBANK_SAVINGS": ["NewBank"]) - Add the display name to
accountIdsToDisplayNameMap(e.g.,"NEWBANK_SAVINGS": "NewBank") - Add secret identifiers to
mySecrets(e.g.,ACCOUNT_ID_NEWBANK_SAVINGS: ["XX1234"]) and runstoreSecrets(mySecrets)once - Delete the actual values from the file after storing
- Add the email subject to
emailSubjectToRuleIdMapwith a unique Rule ID - Add the Rule ID to
emailRuleMapwith regex patterns for each field (amount, date, account, merchant, etc.) - If the email has truncated plain text, add the Rule ID to
HTML_BODY_RULE_IDS - Create a Gmail filter for the subject to apply the
Txs/💳label - Test with a real email using
getTestThreadsOrQuery()intest.gs
- Add an identification regex to
smsReferencePatterns(matches the SMS body to determine which rule applies) - Add extraction regex to
smsRuleMap(pulls out amount, account, merchant, etc.) - If this SMS is a duplicate of an email you already process, add the pattern to the
DUPLICATEsection insmsReferencePatternsinstead - If needed, add a SmsForwarder rule on your phone for the new bank's sender ID. See SMS Setup Guide.
Edit categorySubcategoryKeywordMap in your userConfig. Add new merchant keywords to the relevant subcategory. Order matters -- first match wins, so put more specific keywords in earlier categories.
Add an entry to categoryToBudgetMap. The key is the category or subcategory name, the value is the exact budget name in Cashew. Subcategory-level entries take priority over category-level ones.
Edit titleMappingConfig. For a straightforward raw-title-to-clean-title mapping, add to exactTitleMappings. If the raw title is too generic and you need to check the notes field for context, add to contextualMappings.
- Fix the regex rule in your userConfig
- In
5-test.gs, set the failing email's thread ID ingetTestThreadsOrQuery():return ['thread_id']; - Set
DEV_CONFIG.RERUN_READ_MAILS = trueandDEV_CONFIG.MARK_AS_PROCESSED = false - Run via the
/devendpoint ortestDoGet() - If it passes, remove the
Txs/🛠️label from the email and let TPS process it normally on the next trigger
| File | Purpose |
|---|---|
1-constants.gs |
Config objects, enums, labels, error types |
2-sampleUserConfig.gs |
Template with annotated regex rules, categories, accounts (customize this) |
3-main.gs |
Entry point -- email loop, transaction extraction, sheet append |
4-utils.gs |
All utility functions: parsing, email generation, sheet ops |
5-test.gs |
Dev config, test helpers, sanity test framework |
resources/ |
Sample sheet, screenshots, setup guides, changelog |
Transfer transaction support, O(n) duplicate detection, HTML body parsing, budget assignment, title normalization, and a redesigned success email with separate normal and transfer sections. See the full Changelog.
Once your transactions are in Cashew, Cashew-Reports turns them into a visual dashboard - numerous widgets covering burn rate, savings, category breakdowns, spending trends, and more. It reads the same Cashew Outbox CSV and runs as a separate GAS Web App.
If you use both repos, keep the category and subcategory names in sync: TPS's categorySubcategoryKeywordMap should match the CONFIG section in Cashew-Reports' Code.gs.
- Notification scanning for payment apps (Google Pay, PhonePe) via SmsForwarder APP rules
- Bulk SMS sheet import mode for processing offline SMS batches

