A message automation program that essentially process incoming message and forward to designate channel.
Currently 2 services provided:
- Mail forwarding. It receives email, summarize using agent and forward to telegram chat.
- Chat. A Telegram group chatbot backed by agent with recent multi-user context.
Services have different requirements. For example, using Chatbot does not require setting up mail routing.
This project is an experiment towards an automation that does the things I need in a way that I understand.
At a high level, the program is event driven and consist of modules and tasks.
A module is a stateful service unit that exposes API to do things. For example, telegram bot module has function to receive update, send messages. I think of them as limbs, claws, jet engine, or plasma cannon installed on msgbot. Not all modules require explicit state and one can simply provide utility functions.
A task describe a workflow. For example, when receiving a mail, send it to agent for summary. Then forward summary to telegram chat. It achieves this by producing a callback handler which will be registered into specified module entry point.
Note: not a plugin system like extensions where a standarized interface is exposed by modules.
The program runs as a service and reads configuration from a TOML file:
- executable
~/.local/bin/msgbot - config file
~/.config/msgbot/config.toml
The checked-in config.toml shows the expected shape.
[modules.mail]configures the webhook listener, shared secret, and.emlsave directory[modules.telegram]configures the bot token and the default destination chat used byforwardmail[modules.agent]choosespassthroughorclaude[tasks.forwardmail]enables the mail-to-Telegram workflow[tasks.chat]enables the group-chat reply workflow, chat-specific agent overrides, and chat context/reply limits
Note:
modules.agent.type = "passthrough" basically means echo.
To enable real LLM agent, switch it to "claude" and fill in [modules.agent.claude].
Start the service with an explicit config path:
go run . -config ./config.tomlBuild and install into your user account:
go build -o msgbot .
mkdir -p ~/.local/bin ~/.config/msgbot ~/.config/systemd/user ~/msgbot/mail
install -m 0755 msgbot ~/.local/bin/msgbot
cp config.toml ~/.config/msgbot/config.toml
cp deploy/msgbot.service ~/.config/systemd/user/msgbot.serviceIf you want the service to start at boot even when you are logged out, enable lingering for your user:
sudo loginctl enable-linger "$USER"Then start the user service:
systemctl --user daemon-reload
systemctl --user enable --now msgbotUseful commands:
systemctl --user status msgbot
journalctl --user -u msgbot -f
systemctl --user restart msgbotReceives email, extract email body, summarize using agent, forwards summary to chat, and saves received messages to disk.
For the mail receiving end, it should terminate HTTPS in TLS proxy (Nginx, Caddy, etc) and keep msgbot bound to localhost.
Methodology:
- the program: this app
- receive mail: email routed (by cloudflare worker)
- extract email: plain text or summary generated by LLM
- forward to chat: send (by telegram bot)
- save to disk:
.eml
Requirement:
- Valid domain managed by cloudflare
- Public server (VPS)
- Time or fixation for configuring Cloudflare
The example worker.js can be used as the Cloudflare Email Routing ingress that POSTs raw RFC822 bytes to the bot.
In the worker configuration:
WEBHOOK_SECRETmust matchmodules.mail.webhook_secretinconfig.tomlSERVER_ENDPOINTshould be set tohttp://{DOMAIN}/email/notify
The webhook listens on http://127.0.0.1:8181/email/notify by default and expects header X-Webhook-Secret.
Setting up reverse proxy behind cloudflare requires additional setup.
- Create the Origin CA cert in Cloudflare
- Copy them into server, e.g.
/etc/ssl/cloudflare/origin.crt,/etc/ssl/cloudflare/private.key - Ensure read permission for proxy
- Configure proxy to use the certificates
Example using Caddy to proxy the https traffic to msgbot:
example.com {
tls /etc/ssl/cloudflare/origin.crt /etc/ssl/cloudflare/private.key
reverse_proxy /email/notify localhost:8181
}When [tasks.chat] is enabled, the bot keeps an in-memory recent-message window for each Telegram chat. Every non-empty user text message from a group is recorded.
When someone mentions the bot, it sends the agent an IRC-style transcript of recent messages in chronological order. Human messages are prefixed with their display name, and the bot's own previous chat-task replies are prefixed with [bot]. This history is shared across topics in the same supergroup chat, but the reply is still posted back into the triggering thread.
The chat context is runtime-only and resets on restart. forwardmail messages are not added to chat history.
Available [tasks.chat] settings:
enabled: turn the chat task on or offcontext_max_messages: maximum number of recent chat messages to include, default24context_window: age limit for chat context, default"12h"max_reply_messages: maximum number of Telegram messages used for one bot reply, default2agent_max_tokens: token budget passed to the agent for one reply, default2048agent_model: optional Anthropic model override used only for chat replies; when empty, chat usesmodules.agent.anthropic.modelweb_search_enabled: enable Claude web search for chat repliesweb_search_max_uses: max Claude web-search tool calls for one reply, default5
When web_search_enabled=true:
- Anthropic web search must be enabled in Claude Console for the workspace before deployment
- msgbot only passes the Claude web-search tool to the API and lets Anthropic decide whether the current model can use it
Chat replies with Claude web search append a plain-text Sources: footer using cited URLs returned by Anthropic.
