Visit Google Apps Script and create a new project.
Upload the script files in the following order (files are evaluated in order):
constants(1-constants.gs)userConfig(2-sampleUserConfig.gs -- copy and customize for your setup)main(3-main.gs)utils(4-utils.gs)test(5-test.gs)
The files in GScript use the
.gsextension but are plain JavaScript. You can rename to.jsto work locally.
Follow this guide to add the Moment.js library to your GAS project.
Configure the Gmail API as per Google's documentation.
Use storeSecrets to save sensitive data securely in Script Properties:
const mySecrets = {
EMAIL: "user1@example.com",
SPREADSHEET_ID: "your-spreadsheet-id",
ACCOUNT_ID_BANKA_SAVINGS: ["XX1234", "additional identifier"],
ACCOUNT_ID_BANKA_CC: ["XX5678"],
};
storeSecrets(mySecrets);After running storeSecrets once, delete the actual values from userConfig. The script reads them from Script Properties at runtime.
Import or create a Google Sheet for storing transactions, and set its ID in mySecrets.SPREADSHEET_ID.
A sample spreadsheet is available: Sample Sheet
Define regex patterns to extract transaction details from email content. Example for a credit card debit email:
Your BankA Credit Card XX1234 has been used for a transaction of INR 363.00 on Dec 06, 2024 at 01:18:50.
Info: SAMPLE SERVICE. The Available Credit Limit...
Corresponding rule:
"BankA-CC-Debit": {
dateRegex: /\b([A-Za-z]{3}\s\d{2},\s\d{4})\s*at\s(\d{2}:\d{2}:\d{2})\b/,
fromAccountRegex: /Credit Card\s([A-Za-z0-9]+)\s/,
amountRegex: /transaction of\s[A-Za-z]+\s([\d,]+\.?\d*)/,
merchantRegex: /Info:\s*(.+?)\. The Available Credit Limit/
}Key patterns to know:
(?!)-- Never-match regex. Use when you want the field to fall back to defaults (e.g., use email date instead of extracting from body).null-- No signal. Category/subcategory will be resolved by the keyword map.- Static strings (e.g.,
"Cash","ATM Withdrawal") -- Used as literal values instead of regex extraction.
See 2-sampleUserConfig.gs for a full set of annotated examples covering different patterns.
Set up categorySubcategoryKeywordMap with your expense and income categories. Keywords are matched case-insensitively against the transaction's merchant, category signal, and email body.
const categorySubcategoryKeywordMap = deepFreeze({
expenses: {
"Food": {
subcategories: {
"Eating Out": ["UBER EATS", "DOORDASH", "DOMINOS", "STARBUCKS"],
"Groceries": ["WALMART", "COSTCO", "INSTACART"],
},
},
"Misc Expense": {
subcategories: {
"Uncategorized Expense": [],
},
},
},
incomes: { ... }
});Create the following labels in Gmail and set up filters to tag transaction emails:
| Label | Purpose |
|---|---|
Txs/[credit card emoji] |
All bank transaction emails |
Txs/[speech bubble emoji] |
SMS forwarded as email (single) via SmsForwarder |
Txs/[wifi emoji] |
Offline SMS batch forwarded later (legacy, only needed with the Automate app) |
Txs/[test tube emoji] |
Emails for sanity test runs |
Txs/[x emoji] |
Ignored/invalid emails (skipped) |
Txs/[check emoji] |
Successfully processed emails |
Txs/[wrench emoji] |
Emails needing regex fixes |
Using Gmail API to label individual emails (not threads) means partially failed threads can be reprocessed. Enable "Conversation view off" in Gmail Settings to see per-email labels.