https://github.com/tomusdrw/telemach-bot
A personal Telegram bot that forwards messages, attachments, and transcribed voice notes to your email. Subject lines generated by an LLM via OpenRouter, delivery via Resend. Multi-user with admin approval. Single container, one mounted data volume.
https://github.com/tomusdrw/telemach-bot
Last synced: 4 days ago
JSON representation
A personal Telegram bot that forwards messages, attachments, and transcribed voice notes to your email. Subject lines generated by an LLM via OpenRouter, delivery via Resend. Multi-user with admin approval. Single container, one mounted data volume.
- Host: GitHub
- URL: https://github.com/tomusdrw/telemach-bot
- Owner: tomusdrw
- Created: 2026-05-18T09:55:31.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-07-08T21:40:40.000Z (12 days ago)
- Last Synced: 2026-07-08T23:18:12.154Z (12 days ago)
- Language: TypeScript
- Homepage:
- Size: 243 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# telemach-bot
[](https://github.com/tomusdrw/telemach-bot/actions/workflows/ci.yml)
A personal Telegram bot that forwards messages, attachments, and transcribed
voice notes to your email. Subject lines generated by an LLM via OpenRouter,
delivery via Resend. Multi-user with admin approval. Single container, one
mounted data volume.
See `docs/superpowers/specs/2026-05-18-telegram-email-bot-design.md` for the
full design.
## Quick start (Docker)
1. Copy `.env.example` to `.env` and fill in:
- `TELEGRAM_BOT_TOKEN` from @BotFather
- `ADMIN_TELEGRAM_USER_ID` (your numeric Telegram user id, ask @userinfobot)
- `ADMIN_EMAIL`
- `OPENROUTER_API_KEY` (used for both transcription and subject generation)
- Optionally override `OPENROUTER_MODEL` (subjects) and `OPENROUTER_TRANSCRIPTION_MODEL` (voice → text)
- Optionally set `EVENT_MODEL` (event extraction) and `EXPANSION_MODEL` (body cleanup/expansion); both default to `OPENROUTER_MODEL`
- `RESEND_API_KEY` and `RESEND_FROM_EMAIL` (must be a verified Resend domain)
2. Create the data directory with the right ownership. The container runs as the
`node` user (UID 1000); the bind-mount preserves host UID, so:
```bash
mkdir -p data
sudo chown 1000:1000 data
```
Skip the `chown` if your host user is already UID 1000 (typical for a fresh
non-root user on most Linux distros).
3. `docker compose up -d --build` (or pull the prebuilt image — see below).
4. DM your bot `/start`.
### Using the prebuilt image from GHCR
Every push to `main` builds and publishes an image to
`ghcr.io/tomusdrw/telemach-bot`. To pull it instead of building locally, edit
`docker-compose.yml`:
```yaml
services:
telemach-bot:
image: ghcr.io/tomusdrw/telemach-bot:latest # remove `build: .`
# ... rest unchanged
```
Then `docker compose pull && docker compose up -d`.
## Development
```bash
npm install
npm test
npm run dev
```
## How it works
- `/start` → bot greets, asks for `/register your@email.com`.
- `/register` → bot stores email, DMs admin with Approve/Reject buttons.
- After approval, forwarded messages are acknowledged with reactions only:
👀 received → ✍ working → 👍 sent (or 💩 on error).
- A short, single-line message (≤ 80 chars) becomes the email subject verbatim;
longer messages get an LLM-generated subject. For every text message the LLM
also appends a cleaned-up/expanded rendition (typos fixed, terse notes spelled
out) below the original in the body; links are left untouched.
- Voice messages are transcribed via OpenRouter's `/audio/transcriptions`
endpoint (default model: `openai/whisper-large-v3`, override with
`OPENROUTER_TRANSCRIPTION_MODEL`). The transcript is sent as the email
body; original audio is not attached.
- Photos/documents/videos/audio/animations/stickers are attached verbatim.
- Multiple photos uploaded together bundle into one email.
- When a message contains a date, the bot extracts the event and attaches an
`.ics` calendar file to the email so you can add it to your calendar in one
click. A 📅 reaction confirms the attachment was created.
- `/timezone Europe/London` sets your per-user IANA timezone (default:
`Europe/Warsaw`). Run `/timezone` with no argument to see the current value.
Affects how calendar invite times are interpreted and displayed.
## Admin commands
These work only when invoked by the configured `ADMIN_TELEGRAM_USER_ID`:
- `/users` — list up to 50 most-recent users with status and email.
- `/revoke ` — flip an `APPROVED` user to `REJECTED`. Refuses to act on the admin.
- `/reset ` — clear the user's email and return them to `PENDING_EMAIL` so they can re-register.
## Troubleshooting
- **💩 reaction on every message.** Check the container logs (`docker compose
logs -f telemach-bot`). The error class (`TransientError`/`FatalError`) and
provider (`whisper` / `openrouter` / `resend` / `telegram` / `db`) are in
every error line. The most common first-deploy cause is an unverified
`RESEND_FROM_EMAIL` domain — verify the domain at
before sending the first message.
- **Container exits immediately with "Invalid environment".** A required env
var is missing. The fatal log line lists every missing or invalid var name.
- **`EACCES: permission denied, open '/data/bot.db'`.** The bind-mounted host
directory is owned by the wrong UID. Run `sudo chown 1000:1000 data` on the
host.
- **Bot never responds to `/start`.** Double-check `TELEGRAM_BOT_TOKEN`. The
container logs a `{"msg":"bot online"}` line on successful login.
- **Approved user can't actually send anything.** Confirm the admin tapped
Approve (look for `Approved @username ✓` in your own admin DM). If the
status got stuck in `PENDING_APPROVAL`, fix it directly:
```bash
docker compose exec telemach-bot \
sqlite3 /data/bot.db "UPDATE users SET status='APPROVED' WHERE telegram_id=;"
```
- **Disable / un-approve a user (no command for this yet).**
```bash
docker compose exec telemach-bot \
sqlite3 /data/bot.db "UPDATE users SET status='REJECTED' WHERE telegram_id=;"
```
- **See recent activity for a user.**
```bash
docker compose exec telemach-bot \
sqlite3 /data/bot.db \
"SELECT created_at, event, details FROM audit_log
WHERE telegram_id= ORDER BY id DESC LIMIT 20;"
```
## Updating
```bash
git pull
docker compose up -d --build
```
The SQLite file in `./data/bot.db` survives rebuilds.