An LLM-powered, multi-platform chat assistant written in Go β one bot that lives in Telegram, Discord, Slack and Email, understands natural language, and gets work done: GitLab reports, email delivery, cross-platform message sync, and more.
Address the bot on any platform β @mention it, reply to it, or call it by name β and it decides what to do via LLM tool calling (Groq's free OpenAI-compatible API). No command syntax to memorize.
you> @smartty gitlab report for webapp
smartty> π webapp β 12 open issues (3 in progress, 75% done) β¦
you> and the oldest ones?
smartty> π° The 5 oldest open issues are β¦ β follow-ups work: per-chat memory
- Assistant-first UX β natural language on every platform, parsed into typed tool calls (
gitlab_report,send_email,cross_post,toggle_sync). Un-addressed messages never touch the LLM, keeping API usage near zero. - Conversation memory β bounded per-chat context (mutex-safe, oldest-evicted) so follow-up questions like "and the in-progress ones?" just work.
- Interactive reports β GitLab reports in Telegram render with an inline-keyboard pager; sections swap in place via callbacks, with a bounded tracker so memory stays flat.
- Two-way channel sync β opt-in mirroring between same-named Telegram/Discord/Slack channels through a platform-agnostic
Messengerinterface. - Push everywhere, poll nowhere β Slack over Socket Mode (no public endpoint), email over IMAP IDLE (with automatic polling fallback), Discord and Telegram over their streaming APIs.
- Every channel is optional β each platform enables itself only when its tokens are configured; any subset (or none) runs fine, and one misconfigured channel never takes down the rest.
- Zero-dependency persistence β embedded SQLite behind generic, mutex-safe stores; no external database to operate.
- Production hygiene β graceful signal-based shutdown, structured logging (
log/slog),/healthz+/metricsendpoints, Docker Compose deployment.
flowchart LR
subgraph channels["Chat channels β each optional"]
TG[Telegram]
DC[Discord]
SL[Slack<br/>Socket Mode]
EM[Email<br/>SMTP + IMAP IDLE]
end
HUB[Hub<br/>cross-post & two-way sync]
AST[Assistant<br/>Groq tool calling + memory]
GL[GitLab client<br/>+ report renderer]
DB[(SQLite<br/>persisted stores)]
HS[health server<br/>/healthz Β· /metrics]
TG <--> HUB
DC <--> HUB
SL <--> HUB
EM <--> HUB
TG -- "addressed msgs" --> AST
DC -- "addressed msgs" --> AST
SL -- "addressed msgs" --> AST
AST --> GL
AST -- send --> EM
AST --> HUB
HUB --- DB
AST --- DB
| Package | Responsibility |
|---|---|
cmd/smartty_bot |
Entrypoint: config, dependency wiring, signal-based graceful shutdown |
internal/config |
Env loading, SQLite bootstrap, persisted stores |
internal/store |
Generic, mutex-safe, SQLite-backed key/value maps |
internal/assistant |
The LLM brain: Groq tool calling, per-chat memory, addressed-message detection |
internal/hub |
Cross-platform router: cross-posting + opt-in channel sync over a Messenger interface |
internal/telegram |
Telegram client, commands, callback handling, interactive report pagination |
internal/discord |
Discord client, prefix + slash commands |
internal/slack |
Slack client via Socket Mode |
internal/email |
SMTP sending + IMAP IDLE inbox listener |
internal/gitlab |
GitLab REST API client |
internal/gitlabreport |
Renders GitLab data into chat replies and paged report sections |
internal/health |
/healthz + /metrics HTTP server |
Design notes for the curious:
- New platforms plug in by implementing the hub's small
Messengerinterface β each platform is a self-contained package with no cross-imports between siblings. - New assistant abilities are two steps: declare the tool schema in
internal/assistant/tools.go, dispatch it ininternal/assistant/assistant.go. - Concurrency is treated as a first-class concern: shared state lives behind mutex-guarded stores and every CI run executes the full test suite under the race detector.
- Tested with fakes at the seams (
FakeMessenger,FakeReporter, fake routers) β no network calls in tests.
Requires Go 1.25+.
git clone https://github.com/omarperezr/SmarttyBot
cd SmarttyBot
cp .exampleenv .env # fill in tokens for the channels you want
go build ./cmd/smartty_bot
./smartty_botState lives in data/smartty.db (SQLite), created automatically on first run.
Or with Docker:
docker compose up --build.env supplies configuration; a named volume persists data/.
Every channel is optional. The bot enables each platform solely based on whether its tokens are set, starts fine with any subset, and a misconfigured channel logs its own error without affecting the others. See .exampleenv for the full variable list (including BOT_NAME, PORT, LOG_LEVEL, DB_PATH).
Groq β natural-language assistant
Create a free API key at https://console.groq.com/keys, then:
GROQ_API_KEY="gsk_..."
GROQ_MODEL="llama-3.3-70b-versatile" # optional; this is the defaultGitLab β reports
Create a personal access token with read_api scope at https://gitlab.com/-/user_settings/personal_access_tokens, then:
GITLAB_TOKEN="glpat-..."
GITLAB_URL="https://gitlab.com/"Telegram
Message @BotFather, send /newbot, choose a name and a username ending in bot, then:
TELEGRAM_API_KEY="123456:ABC-DEF..."The bot discovers its own username automatically.
Discord
Create an application at the Discord Developer Portal, open Bot β Reset Token, then:
DISCORD_API_KEY="..."EMAIL_ACCOUNT="account@gmail.com"
EMAIL_PASSWORD="app-password"
SMTP_SERVER="smtp.gmail.com"
SMTP_PORT=587
IMAP_SERVER="imap.gmail.com"
IMAP_PORT=993Incoming mail is delivered via IMAP IDLE (instant push); servers without IDLE fall back to polling automatically.
Slack
Slack uses Socket Mode β no public endpoint required. In your Slack app config: enable Socket Mode, add an app-level token with connections:write, subscribe to app_mention (and optionally message.channels) events, and install the bot. Then:
SLACK_APP_TOKEN="xapp-..." # app-level token
SLACK_BOT_TOKEN="xoxb-..." # bot tokenSlack stays disabled unless both tokens are set.
On any platform, address the bot to trigger the LLM:
- @mention it, reply to one of its messages, or start your message with its name (
BOT_NAME, defaultsmartty) - e.g.
@smartty gitlab report for webapp, orsmartty email alice@x.com subject "hi" body "..."
Plain, un-addressed messages are never sent to the LLM. In a synced channel they are mirrored to the paired channels instead.
Legacy prefix commands still work alongside natural language:
| Platform | Commands |
|---|---|
| Telegram | -m/-mention <text> Β· -p/-parse <text> Β· -r/-register @TelegramUser discordUser Β· -sync on|off |
| Discord | -a/-answer (/answer) Β· -r (/register) Β· -rm (/register-email) Β· -m/-send-email (/send-email) Β· -sync (/sync) |
Discord also registers native slash commands (/answer, /register, /send-email, /sync, β¦).
go build ./... # compile everything
go vet ./... # static analysis
go test -race ./... # full suite under the race detector
golangci-lint run # lint (errcheck, staticcheck, ineffassign, misspell, β¦)CI runs formatting checks, go vet, race-enabled tests, golangci-lint and a Docker build on every push and pull request.
Running an existing deployment? Updating is a pull-and-rebuild:
git pull
go build ./cmd/smartty_bot && ./smartty_bot # binary deployment
# or
docker compose up --build -d # Docker deploymentThe SQLite schema is created/migrated automatically on startup and data/ is preserved across updates (named volume under Docker). To bump dependencies during development:
go get -u ./... && go mod tidy
go test -race ./...- More platforms (WhatsApp, Matrix) β the hub's
Messengerinterface makes each new platform a self-contained package - Real observability β Prometheus client metrics (messages handled, LLM latency, tool-call counts per platform) plus a ready-to-import Grafana dashboard
- Long-term memory (RAG) β persist conversation history in SQLite so the assistant can recall past context ("what did we decide last week?")
- Scheduled digests β natural-language scheduling ("every morning at 9, post the gitlab report to #standup") via a cron tool the assistant can invoke
- GitHub support alongside GitLab β one
Reporterinterface, two backends - Release automation β GoReleaser, signed binaries and a published container image on every tag
If this project is useful to you, consider supporting it:
