Step-by-step installation guide Β· v1.2 Β· October 2026
Build Your Own Telegram Receipt & Claim Bot (No Coding)
Snap a receipt in Telegram. It lands in Google Sheets, the photo is filed in Google Drive, and your monthly claim form is emailed with one tap.
Target set-up time: typically under an hour.
For freelancers, small-company staff and the bosses who approve their claims. Malaysia-friendly examples (RM), usable anywhere.
Rangka AI Β· rangkaai.my Β· free guide
Contents
- What the bot does, and who it's for
- What you need (and what it costs)
- The plan (typically under an hour)
- Step 1: Create your Telegram bot (5 min)
- Step 2: Get your OpenAI key (5 min)
- Step 3: Paste the code and fill in the settings (10 min)
- Step 4: Run setup and allow Google permissions (5 min)
- Step 5: Make yourself admin and test it (5 min)
- Daily use: commands and tips
- Customising categories, limits and mileage
- Adding team members
- Privacy and security
- Troubleshooting FAQ
- Updating the bot and keeping it running
- Final checklist
This free guide does not include the code itself. Things change. Prices, button names and screens at Google, Telegram and OpenAI can change after this guide was written (October 2026). If a button has moved, look for a similar name nearby. The steps stay the same.
1. What the bot does, and who it's for
Most people keep claim receipts in a wallet or a drawer, then spend an evening at month end typing them into a form. This bot does that job as you go. You send it a receipt from your phone, and that's it.
You send the bot any of these:
- a photo of a receipt, or a PDF invoice
- a Google Maps or Waze screenshot of a drive (for mileage)
- a voice note: "parking five ringgit, cash"
- a typed message: "lunch with client RM45" or "mileage 48km office to Shah Alam"
The bot then:
- reads the date, shop, amount and payment method, and picks a category;
- adds a row to your own Google Sheet;
- saves the receipt in your own Google Drive, sorted as
Year / Person / Month, with a clear file name like2026-10-14_Fuel_Petronas_RM50.00.jpg; - replies with what it saved, plus an Undo button in case it got something wrong.
At month end you type /generate to get an Excel claim form, or /submit to email the form, with every receipt attached, to your boss or accounts. The bot asks you to confirm before it sends anything.
you send a receipt
the bot's "brain"; checks Telegram every minute
reads the receipt / voice note
one row per claim
receipt files, private
/submit emails the claim form
Everything runs in your Google account. There is no server to rent and no app to install. You only pay OpenAI a few sen per receipt.
Who it's for
- Freelancers and sole proprietors who want every business receipt in one place for tax time.
/year 2026gives a yearly summary with links to each receipt. - Small companies (about 2 to 10 people) where staff claim fuel, toll, parking, meals and mileage. Each person sees only their own claims. The owner (the admin) approves who can use the bot and can pull everyone's claim forms.
- Anyone tired of chasing receipts: sales reps, technicians, site staff, consultants.
What it is not
- It is not accounting software, and it does not pay anyone. It records claims and produces the form; your normal approval and payment process stays the same.
- It is not a mobile app. It lives inside Telegram, which you probably already use.
- The AI is good but not perfect. Glance at each reply and tap Undo if something is wrong.
2. What you need (and what it costs)
| You need | Notes | Cost |
|---|---|---|
| A Google account | A normal free Gmail account works. Google Workspace works too. | Free |
| Telegram on your phone | Plus Telegram Desktop or web.telegram.org on your computer to make copying easier. | Free |
| An OpenAI API account | This is separate from a ChatGPT subscription; ChatGPT Plus does not include API credit. You add a small prepaid credit. | Pay as you go |
| A computer with Chrome, Edge or Safari | Setup is done on a computer. Daily use is on your phone. | n/a |
| The Code.gs file | One file, copy-paste only. This free guide explains every step; the ready-to-paste code is sold separately: get the code. | see below |
| About 1 hour set aside | A target, not a promise: the steps below typically take 30 to 40 minutes, and the rest is buffer for permission screens and typos. | n/a |
How much does OpenAI cost per receipt?
Honest numbers, worked out from OpenAI's published prices as of 17 October 2026 for the default models in the code (gpt-4o-mini for reading, whisper-1 for voice):
| What you send | Approximate cost each |
|---|---|
| Receipt photo | about US$0.004 (around 2 sen) |
| Voice note (15 seconds) | about US$0.002 |
| Typed claim ("parking RM5") | under US$0.0002 |
| PDF invoice (1 page) | usually similar to a photo or less; depends on the file |
So 100 receipt photos a month comes to roughly US$0.40. A team of five sending 300 receipts a month would be around US$1.20 to 1.50.
These are estimates. Your real cost depends on photo size and on OpenAI's prices at the time. The bot records every request and its estimated cost in the TOKEN_LOG tab of your Sheet, messages the admin if one day's spend passes US$0.50, and has a daily spending cap (default US$1.00): before each AI call it checks that the call fits under the cap; if not, receipt photos and voice notes pause until the next day, while simple typed claims still work. You can change both numbers. These are the bot's own estimates, so also set a monthly budget limit in your OpenAI account (Step 2). Check current prices at openai.com/api/pricing or platform.openai.com/docs/pricing.
Model retirement notice. OpenAI has announced that gpt-4o-mini will be retired on 14 January 2027 and whisper-1 on 26 February 2027. When that happens, you change one or two words in the settings (see Section 14: Updating). The setup step also checks this and warns you if your model has a shutdown date.
This guide is free. The bot itself is one ready-to-paste file, Code.gs, sold with a PDF copy of this guide. You need it for Step 3.
- One file; no coding; paste it and fill in a few settings
- Receipts, PDFs, mileage screenshots, voice notes and typed claims
- Excel claim form, one-tap email submit, team approvals
3. The plan (typically under an hour)
| Step | What you do | Time |
|---|---|---|
| 1 | Create the Telegram bot with @BotFather and copy its token | 5 min |
| 2 | Create an OpenAI API key and add a little credit | 5 min |
| 3 | Make a Google Sheet, paste Code.gs, fill in the settings, save the token and key | 10 min |
| 4 | Run setup and click through Google's permission screens |
5 min |
| 5 | Make yourself admin, register, and send a test receipt | 5 min |
| Total hands-on time (target) | typically about 30 min |
Before you begin: open a notepad (Notes, Notepad, Google Keep) to hold your token and key for a few minutes only. Delete the note when you finish Step 3.
Golden rule: your Telegram token and OpenAI key are like passwords. Never paste them into a chat, an email, a screenshot, or into the code itself. In Step 3 they go into a protected place called Script Properties.
4. Step 1: Create your Telegram bot (5 min)
Telegram has an official bot called @BotFather that creates bots for you. It's free.
- In Telegram, search for @BotFather. Pick the one with the blue verified tick, then tap Start.
- Send
/newbot. - BotFather asks for a name. This is the display name people see, for example
My Company Claims. - Next it asks for a username. It must be unique and end in
bot, for examplemycompany_claims_bot. If the name is taken, try another. - BotFather replies with a message containing your token. It looks like this made-up example:
123456789:AAH-Exampl3TokenDoNotUse_abcdefghijk
- Copy the token into your temporary note.
Optional, but nice:
/setuserpic: give the bot your company logo./setdescription: for example, "Send me your receipts. I'll log them for your monthly claim."
Lost or leaked the token? In @BotFather send /revoke, choose your bot, and you get a new token. The old one stops working at once. Put the new token in Script Properties (Step 3) and run setup again.
5. Step 2: Get your OpenAI key (5 min)
- Go to platform.openai.com and sign up or log in. You can use the same login as ChatGPT, but API billing is separate.
- Open Settings β Billing and add a payment method plus a small prepaid credit. US$5 lasts most small users many months; see the costs in Section 2.
- Recommended: under Settings β Limits (or your project's limits), set a monthly budget or alert so you can never be surprised.
- Open API keys and click Create new secret key. Name it
Claim Bot. - Copy the key now. OpenAI shows it only once. It starts with
sk-. Paste it into your temporary note.
Use a new key just for this bot. Don't reuse a key from another app. Then, if you ever stop using the bot, you can delete this one key without breaking anything else.
6. Step 3: Paste the code and fill in the settings (10 min)
3a. Create the Google Sheet
- On your computer, go to sheets.new (or Google Drive β New β Google Sheets).
- Name it, for example, Claim Bot Data. This Sheet becomes your bot's database.
3b. Open Apps Script and paste the code
- In the Sheet menu, click Extensions β Apps Script. A new tab opens with a code editor and a file called
Code.gs. - At the top left, click Untitled project and rename it Claim Bot.
- Click inside the editor, select everything (Ctrl+A, or Cmd+A on a Mac) and delete it. The editor should be completely empty.
- Open Code.gs from the code package you bought in a text editor (Notepad, TextEdit, or VS Code), select all, copy, and paste it into the empty editor.
- Click the πΎ Save icon (or Ctrl+S).
3c. Fill in the settings (the CONFIG block)
The top of the code has a block called CONFIG. That's the only part you edit. Change the values between the quote marks and leave the quotes, commas and colons alone.
COMPANY_NAME: "My Company Sdn Bhd", β¬ printed on the claim form CURRENCY: "RM", β¬ or "SGD", "USD", ... MILEAGE_RATE: 0.60, β¬ per km; no quotes around numbers TIMEZONE: "Asia/Kuala_Lumpur", β¬ e.g. "Asia/Singapore", "Asia/Jakarta"
Click πΎ Save again.
Don't put your token or key in CONFIG. The code doesn't even look for them there. They go in the next step.
3d. Save the token and key in Script Properties
Script Properties is a small, private storage area attached to your script. It isn't part of the code, so the code can be copied or updated without exposing your secrets.
- In the Apps Script left sidebar, click the βοΈ gear icon (Project Settings).
- Optional: under General settings, check that Time zone is yours, e.g. (GMT+08:00) Kuala Lumpur.
- Scroll to the bottom, to Script Properties, and click Add script property.
- Add these three properties. Type the names exactly, in capitals with underscores:
| Property | Value |
|---|---|
TELEGRAM_BOT_TOKEN | paste the token from Step 1 |
OPENAI_API_KEY | paste the key from Step 2 (starts with sk-) |
SUBMIT_TO_EMAIL | who receives /submit claim emails, e.g. accounts@mycompany.com. Use your own email while testing. |
- Click Save script properties.
- Delete your temporary note with the token and key. You won't need it again.
Why Script Properties and not CONFIG? Settings that are personal or sensitive (keys, your Telegram ID, the claims email) stay out of the code. The code has no built-in admin ID or email to fall back on: the bot reads them only from Script Properties. So you can share or update Code.gs without exposing them, and editing the code can't change where claims are emailed. Setup stops with a clear message if SUBMIT_TO_EMAIL is missing or isn't an email address. To change the email later, use the Sheet menu Claim Bot β Set submit-to email (or edit the property). Whenever the admin ID or the email changes, the bot messages the previous admin on Telegram, so a quiet change by another Sheet editor doesn't go unnoticed.
7. Step 4: Run setup and allow Google permissions (5 min)
- Click the < > Editor icon in the left sidebar to go back to the code.
- In the toolbar above the code there's a drop-down showing a function name. Choose setup.
- Click βΆ Run.
The permission screens (don't panic)
The first time you run it, Google asks you to approve what the script may do. This is normal for any Apps Script.
- "Authorization required": click Review permissions.
- Choose your Google account.
- "Google hasn't verified this app": click Advanced (small link, bottom left), then Go to Claim Bot (unsafe).
- A list of permissions appears. If you see checkboxes, tick Select all. Then click Allow (or Continue).
Why "unverified" and "unsafe"? Google shows this for every private script that hasn't gone through Google's app review. Review is meant for apps published to the public. Here, you are both the developer and the only user: the script runs in your account and sends your data only to Telegram and OpenAI. You can read the code yourself, because it's all in one file. Never click through this warning for scripts from people you don't trust.
What each permission is for:
| Google asks to⦠| Why the bot needs it |
|---|---|
| See, edit, create and delete your spreadsheets | Write claim rows; build the Excel claim form |
| See, edit, create and delete your Google Drive files | Save receipts into the bot's folder; trash a receipt when you Undo |
| Connect to an external service | Talk to Telegram and OpenAI |
| Send email as you | /submit emails the claim form |
| Allow this application to run when you are not present | The 1-minute check that keeps the bot running |
Check the result
After you allow access, setup runs (if it doesn't, click βΆ Run once more). At the bottom, the Execution log should show something like:
SETUP COMPLETE Telegram bot OK: @mycompany_claims_bot OpenAI key OK. Text model gpt-4o-mini is available OpenAI key OK. Voice model whisper-1 is available WARNING: OpenAI will retire ... (shown if OpenAI reports a retirement date) Text model: gpt-4o-mini ($0.15 / $0.6 per 1M tokens) Voice model: whisper-1 ($0.006 per minute) Sheet ready: Claim Bot Data (tabs: Claims, STAFF, SUBMISSIONS, TOKEN_LOG, CHAT_LOG) Drive folder ready: Claim Bot Receipts (private: only you can open it) 1-minute trigger created (the bot checks Telegram every minute). Daily log clean-up: TOKEN_LOG/CHAT_LOG rows older than 90 days are deleted (about 3am). Submit-to email: accounts@mycompany.com Email quota left today: 100 Bank details: not collected (default) AI daily cap: $1/day REQUIRED NEXT STEP: the admin is not set, so nobody can register yet. Open @mycompany_claims_bot in Telegram, send /whoami, add the number as Script Property ADMIN_TELEGRAM_ID ...
If you see a red error instead, it tells you exactly what to fix: usually a mistyped Script Property name or a token copied with a missing character. Fix it and run setup again. Running setup again is always safe; it never deletes data.
Switch back to your Sheet tab. You'll see the new tabs (Claims, STAFF, and so on). Reload the Sheet and a Claim Bot menu appears too, with shortcuts for setup, status, setting the admin ID, the submit-to email and the AI models, pause and uninstall. If you run setup from that menu, it asks for any missing token, key or email in a pop-up box instead.
8. Step 5: Make yourself admin and test it (5 min)
5a. Become the admin
- In Telegram, open your bot (search its username, or go to
t.me/your_bot_username) and tap Start. - Send
/whoami. Wait up to one minute. The bot checks for messages once a minute (Section 12 explains why). It replies: Your Telegram ID: 123456789. - Save that number as your admin ID. Either: - Project Settings β Script Properties β Add script property, then click Save script properties:
| Property | Value |
|---|---|
ADMIN_TELEGRAM_ID | 123456789 (your number from /whoami, digits only) |
- or, in your Sheet, use Claim Bot β Set admin Telegram ID and paste the number.
- You don't need to run setup again.
- In Telegram, send
/start. Answer two questions: your name, then your staff number (or typeskip). As admin, you're approved immediately. (The bot does not ask for bank details unless you switch that on; see Section 10.)
5b. Test everything (tick as you go)
- β Send a photo of a real receipt. Within about a minute: "Reading your receipt...", then "Added - Fuel β¦ RM50.00 β¦ Receipt saved to Drive".
- β Open the Sheet's Claims tab: one new row.
- β Open Google Drive: Claim Bot Receipts / 2026 / Your Name / 2026-10 / contains the photo.
- β Type
parking RM5. A second row appears. - β Send a voice note: "toll twelve fifty, Touch n Go". The bot replies with Heard: "β¦" and the entry.
- β Send
/list, then/summary. - β Tap Undo under one of the entries. That row disappears and its receipt goes to Drive Trash.
- β Send
/generateβ This Month. You receive an Excel claim form. - β Send
/submitβ This Month β Send. An email with the form and receipts arrives at theSUBMIT_TO_EMAILaddress. (Use your own email for this test, then change it to the real recipient in Script Properties or with Claim Bot β Set submit-to email.)
All ticked? You're done. π Delete the test entries with /undo, or delete the rows in the Sheet, before you start real claims.
9. Daily use: commands and tips
Logging a claim (no command needed)
| Send this | What happens |
|---|---|
| π· Photo of a receipt | Read, saved to the Sheet, photo filed in Drive |
| π PDF invoice (as a file) | Same as a photo; saved as .pdf |
| πΊοΈ Google Maps / Waze screenshot of your route | Mileage: distance Γ your rate (e.g. 48 km Γ RM0.60 = RM28.80) |
| π€ Voice note, any language the AI understands (English, Malay, β¦) | Turned into text, then logged |
β¨οΈ Typed text: lunch client RM45 card |
Logged; no receipt |
β¨οΈ mileage 48km office to Shah Alam |
Mileage claim |
Commands
| Command | What it does |
|---|---|
/start |
Register (first time) or show the welcome message |
/list |
This month's entries, numbered, with total. /list last = last month, /list 2026-09 = September 2026 |
/summary |
Totals by category. Also works with last, -2, or 2026-09 |
/undo |
Delete your most recent entry, and trash its receipt |
/generate |
Excel claim form for a month (buttons), or /generate 2026-09 |
/submit |
Email the claim form and receipts to the admin-set SUBMIT_TO_EMAIL address, after you confirm. Staff can't change the address |
/year 2026 |
Yearly summary in Excel, with a link to every receipt (handy for tax) |
/whoami |
Shows your Telegram ID |
/help |
Lists all commands |
Admin only: /staff |
List users and their status |
Admin only: /generateall |
One claim form per person, for a month |
Admin only: /year 2026 all |
Yearly summary for everyone |
The keyboard buttons under the chat (This Month, Last Month, Claim Form, Help) are shortcuts.
Tips for good results
- Flat, bright, whole receipt. Put it on a table, include the total, avoid shadows. Crumpled thermal paper is the main cause of wrong amounts.
- Several receipts? Send them one after another. The bot handles them together in the next minute.
- Check the reply. If the amount or category is wrong, tap Undo and send it again, or type it:
Toll RM12.50 14/10 client visit. - Strange date? If a receipt's date is more than about 3 months from today, the bot asks: Keep 01/01/2023 or Use today's date.
- Dates are DD/MM/YYYY (14/10/2026 = 14 October).
- Submitted twice by mistake? The bot warns you if a month was already sent and needs a second, separate Resend tap.
10. Customising categories, limits and mileage
All of these are in the CONFIG block. After any change, click πΎ Save. It takes effect within a minute; you don't need to run setup again (except for WEEKLY_DIGEST and LOG_RETENTION_DAYS).
Categories
The default list suits most freelancers and small companies:
CATEGORIES: ["Meal", "Fuel", "Toll", "Parking", "Mileage",
"Accommodation", "Transport", "Supplies", "Others"],You can rename, remove or add categories, for example "Client Entertainment" or "Training". Rules:
- Keep each name in double quotes, separated by commas, inside the square brackets.
- Keep
"Others"as the catch-all. - If you add your own category, help the AI recognise it with a hint:
CATEGORY_HINTS: { "Client Entertainment": "client lunch, dinner, coffee with customers",
"Training": "course fees, seminar, workshop, exam fee" },- Renaming a category doesn't change rows already saved in the Sheet.
Limits (warning only)
Want a warning when a meal is over RM30 or a hotel over RM250 a night?
CATEGORY_LIMITS: { "Meal": 30, "Accommodation": 250 },The claim is still saved. The reply just adds "Note: above the Meal limit of RM30.00", so the claimant knows before the boss does.
Mileage
MILEAGE_RATE: 0.60means RM0.60 per km. Use your company's rate.MILEAGE_RATE: 0turns off automatic calculation. Staff then type the amount, e.g.mileage 48km RM30.MILEAGE_CATEGORY: "Mileage"must match a name in yourCATEGORIESlist.
Other options
| Setting | What it does |
|---|---|
COMPANY_NAME |
Header of the claim form and email subject |
CURRENCY |
Shown everywhere: "RM", "SGD", "USD", β¦ |
TIMEZONE |
Used for "today", months and timestamps |
MAX_USERS |
Maximum approved users (default 10) |
ALLOWED_USER_IDS |
Telegram IDs approved automatically, e.g. ["111222333", "444555666"] |
| Admin ID and submit-to email | Not in CONFIG. Change them in Script Properties (ADMIN_TELEGRAM_ID, SUBMIT_TO_EMAIL) or the Sheet menu |
DRIVE_FOLDER_NAME |
Name of the receipts folder (set it before the first setup) |
WEEKLY_DIGEST |
true = each user gets a Sunday-evening recap. Run setup again after changing it |
COLLECT_BANK_DETAILS |
false (default): the bot never asks for bank details. true: /start asks for them with a short privacy notice, and they're printed on the claim form and in the /submit email |
DELETE_BANK_REPLY |
Only used when collecting bank details. true (default): after saving, the bot deletes the person's bank-details message from the Telegram chat and says so. If Telegram refuses, the details are still saved and the person is asked to delete the message themselves |
LOG_CHATS |
true keeps a short CHAT_LOG of conversations for troubleshooting: each entry is cut to about 200 characters, and any run of 8 or more digits (account, phone or ID numbers) is replaced by [n digits hidden]. false turns it off |
LOG_RETENTION_DAYS |
Default 90. Every night, TOKEN_LOG and CHAT_LOG rows older than this are deleted. 0 keeps them forever. Run setup again after changing it. Claims are never deleted |
DAILY_COST_ALERT_USD |
Admin alert when the day's estimated AI cost passes this amount (default 0.50) |
DAILY_AI_CAP_USD |
Daily spending cap (default 1.00). Before each AI call the bot reserves a deliberately high cost estimate and only makes the call if it fits under the cap. When it doesn't, photos and voice notes pause until midnight and the bot tells people to resend tomorrow; typed claims like parking RM5 are still saved using a simple built-in reader. Voice transcription counts too. 0 = no cap |
CHECK_AMOUNT_ABOVE |
Default 1000. A receipt read above this amount gets a "Please check" note (a receipt read as RM0.00 always does). 0 turns the high-amount note off |
TEXT_MODEL, VOICE_MODEL |
Default OpenAI models. To switch without editing code, use Claim Bot β Set AI models. See Section 14 |
MODEL_PRICES |
Prices the bot uses for its cost estimates, one row per model. It picks the row for the model in use automatically |
AI_PRICE_IN_PER_M, AI_PRICE_OUT_PER_M, VOICE_PRICE_PER_MIN |
Optional overrides (USD per 1M tokens / per audio minute). Leave them null to follow MODEL_PRICES |
Made a typo and now nothing works? A missing comma or quote breaks the whole script. Apps Script usually underlines the problem in red when you save. Compare your line with the examples above. When in doubt, paste a fresh copy of Code.gs and fill in CONFIG again. Your data and Script Properties are not affected.
11. Adding team members
- Send your staff the bot link:
t.me/your_bot_username. - Each person taps Start, then answers the same two questions (name, staff number), plus bank details only if you turned
COLLECT_BANK_DETAILSon. - You, the admin, get a message with Approve and Reject buttons. Tap one. The person is told straight away.
- That's it. They can start sending receipts.
Good to know:
- Everyone sees only their own claims.
/list,/summary,/undo,/generateand/submitonly ever touch the sender's own rows. - Skip the approval tap: put trusted IDs in
ALLOWED_USER_IDS(ask them to send/whoamifirst). - Bot full? Raise
MAX_USERS. - Remove someone: in the Sheet's STAFF tab, change their Status to
REJECTED. The bot stops accepting their messages within a minute. Their past claims stay in the Sheet. - Fix someone's details (name, staff number, bank if collected) directly in the STAFF tab. To delete someone's bank details, clear that cell.
- Emails come from your Gmail.
/submitsends from the Google account that owns the script, with the sender name shown as e.g. "Aisyah (Claim Bot)". Free Gmail accounts can email up to about 100 recipients a day, more than enough here. - All receipts live in your Drive, sorted by person. If staff need to show a receipt, they're attached to the
/submitemail. - Monthly routine for the admin:
/generateall laston the 1st gives you every person's form for last month.
12. Privacy and security
Your secrets
- The Telegram token and OpenAI key are stored in Script Properties, never in the code. The bot also blanks them out of any error message it sends or logs.
- Be careful who you share the Sheet or script with. Anyone you add as an editor of the Sheet can open the script and its Script Properties. Share as Viewer only, or don't share it at all.
- Receipt images are sent to OpenAI as data, not as a link, so your bot token never leaves Google.
Your data
- Your claims sit in your Google Sheet, and receipts in your Google Drive. The bot never changes sharing settings; every file is private to you until you share it.
- To read a receipt or voice note, the bot sends it to OpenAI. OpenAI says it does not use API data to train its models by default; check its current policy at openai.com/policies.
- Messages pass through Telegram. Bot chats are not end-to-end encrypted, so don't send the bot anything you wouldn't put on a normal receipt.
- Bank details are off by default. The bot only asks for them if you set
COLLECT_BANK_DETAILS: true. Each person then sees a short notice first, explaining that the details are optional and used only to pay claims. They're stored in the STAFF tab. While the setting is on, they're printed on the claim form and also included in the/submitemail (in the email text and the attached claim form), so they go to whoeverSUBMIT_TO_EMAILpoints to. Turn the setting off and they're left out of both. The chat log records only "[registration answer]", not the details themselves. - Logs don't pile up forever.
TOKEN_LOGandCHAT_LOGrows older thanLOG_RETENTION_DAYS(default 90) are deleted automatically every night. Claims, receipts and submissions are kept until you delete them. - Spending is capped, using estimates. Before every AI call (receipt, typed claim, voice transcription), the bot reserves a deliberately high estimate of that call's cost. It makes the call only if today's logged cost + calls still in progress + this estimate stays within
DAILY_AI_CAP_USD. When the answer arrives, the reservation is swapped for the real token count OpenAI reports. What this guarantees: the bot's estimated spend for the day stays under the cap, even for a burst of photos in one minute, as long as no single call costs more than its reservation. A photo is reserved at about 50,000 input tokens, more than a normal photo uses with the default model. A very long PDF, or a new model that counts image tokens differently, could cost more. The figures are the bot's own estimates from published prices, not OpenAI's bill. So also set a monthly budget limit in your OpenAI account (platform.openai.com β Settings β Limits/Billing): that's the hard stop on the OpenAI side. - Short, masked chat log.
CHAT_LOGkeeps at most about 200 characters per message and hides runs of 8 or more digits. Registration answers are never logged.
Using it for staff in Malaysia? Staff claims are personal data under Malaysia's Personal Data Protection Act 2010 (PDPA): names, staff numbers, receipts and, if collected, bank details. See Appendix A: PDPA quick notes (Malaysia) at the end of this guide for a plain-language checklist. It's general information, not legal advice.
Who can use the bot
- Anyone can find a Telegram bot, but nobody can use yours without registering and being approved by you (or being in
ALLOWED_USER_IDS). - The bot only answers in private chats. It ignores groups.
MAX_USERScaps how many people can be approved.
Why replies take up to a minute
The bot doesn't run all the time. Every minute, Google wakes it up for a second or two to check Telegram for new messages. That's why a reply can take up to about 60 seconds.
We chose this on purpose:
- The "instant" method (a webhook) is widely reported to be unreliable with Google Apps Script, including lost photo messages.
- Keeping the bot awake and waiting all the time would burn through the daily trigger time a free Google account allows (about 90 minutes a day).
- A short check every minute is reliable, free, and uses only a small part of that allowance.
Turning it off
| You want to⦠| Do this |
|---|---|
| Pause for a while | Run pauseBot (or Sheet menu Claim Bot β Pause bot). Nothing is read or saved while paused, and no AI is used. The bot still checks Telegram every minute and tells each person once that their message wasn't saved and that they should send it again later. After resumeBot, anything sent before the resume is not processed (those senders are told to resend), so a pause is a real stop, not a delay. Messages sent after the resume are handled normally |
| Remove the bot completely | Run uninstall: stops the trigger and deletes the token, key and settings. Your Sheet and receipts are kept |
| Stop it completely right now (e.g. a leaked token) | Send /revoke to @BotFather for a new token, so the old one stops working, then pause the bot |
| Kill the bot account | Send /deletebot to @BotFather |
| Stop all AI costs | Delete the key at platform.openai.com β API keys |
| Remove Google access | myaccount.google.com β Security β Third-party apps β Claim Bot β Remove access |
13. Troubleshooting FAQ
Where do I see what went wrong?
In Apps Script, click Executions (the β°βΆ icon in the left sidebar). Every run of pollUpdates is listed, and failed ones are red. Click one to read the error. Running checkStatus (or Sheet menu Claim Bot β Check status) also shows a quick health summary.
The bot doesn't reply at all. 1. Wait a full minute; replies aren't instant. 2. Run checkStatus. Is the 1-minute trigger ON? If not, run setup or resumeBot. 3. Check the Executions list for red errors. 4. Did you edit CONFIG and break a comma or quote? Save and look for a red underline.
"Almost ready! The admin is not set yet."
Add the number from /whoami as the Script Property ADMIN_TELEGRAM_ID (or use Claim Bot β Set admin Telegram ID in the Sheet), then send /start again.
"You are not registered yet."
Send /start and answer the questions. If you're staff, wait for the admin to tap Approve.
Setup says "Telegram token was rejected".
The token in Script Properties is wrong or incomplete. Copy it again from @BotFather (or /revoke for a new one), paste it with no spaces, save, and run setup again.
Setup says "OpenAI key was rejected" or the bot says the "credit may be empty".
Check that the OPENAI_API_KEY property is correct and that your OpenAI account has credit (platform.openai.com β Billing). A brand-new account sometimes needs a few minutes after payment.
"Missing Script Properties".
The property names must be exactly TELEGRAM_BOT_TOKEN and OPENAI_API_KEY: capitals, underscores, no spaces.
The amount or category is wrong.
Tap Undo and resend a clearer photo, or simply type the claim. If a category is often wrong, add a hint in CATEGORY_HINTS.
The date is wrong (day and month swapped). Dates are stored as DD/MM/YYYY text on purpose, so Google Sheets can't swap them. If a receipt prints dates the US way (MM/DD), type the claim with the right date instead.
/submit says the email could not be sent.
Check that the Script Property SUBMIT_TO_EMAIL is set (run checkStatus to see it). Then run setup once from the editor, in case Google needs you to approve email permission again. Free Gmail accounts have a daily email limit (about 100 recipients).
The email has links instead of attached receipts. Gmail limits the size of attachments. If one month's receipts add up to more than 20 MB, the bot sends private Drive links instead. Your recipient must ask you for access, or you can send the files yourself.
"Could not create the claim form". Usually a temporary Google hiccup. Try again in a minute. If it keeps happening, run setup again to refresh permissions.
A receipt got no reply, or the bot replied twice.
The bot is built so that a crash or Google's time limit neither drops nor duplicates a message. It finishes each message first and records its Telegram message number in a short "already done" list (the last 500). Only then does it tell Telegram to move on. If a run stops halfway, the next run (a minute later) picks up the same messages again and skips the ones already done. So a receipt that was interrupted is simply read again, and you may see "Reading your receipt..." twice before one "Added" reply. If a message fails three runs in a row, the bot gives up on it and replies "Sorry, I couldn't process your last message... Please send it again". A real duplicate can only happen in the split second between saving a row and recording it as done. The bot also doesn't spot a receipt you sent twice. In either case use the Undo button, /undo, or delete the extra row in the Sheet. If nothing at all happens for more than 3 minutes, check Executions (see above) and the Claims tab.
Google asks for permission again after I changed something. Normal after you paste a new version of the code. Click through the same screens as in Step 4.
I see "Exceeded maximum execution time" or a quota error.
Free Google accounts have daily limits. They reset every 24 hours. If it happens often, set LOG_CHATS: false to reduce work per message.
"Today's AI limit has been reached".
The next AI call wouldn't fit under DAILY_AI_CAP_USD (the bot checks before each call). Photos and voice notes work again after midnight (your time zone). Until then, typed claims with an amount still work. If the cap is too low for your team, raise it in CONFIG and click Save. checkStatus shows today's spend against the cap.
A receipt was saved as RM0.00, or says "Please check".
The AI couldn't read the total (faded or crumpled receipt), or read an amount above CHECK_AMOUNT_ABOVE. The row is saved so nothing is lost. If it's wrong, tap Undo and type the claim instead, e.g. fuel RM50.
"The claim bot is paused" / "was paused when you sent...".
The admin ran pauseBot. Nothing sent during the pause was saved. Send it again once the bot is back on.
The previous admin got a "setting was changed" message.
Someone changed ADMIN_TELEGRAM_ID or SUBMIT_TO_EMAIL (from the Sheet menu or in Script Properties). If that wasn't you, check Project Settings β Script Properties and who can edit the Sheet.
Typed claim says "I couldn't find an amount".
Include a number with the currency, e.g. toll RM12.50 or mileage 48km. Nothing is saved without an amount.
Can I use it in a Telegram group? No. It only works in private chats, so each person's claims stay private.
Can I use a different AI provider? The code is built for OpenAI's API. You can change the model names (Section 14), but not the provider, without editing code.
14. Updating the bot and keeping it running
When OpenAI retires a model
OpenAI regularly retires older models. As of October 2026 it has announced:
| Setting | Default in v1.2 | Retirement date | OpenAI's recommended replacement | Price (standard, as listed 17 Oct 2026) |
|---|---|---|---|---|
TEXT_MODEL |
gpt-4o-mini (US$0.15 / 0.60 per 1M tokens) |
14 January 2027 | gpt-6-luna |
US$0.10 in / US$0.50 out per 1M tokens |
VOICE_MODEL |
whisper-1 (US$0.006 per minute) |
26 February 2027 | gpt-transcribe (or gpt-live-transcribe for live audio) |
gpt-transcribe: US$0.0045 per minute (estimated) |
Sources: platform.openai.com/docs/deprecations, platform.openai.com/docs/pricing, and the model pages for gpt-6-luna and gpt-transcribe. Prices change, so check them again before you switch.
To switch (no code editing):
- In the Sheet, choose Claim Bot β Set AI models.
- Type the new text model (e.g.
gpt-6-luna) and the new voice model (e.g.gpt-transcribe), or leave a box empty to keep the current one. - The bot checks with OpenAI that your key can use each model, and saves it only if it can. The choice is stored as Script Properties
TEXT_MODEL/VOICE_MODEL. - Send a test receipt photo, a test PDF and a test voice note.
Typing default goes back to the model in CONFIG. setup checks both the text model and the voice model, and warns if OpenAI has announced a retirement date. Check the current list at platform.openai.com/docs/deprecations.
Costs follow the model automatically. MODEL_PRICES in CONFIG already lists gpt-4o-mini, gpt-6-luna, whisper-1 and gpt-transcribe, and the bot uses the row for the model in use, both for TOKEN_LOG and for the daily cap. If you choose a model that isn't listed, the bot uses a deliberately high estimate and says so in checkStatus; add a row with its price from OpenAI's pricing page. To force your own figures, set AI_PRICE_IN_PER_M, AI_PRICE_OUT_PER_M or VOICE_PRICE_PER_MIN.
Honest note on testing: this bot is based on a claim bot the author built and uses. This generic v1.2 edition has so far been checked only with a simulated test harness (fake Telegram, Google and OpenAI). It has not yet been run end-to-end on live services, and neither have the newer models. Things to watch when you switch:
- OpenAI's model page for
gpt-6-lunalists text and image input, but doesn't mention PDF files, so test a PDF receipt. gpt-transcribeuses the same transcription endpoint aswhisper-1. OpenAI's list of supported audio formats (mp3, mp4, mpeg, mpga, m4a, wav, webm) doesn't include.ogg, the format Telegram uses for voice notes, so test a voice note.
If anything misbehaves, use Set AI models β default to switch back while the old model is still available.
Installing a newer version of Code.gs
- Copy your current CONFIG values into a note: company, categories, limits, etc. The token, key, admin ID, submit-to email and model choices are in Script Properties and survive the update. Coming from v1.0? It kept
SUBMIT_TO_EMAILandADMIN_TELEGRAM_IDin CONFIG. Add them as Script Properties first; v1.2 no longer reads them from CONFIG. Also renameAI_MODELtoTEXT_MODELif you changed it. - In the editor, select all, delete, and paste the new Code.gs.
- Fill CONFIG back in, then click Save.
- Run setup once and allow permissions if asked.
Your Sheet data, Drive receipts and Script Properties are untouched.
Healthy-bot habits
- Once a month, glance at TOKEN_LOG to see your AI spend (it keeps the last 90 days by default).
- Keep your OpenAI credit above US$1, or turn on auto-recharge with a monthly budget limit set in your OpenAI account.
- Back up the Sheet occasionally: File β Download β Excel.
15. Final checklist
Accounts
- β Telegram bot created with @BotFather; token copied
- β OpenAI account with credit and a monthly budget; new API key copied
- β New Google Sheet created (e.g. Claim Bot Data)
- β Extensions β Apps Script; project renamed Claim Bot
- β Old code deleted; Code.gs pasted; Saved
- β CONFIG filled in:
COMPANY_NAME,CURRENCY,MILEAGE_RATE,TIMEZONE - β Script Properties:
TELEGRAM_BOT_TOKEN,OPENAI_API_KEYandSUBMIT_TO_EMAILsaved - β Temporary note with the token and key deleted
- β
setuprun; permissions allowed; log shows SETUP COMPLETE
Telegram
- β
/whoamiβ ID saved as Script PropertyADMIN_TELEGRAM_ID - β
/startβ registered as admin - β Test photo receipt β row in Sheet + file in Drive
- β Test typed claim and voice note
- β
/list,/summary, Undo button tested - β
pauseBotβ message β told to resend βresumeBottested - β
/generateβ Excel form received - β
/submitβ email received (tested with your own email first) - β
SUBMIT_TO_EMAILproperty changed to the real recipient
Team (optional)
- β Bot link sent to staff; each one approved
- β
MAX_USERSandCATEGORY_LIMITSset the way you want
Security
- β Sheet not shared with editors you don't fully trust
- β OpenAI monthly budget limit set in your OpenAI account;
DAILY_AI_CAP_USDsuits your team - β
COLLECT_BANK_DETAILSleftfalseunless you really need it (and staff were told why) - β
LOG_RETENTION_DAYSset (default 90) - β Calendar reminder for the model retirement dates in Section 14 (switch with Claim Bot β Set AI models, then test a photo, a PDF and a voice note)
Get Code.gs, the licence, a README and a PDF copy of this guide in one download, then follow the steps above. Setup typically takes about 30 minutes of hands-on time.
Get the code βTelegram, Google, Gmail, Google Sheets, Google Drive, Apps Script and OpenAI are trademarks of their respective owners. This guide is independent and not affiliated with or endorsed by them. Prices and screens were checked in October 2026 and may change.
Appendix A: PDPA quick notes (Malaysia)
A plain-English PDPA 2010 (as amended 2024) checklist.
What the bot holds: staff names, Telegram IDs and optional staff numbers (STAFF), claims (Claims, SUBMISSIONS), logs (CHAT_LOG keeps a short copy of each message, about 200 characters, with runs of 8+ digits shown as "[n digits hidden]"; TOKEN_LOG: AI usage) and receipts in a private Drive folder. OpenAI reads receipts, voice notes and typed claims. You are the data controller.
1. Bank details: leave them off. The bank-details setting is off by default. If you turn it on, give staff a notice first (step 2); the bot asks in the private chat and staff can type skip. The answer is not written to CHAT_LOG, and the delete-bank-reply setting (on by default) then deletes it from the chat. Messages older than 48 hours can't be deleted; the bot then asks the person to delete it. Keep the STAFF tab protected. When on, they appear on the claim form and /submit email.
2. Tell staff before they start. Give a BM/English notice; record who received it. Sample:
"The claim bot records your name, Telegram ID, claims, receipt photos and voice notes (transcribed; audio not kept) [and bank details, if you choose to give them] so that we can process expense claims. OpenAI reads it and Google stores it, possibly outside Malaysia. Telegram carries the messages. Only [admin/accounts] can see all claims. Logs are kept for [N] days, and claims and receipts for [period required for tax]. To see or correct your data, contact [name/role]."
Also record your basis for transfers outside Malaysia (usually staff consent in this notice, or necessity for processing claims). OpenAI may keep API inputs for a limited period under its terms; photos and voice notes also stay in the Telegram chat unless deleted.
3. Control who has access.
- Use a separate admin Google account with 2-step verification.
- Anyone who can edit the Sheet can read the Telegram token and OpenAI key in Script Properties. Give staff no access, or Viewer only.
- The admin ID and /submit email live only in Script Properties, and the previous admin is told of any change. Any Sheet editor can still change them, so check both after updates, and the recipient shown at each /submit.
4. Keep data only as long as needed.
- Agree periods with your accountant; claims and receipts follow tax record-keeping rules.
- Use the log retention setting to delete CHAT_LOG and TOKEN_LOG on a schedule.
- Disable leavers in STAFF.
5. Keep the Drive folder private. Keep sharing Restricted; never "Anyone with the link".
6. Rotate keys when in doubt. Rotate if a token or key was exposed, an editor leaves, or costs look wrong.
- New Telegram token:
/revokein BotFather. New OpenAI key: create one, delete the old. - Update Script Properties and run setup again.
Keep the daily spend cap setting on (the cost alert warns earlier). pauseBot() stops processing: the timer keeps running, but messages sent while paused are skipped, never saved later, and each sender is asked to resend. To stop completely, revoke the token.
7. Breach checklist (e.g. leaked token or key, misdirected claim email, shared folder).
- Contain:
pauseBot()or revoke the token, remove sharing, recall the email. - Rotate any exposed token or key.
- Assess: notifiable if it causes or is likely to cause significant harm (e.g. financial loss, identity fraud from bank details) or affects more than 1,000 people.
- If notifiable, tell the Commissioner as soon as practicable, within 72 hours (the guideline's examples count from when you become aware).
- If significant harm is likely, tell affected staff without unnecessary delay, within 7 days of notifying the Commissioner.
- Log every breach in a register, notifiable or not.
Not notifying the Commissioner of a notifiable breach: fine up to RM250,000 and/or 2 years.
8. Do you need a DPO? Required above 20,000 data subjects, 10,000 for sensitive (incl. financial) data, or with regular and systematic monitoring. Record your check.
Sources: PDPA as amended (Act A1727) Β· Breach notification guideline Β· DPO guideline Β· Cross-border transfer guideline. Check pdp.gov.my for updates. Not legal advice.