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.
-
Copy
.env.exampleto.envand fill in:TELEGRAM_BOT_TOKENfrom @BotFatherADMIN_TELEGRAM_USER_ID(your numeric Telegram user id, ask @userinfobot)ADMIN_EMAILOPENROUTER_API_KEY(used for both transcription and subject generation)- Optionally override
OPENROUTER_MODEL(subjects) andOPENROUTER_TRANSCRIPTION_MODEL(voice → text) - Optionally set
EVENT_MODEL(event extraction) andEXPANSION_MODEL(body cleanup/expansion); both default toOPENROUTER_MODEL RESEND_API_KEYandRESEND_FROM_EMAIL(must be a verified Resend domain)
-
Create the data directory with the right ownership. The container runs as the
nodeuser (UID 1000); the bind-mount preserves host UID, so:mkdir -p data sudo chown 1000:1000 data
Skip the
chownif your host user is already UID 1000 (typical for a fresh non-root user on most Linux distros). -
docker compose up -d --build(or pull the prebuilt image — see below). -
DM your bot
/start.
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:
services:
telemach-bot:
image: ghcr.io/tomusdrw/telemach-bot:latest # remove `build: .`
# ... rest unchangedThen docker compose pull && docker compose up -d.
npm install
npm test
npm run dev/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/transcriptionsendpoint (default model:openai/whisper-large-v3, override withOPENROUTER_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
.icscalendar file to the email so you can add it to your calendar in one click. A 📅 reaction confirms the attachment was created. /timezone Europe/Londonsets your per-user IANA timezone (default:Europe/Warsaw). Run/timezonewith no argument to see the current value. Affects how calendar invite times are interpreted and displayed.
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 <telegramId>— flip anAPPROVEDuser toREJECTED. Refuses to act on the admin./reset <telegramId>— clear the user's email and return them toPENDING_EMAILso they can re-register.
-
💩 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 unverifiedRESEND_FROM_EMAILdomain — verify the domain at https://resend.com/domains 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. Runsudo chown 1000:1000 dataon the host. -
Bot never responds to
/start. Double-checkTELEGRAM_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 inPENDING_APPROVAL, fix it directly:docker compose exec telemach-bot \ sqlite3 /data/bot.db "UPDATE users SET status='APPROVED' WHERE telegram_id=<id>;"
-
Disable / un-approve a user (no command for this yet).
docker compose exec telemach-bot \ sqlite3 /data/bot.db "UPDATE users SET status='REJECTED' WHERE telegram_id=<id>;"
-
See recent activity for a user.
docker compose exec telemach-bot \ sqlite3 /data/bot.db \ "SELECT created_at, event, details FROM audit_log WHERE telegram_id=<id> ORDER BY id DESC LIMIT 20;"
git pull
docker compose up -d --buildThe SQLite file in ./data/bot.db survives rebuilds.