Ping your expenses. MoneyPing handles the rest.
MoneyPing is a lightweight personal finance automation bot built with .NET and Telegram.
Send a transaction in natural language and MoneyPing turns it into structured financial data, detects whether it is an expense or income, resolves its category and account, and generates a Cashew App Link ready for confirmation.
spent 25 at Lidl using AIB
MoneyPing replies with something like:
🧾 Lidl
💶 €25.00 · Expense
🏷 Groceries
🏦 AIB
📅 10/09/2026
[ ✅ Add to Cashew ]
- Natural-language transaction input through Telegram
- Expense and income detection
- Amount extraction and validation
- Merchant/source extraction
- Automatic category resolution
- Account detection
- Date parsing (
today/yesterday) - Ambiguous transaction rejection
- Telegram user allow-list for private usage
- Cashew App Link generation
- Inline confirmation before creating transactions
- Unit tests with xUnit
- Zero LLM/API cost in V1
MoneyPing deliberately keeps V1 deterministic and inexpensive. Transactions are currently parsed using regex, rules, and heuristics rather than an LLM.
- .NET 10
- C#
- Telegram.Bot
- xUnit
- Cashew App Links
- JSON-based category rules
Telegram
│
▼
Natural-language message
│
▼
TransactionParser
│
├── Detect transaction type
├── Extract amount
├── Extract merchant/source
├── Extract account
└── Resolve date
│
▼
CategoryResolver
│
├── merchant-rules.json
└── income-rules.json
│
▼
CashewLinkBuilder
│
▼
Telegram inline button
│
▼
Cashew App Link
The Telegram bot does not directly modify a remote Cashew database.
Instead, MoneyPing generates a Cashew App Link containing the structured transaction. When the user taps the button, Cashew handles the transaction on the device.
Install:
- .NET 10 SDK
- Telegram
- Cashew
Verify your .NET installation:
dotnet --versionOpen Telegram and start a conversation with:
@BotFather
Create a new bot:
/newbot
Choose a bot name and username.
BotFather will return an API token similar to:
1234567890:AAxxxxxxxxxxxxxxxxxxxxxxxx
Keep this token private.
Never commit it to Git.
git clone https://github.com/YOUR_USERNAME/moneyping.git
cd moneypingRestore dependencies:
dotnet restoreMoneyPing uses the standard .NET configuration system.
For local development, use .NET User Secrets instead of storing credentials inside appsettings.json.
Initialize User Secrets if needed:
dotnet user-secrets initSet your Telegram token:
dotnet user-secrets set "Telegram:BotToken" "YOUR_BOT_TOKEN"Initially, you can run the bot without restricting the Telegram user.
Start MoneyPing:
dotnet runThen send:
/whoami
to your Telegram bot.
MoneyPing will return your Telegram numeric user ID.
Store it:
dotnet user-secrets set "Telegram:AllowedUserId" "YOUR_TELEGRAM_USER_ID"Restart the application.
Now only your Telegram account can interact with the bot.
MoneyPing uses rule files to map merchants and income sources to Cashew categories.
Edit:
merchant-rules.json
Example:
{
"lidl": "Groceries",
"aldi": "Groceries",
"tesco": "Groceries",
"dunnes": "Groceries",
"dealz": "Groceries",
"luas": "Transport",
"dublin bus": "Transport",
"irish rail": "Transport",
"uber": "Transport",
"free now": "Transport",
"mcdonald": "Eating Out",
"burger king": "Eating Out",
"deliveroo": "Eating Out",
"just eat": "Eating Out",
"48": "Bills",
"chatgpt": "Subscriptions",
"netflix": "Subscriptions",
"spotify": "Subscriptions",
"rent": "Bills and fees",
"trash": "Bills and fees",
"internet": "Bills and fees",
"energy": "Bills and fees"
}The category names must match your Cashew categories.
Edit:
income-rules.json
Example:
{
"tk maxx": "Income",
"salary": "Income",
"wage": "Income",
"paycheck": "Income",
"freelance": "Income",
"client": "Income",
"consulting": "Income",
"refund": "Income",
"reimbursement": "Income",
"cashback": "Income",
"interest": "Income",
"sale": "Income",
"sold": "Income"
}Start MoneyPing:
dotnet runOpen your bot on Telegram and send transactions such as:
spent 25 at Lidl using AIB
spent 4.20 on Luas using Revolut
paid 8.99 at Dealz yesterday using AIB
received 300 from TK Maxx
received 150 from freelance work
MoneyPing requires the transaction type to be clear.
For example:
25 at Lidl using AIB
will be rejected because MoneyPing cannot safely determine whether the transaction is an expense or income.
This prevents accidental transactions from being created with the wrong type.
V1 intentionally supports a small set of date expressions.
Currently supported:
today
yesterday
Examples:
spent 20 at Tesco today
spent 12 at Lidl yesterday
If no date is specified, MoneyPing uses the current date.
MoneyPing can extract an account from the message.
Example:
spent 25 at Lidl using AIB
Produces approximately:
{
"amount": -25,
"title": "Lidl",
"category": "Groceries",
"account": "AIB"
}Another example:
spent 25 at Lidl using Revolut
uses Revolut instead.
If MoneyPing cannot identify an account, Cashew can handle account selection when the transaction is opened.
MoneyPing uses xUnit for automated tests.
Run:
dotnet testTests currently cover scenarios such as:
Expense parsing
Income parsing
Missing amount
Missing transaction type
Yesterday date parsing
Example:
[Fact]
public void Should_Parse_Expense()
{
var result = _parser.Parse(
"spent 25 at Lidl using AIB");
Assert.True(result.Success);
var transaction = result.Transaction!;
Assert.Equal(-25m, transaction.Amount);
Assert.Equal("Lidl", transaction.Title);
}Unit tests are especially important because the V1 parser is deterministic and rule-based. They help ensure that adding support for new sentence structures does not break existing behavior.
Do not store secrets inside:
appsettings.json
or:
appsettings.Production.json
These files should only contain non-sensitive configuration and may safely be committed to Git when no secrets are present.
For development:
.NET User Secrets
For production:
Platform Secret Store
↓
Environment Variables
↓
.NET IConfiguration
Example production environment variables:
DOTNET_ENVIRONMENT=Production
Telegram__BotToken=YOUR_TOKEN
Telegram__AllowedUserId=YOUR_TELEGRAM_ID
The double underscore:
Telegram__BotToken
maps to:
Telegram:BotToken
inside .NET configuration.
MoneyPing V1 intentionally stays small.
It currently expects one transaction per message and relies on deterministic parsing.
Complex messages such as:
spent 25 at Lidl and 4.20 on Luas using AIB
are not yet fully supported.
The current parser also recognizes only a limited vocabulary for transaction types, dates, accounts, and sentence structures.
The next major version will introduce an LLM-backed parser for complex natural-language inputs while keeping the current deterministic parser for simple transactions.
Instead of relying exclusively on regex, an LLM will convert natural language into structured transaction data.
For example:
I spent 25 at Lidl and then 4.20 on the Luas, both using AIB
could become:
{
"transactions": [
{
"type": "expense",
"amount": 25.00,
"title": "Lidl",
"account": "AIB"
},
{
"type": "expense",
"amount": 4.20,
"title": "Luas",
"account": "AIB"
}
]
}MoneyPing will still keep execution deterministic:
LLM
│
│ understands natural language
▼
Structured transaction
│
▼
.NET validation
│
▼
Category resolution
│
▼
Cashew integration
The model interprets the request.
The application validates and executes it.
Planned improvements include multi-transaction messages, richer date parsing, learned merchant rules, transaction confirmation, additional budgeting integrations, Docker support, and 24/7 deployment.
V1 — Working MVP
The current version supports single expense/income transactions, merchant categorization, account detection, date parsing, Telegram confirmation, and Cashew integration.
AI-assisted parsing and multi-transaction messages are planned for V2.
Personal finance tracking is useful.
Opening a budgeting app every time you buy something is not.
MoneyPing reduces that friction to a Telegram message:
spent 4.20 on Luas using AIB
Ping it.
Track it.
Done. 💸
This project is licensed under the MIT License.