Telegram Bot Framework

Hermes
Guide

A fast, small Telegram Bot API library and framework for modern Go.

Introduction

Welcome to the Cookbook.

Hermes is a fast, small Telegram Bot API library and framework for modern Go. It prioritizes zero-allocation dispatch, broad update decoding, and a fully typed API surface.

This guide will take you step-by-step from creating your first bot to building advanced stateful applications with streaming uploads and webhooks.

1. Getting Started

Creating bot with BotFather

Before you can interact with the Telegram API in Go, you must register your bot with Telegram.

  • Open Telegram and search for the official @BotFather account.
  • Send the /newbot command to begin the creation process.
  • Follow the prompts to choose a display name and a unique username (ending in 'bot') for your application.
  • Once completed, BotFather will give you an HTTP API Token (e.g., 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11).

Keep this token secret. In our examples, we will read it from the BOT_TOKEN environment variable.

2. Basics

Writing your first bot

Start a new Go module and install Hermes:

go mod init mybot
go get github.com/sidwiskers/hermes

Create a main.go file. We will initialize the bot, apply basic recovery and timeout middleware, register a simple /start command, and begin long polling.

package main

import (
	"context"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/sidwiskers/hermes"
)

func main() {
	// Setup graceful shutdown context
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
	defer stop()

	// Initialize the bot with your token
	bot := hermes.New(os.Getenv("BOT_TOKEN"))

	// Apply standard middleware for stability
	bot.Use(
		hermes.Recover(),
		hermes.Timeout(15*time.Second),
	)

	// Register a simple command route
	bot.Command("start", func(c *hermes.Context) error {
		return c.Send("Hello from hermes.")
	})

	// Start long-polling
	if err := bot.Run(ctx); err != nil {
		log.Fatal(err)
	}
}

Run your bot using BOT_TOKEN=your_token go run main.go. When you message your bot with /start on Telegram, it will reply immediately.

3. Advanced Actions

Sending requests

Hermes makes it simple to send complex messages, such as those with inline keyboards, HTML formatting, and ephemeral (private) visibility.

This example demonstrates how to send an ephemeral message with an inline button, and how to route the subsequent callback query when the user taps that button.

// Register the /profile command
bot.Command("profile", func(c *hermes.Context) error {
	return c.Ephemeral(
		"<b>Private profile</b>\nOnly you can see this.",
		hermes.HTML,
		hermes.WithKeyboard(hermes.Keyboard(
			hermes.Row(hermes.Button("Refresh", "profile:refresh")),
		)),
	)
})

// Route button clicks matching the "profile:" prefix
bot.CallbackPrefix("profile:", func(c *hermes.Context) error {
	// Answer the callback to remove the loading state on the user's button
	return c.Answer("Updated")
})
4. Production

Webhooks

While long-polling is great for development, production bots should generally use Webhooks. Hermes includes a built-in webhook server that safely validates Telegram's secret headers and manages graceful shutdowns.

const path = "/telegram"
secret := os.Getenv("WEBHOOK_SECRET")
publicURL := "https://yourdomain.com"

// 1. Tell Telegram where to send updates
if err := bot.SetWebhook(ctx, hermes.SetWebhookParams{
	URL:         publicURL + path,
	SecretToken: secret,
}); err != nil {
	log.Fatal(err)
}

// 2. Start the HTTP server to receive those updates
if err := bot.ServeWebhook(ctx, ":8080", path, hermes.WebhookOptions{
	Secret: secret,
}); err != nil {
	log.Fatal(err)
}
5. State & Middleware

Finite-state conversations

Hermes provides optional packages for typed sessions and finite-state machines, allowing you to easily build multi-step conversational flows.

import (
	"github.com/sidwiskers/hermes/fsm"
	"github.com/sidwiskers/hermes/session"
)

type state uint8
const (
	idle state = iota
	waitingForName
)

type profile struct { Name string }

// Setup the memory store and FSM
store := session.NewMemory[fsm.Snapshot[state, profile]](24 * time.Hour)
sessions := session.New(store, session.ByChatUser, session.WithNamespace("profile"))
flow := fsm.New(sessions, idle)

// Define valid state transitions
flow.Add(fsm.Rule[state, profile]{From: idle, Event: "begin", To: waitingForName})

// Inject the FSM middleware
bot.Use(flow.Middleware())

// Start the flow via a command
bot.Command("profile", flow.Then("begin", func(c *hermes.Context) error {
	return c.Send("What should I call you?")
}))

// Handle input when the user is in the 'waitingForName' state
bot.On(flow.In(waitingForName), func(c *hermes.Context) error {
	name := strings.TrimSpace(c.Text())
	// Save state and transition back to idle
	flow.Set(c, fsm.Snapshot[state, profile]{State: idle, Data: profile{Name: name}})
	return c.Send("Saved, " + name + ".")
})
6. Streaming

Uploading files

New files are streamed directly from an io.Reader. The full file is never buffered in memory, ensuring your bot maintains a low memory footprint even when sending massive videos or documents.

bot.Command("upload", func(c *hermes.Context) error {
	chatID, _ := c.ChatID()
	
	file, err := os.Open("document.pdf")
	if err != nil {
		return err
	}
	defer file.Close()

	// Stream the file directly to Telegram
	_, err = c.Bot.SendDocumentUpload(c, hermes.SendDocumentParams{
		ChatID:  chatID,
		Caption: "Streamed by Hermes",
	}, "document.pdf", file)
	
	return err
})
7. Integration

Inline Queries

Hermes fully supports the inline bot API surface, allowing you to answer queries when a user invokes your bot via @yourbot in any chat.

bot.On(hermes.UpdateIs(hermes.UpdateInlineQuery), func(c *hermes.Context) error {
	query := c.Update.InlineQuery
	return c.Bot.AnswerInlineQuery(c, hermes.AnswerInlineQueryParams{
		InlineQueryID: query.ID,
		CacheTime:     10,
		Results: []hermes.InlineQueryResult{
			hermes.InlineQueryResultArticle{
				ID:    "hello",
				Title: "Say hello",
				InputMessageContent: hermes.InputTextMessageContent{
					MessageText: "Hello from Hermes.",
				},
			},
		},
	})
})
Documentation

Need more detail?Read the docs.

Go Package Reference