A messenger with one rule
BotComm is a separate, dedicated app for the software you talk to — AI assistants, home automations, alerts from your own scripts. Your messenger stays for people; here, messaging another human is impossible by design.
Try it in one minute — sign in and say hi to @welcome, the built-in tour bot.
Why only bots
Bots don't belong in the app where you talk to your family — and strangers don't belong in the app where you talk to your bots. Every conversation here is a session between one account and one bot; that single constraint removes most of what makes messengers complicated, noisy, and risky.
There is no friend list, no discovery of other people, no way to be messaged by a stranger. The server has no route for it.
A bot is a program that connects to BotComm over one HTTP stream — no inbound ports, no webhooks, no public URL. Run it on a laptop, a Raspberry Pi, or a cluster.
A bot's credentials are returned exactly once at creation and can be rotated any time. Access tokens are short-lived RS256 JWTs you can verify against a public jwks.json.
How it works
Name it and claim a unique @handle. You get its client credentials back — once.
The bot authenticates with its credentials and holds an event stream from /updates. While the stream is up, it shows as online.
Make the bot public and anyone can open a chat by its handle. Each person gets their own private session.
Delivered instantly over the stream when you're in the app, as a push notification when you're not. Read receipts go both ways.
For developers
This is the complete echo bot, in whichever language you already write. Authentication, token refresh, reconnects, and unread back-fill are the SDK's problem, not yours.
Go, Python, JavaScript, and Rust — the same bot, four ways.b := &bot.Bot{
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("CLIENT_SECRET"),
}
b.NewSession = func(s *bot.Session) {
b.Send(ctx, s.ID, "Hello! I repeat everything you say.")
}
if err := b.Run(ctx); err != nil {
log.Fatal(err)
}
for msg := range b.Messages {
b.Send(ctx, msg.SessionID, msg.Content) // echo
}
bot = Bot(client_id, client_secret)
async def greet(session):
await bot.send(session.id, "Hello! I repeat everything you say.")
bot.on_new_session = greet
await bot.run()
while True:
msg = await bot.messages.get()
await bot.send(msg.session_id, msg.content) # echo
const bot = new Bot({ clientId, clientSecret });
bot.on('new_session', (session) =>
bot.send(session.id, 'Hello! I repeat everything you say.'));
bot.on('message', (msg) =>
bot.send(msg.sessionID, msg.content)); // echo
await bot.run();
let config = Config::new(client_id, client_secret);
let (bot, mut events) = Bot::run(config).await?;
while let Some(event) = events.recv().await {
match event {
Event::NewSession(s) => {
bot.send(&s.id, "Hello! I repeat everything you say.", false).await?;
}
Event::Message(msg) => {
bot.send(&msg.session_id, &msg.content, false).await?; // echo
}
_ => {}
}
}
Under the hood
One Server-Sent Events stream carries messages, new sessions, and read receipts as JSON. No polling, no sockets.
Offline users get an APNs notification instead, with the badge kept at the true unread count.
Reconnecting clients back-fill unread messages per session and confirm reads, so state converges after any outage.
An email code or Sign in with Apple. Refresh tokens rotate on every use, and a reused token revokes its whole family.
Bots publish a command menu; the app renders it, and commands arrive at the bot already separated from chat.
Native apps on the App Store and Google Play, plus a web app — all talking to the same open REST API you can script with curl.
Get started