Build TeleMinute

KINDAreplaces $9.99/mosaves $119.88/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A single-business Telegram bot whose hard problems are a broadcast that survives rate limits and restarts without resending, conversation state that outlives a deploy, and holding customer details you are then obliged to be able to delete.

estimated effort one sittingthe files for this build are in the project pack

RuntimeNode with a Telegram bot framework, one process, long pollingConfigOne YAML file of commands and keyword replies, validated on load and hot reloadedStoreSQLite beside the process with write-ahead logging onBroadcastA materialised ledger with one row per recipient, drained under a token bucketOwnershipOne owner chat identifier, checked on every privileged command

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the single bot process.

    Get it nodejs.org or a version manager. open ↗

    Verify node --version prints v22 or higher

  2. API keyfree

    Why This is the bot's identity and its tenancy boundary, and anyone holding it can be your bot.

    Get it Message the platform's bot registration account, create a bot and copy the token.

    Verify A getMe call returns your bot's username

  3. have readya few pounds a month, or free on hardware you own

    Why Long polling means the process has to be running for the bot to answer, and a laptop that sleeps is a bot that is offline during business hours.

    Get it A small virtual machine, a single-board computer or a spare box that stays on.

    Verify The bot answers a command with your laptop closed

  4. have readyfree

    Why Every privileged command is gated on this one number, so getting it wrong means either you cannot broadcast or somebody else can.

    Get it Message the bot once and read the chat identifier from the update.

    Verify A privileged command works for you and looks unknown to a second account

  5. have readyfree

    Why The broadcast design is a token bucket set below those figures, and they are the kind of number that changes and that nobody rechecks.

    Get it Read the platform's own bot documentation and note the figures with the date.

    Verify The README carries the figures, the source and the date checked

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- chats(chat_id PRIMARY KEY, kind, first_seen_at, last_seen_at, language_code, blocked_at) · blocked_at is set the moment the API tells you the user blocked the bot
- messages(id, chat_id, at, kind, command, text_hash) · a hash, because you do not need to keep what customers typed
- leads(id, chat_id, name, phone, message, at, exported_at, deleted_at) · the only place a customer's own words are kept, and they knowingly sent them
- conversations(chat_id PRIMARY KEY, flow, step, collected_json, started_at, expires_at) · in the database, because the process will restart mid-conversation
- broadcasts(id, at, body, created_by, state) and broadcast_targets(broadcast_id, chat_id, state, attempts, error_code, sent_at) · unique on the pair, so a restart resumes rather than resends
- config_loads(id, at, file_hash, ok, error_text) · the owner will edit the file and want to know why nothing changed
- There is no table of people the bot has not met. A Telegram bot cannot start a conversation, so your broadcast audience is exactly the set of chats that messaged you first, and that is the honest ceiling on this whole feature.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
BOT_TOKENsecretrequired1234567890:AA...redactedThe bot's identity. Anyone with it is your bot, so treat it as a password.
OWNER_CHAT_IDrequired123456789The only chat that may broadcast, read statistics or delete a person.
DATABASE_PATHrequired./data/bot.dbChats, leads, conversations and the broadcast ledger. Back this up.
CONFIG_PATHrequired./commands.yamlCommands and keyword replies. Hot reloaded, validated, and never trusted blindly.
BROADCAST_RATE_PER_SECONDrequired20Set below the published limit. Check the current figure before changing it.
CONVERSATION_EXPIRY_MINUTESrequired30An abandoned contact flow expires and the next message starts fresh.
MESSAGE_RETENTION_DAYSrequired90Message rows are deleted after this. The daily counts remain.
LEAD_RETENTION_DAYSrequired365Leads carry real personal details, so they get an end date too.

The build, in order

  1. One bot, one process, commands from a file

    The bot answers commands defined in a YAML file and a bad edit to that file never takes it offline.

    1. A lock file or the database, but something.

      Files src/lock.jstests/single-instance.test.js

    2. The owner is editing YAML on a phone at some point.

      Files src/config.jstests/config-reload.test.js

    3. A hard-coded command list drifts within a week.

      Files src/commands.js

    done when · tick each as it passes
    watch out
    • Two copies of the process polling the same token fight over updates and the platform rejects one of them with a conflict, which from the outside looks like a bot that answers every other message and sends people away thinking the business is broken; take an exclusive lock on the database file or a lock file at startup, refuse to start a second instance with a clear message, and check what your process supervisor does on a restart before you trust it not to overlap them.
  2. Conversations that survive a restart

    Someone halfway through leaving their details still finishes after a deploy.

    1. The process is going to restart, probably at the worst moment.

      Files src/flows/contact.jstests/resume.test.js

    2. Nobody should be stuck in a flow they forgot about.

      Files src/flows/expire.js

    3. Dropping out of the flow loses the lead entirely.

      Files src/flows/contact.js

    done when · tick each as it passes
    watch out
    • Holding conversation state in a map in memory means a restart drops three people mid-flow, and the next thing they type, which is their phone number, gets answered as if it were a new message and possibly matched against a keyword reply; store the step and what has been collected in the database with an expiry, resume on the next message, and expire abandoned conversations rather than leaving somebody stuck in a flow forever.
  3. Broadcast inside the published limits

    A broadcast to every chat completes, is resumable and never sends twice.

    1. The ledger is what makes a restart safe.

      Files src/broadcast/create.jssrc/db/schema.sql

    2. Check the current published figures; they have changed before.

      Files src/broadcast/drain.jstests/rate.test.js

    3. Otherwise every future broadcast retries them.

      Files src/broadcast/drain.js

    done when · tick each as it passes
    watch out
    • Looping over every chat and sending as fast as the library will go gets you limited, because the platform's own bot guidance says a bot cannot broadcast more than about thirty messages a second and no more than twenty messages a minute into the same group, and the failures arrive partway through so the first few hundred people get the message and the rest silently do not; materialise the targets first, drain under a token bucket below the published figure, honour the retry interval the API hands back, and let a restart resume from the ledger instead of starting again.
  4. Statistics that are not a transcript

    The owner sees useful counts without the bot keeping a record of what customers typed.

    1. You do not want a transcript of a cafe's customers.

      Files src/messages.jstests/no-text-stored.test.js

    2. Do not confirm to a stranger that a command exists.

      Files src/auth.js

    3. Adding it later means deleting data you already promised to keep.

      Files src/retention.js

    done when · tick each as it passes
    watch out
    • Keeping every incoming message so you can add analytics later means the shop's bot is now holding a transcript of what its customers typed, which is a disclosure problem you did not need and a deletion obligation you did not plan for; store the command name and a hash for ordinary traffic, keep real text only where the person was explicitly giving it to you, and set a retention period on both from the first release rather than after the first request.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more no-code apps.