Deployment

Running multiple sessions

One process can hold any number of independent gogram clients. Useful for running several bots side by side, splitting workload between userbots, or supervising customer-owned sessions.

One process, many clients

type Bot struct {
	Name    string
	Client  *telegram.Client
}

bots := []Bot{
	{Name: "alerts", Client: mustNewClient("alerts.session", os.Getenv("ALERTS_TOKEN"))},
	{Name: "support", Client: mustNewClient("support.session", os.Getenv("SUPPORT_TOKEN"))},
	{Name: "growth", Client: mustNewClient("growth.session", os.Getenv("GROWTH_TOKEN"))},
}

for _, b := range bots {
	b := b
	b.Client.On("message:*", func(m *telegram.NewMessage) error {
		log.Printf("[%s] %s", b.Name, m.Text())
		return nil
	})
}

select {} // block forever
type Bot struct {
	Name    string
	Client  *telegram.Client
}

bots := []Bot{
	{Name: "alerts", Client: mustNewClient("alerts.session", os.Getenv("ALERTS_TOKEN"))},
	{Name: "support", Client: mustNewClient("support.session", os.Getenv("SUPPORT_TOKEN"))},
	{Name: "growth", Client: mustNewClient("growth.session", os.Getenv("GROWTH_TOKEN"))},
}

for _, b := range bots {
	b := b
	b.Client.On("message:*", func(m *telegram.NewMessage) error {
		log.Printf("[%s] %s", b.Name, m.Text())
		return nil
	})
}

select {} // block forever
constructor helper
func mustNewClient(session, token string) *telegram.Client {
	c, err := telegram.NewClient(telegram.ClientConfig{
		AppID:       apiID,
		AppHash:     apiHash,
		Session:     session,
		SessionName: filepath.Base(session),
	})
	if err != nil { log.Fatal(err) }
	if err := c.Connect(); err != nil { log.Fatal(err) }
	if err := c.LoginBot(token); err != nil { log.Fatal(err) }
	return c
}
func mustNewClient(session, token string) *telegram.Client {
	c, err := telegram.NewClient(telegram.ClientConfig{
		AppID:       apiID,
		AppHash:     apiHash,
		Session:     session,
		SessionName: filepath.Base(session),
	})
	if err != nil { log.Fatal(err) }
	if err := c.Connect(); err != nil { log.Fatal(err) }
	if err := c.LoginBot(token); err != nil { log.Fatal(err) }
	return c
}

Each *Clienthas its own session, its own auth key, its own dispatcher goroutine. They share the global Go runtime — if one of them does heavy work, the others see scheduler pressure but no correctness issue.

Naming sessions

Set SessionName on each client so the log lines carry an identifier. Default log format prefixes every line with the session name, which makes a single tail of stderr readable when ten clients are interleaved.

Separate cache files per session

The auth key and the peer cache are per-account state. A session file only ever belongs to one account, and reusing it across two clients — even sequentially — is the fastest way to get the server to invalidate the auth key with AUTH_KEY_DUPLICATED.

The library defends against the obvious foot-gun: it will not let two clients open the same session file at the same time, and each client keeps its own in-memory peer cache keyed by its own Session field. That much is handled for you.

Even so, it is worth being deliberate. Give every session its own file name and its own on-disk cache path, right from the start of the process:

bots := []Bot{
	{Name: "alerts",  Client: mustNewClient("./data/alerts/session",  "./data/alerts/peers.db",  os.Getenv("ALERTS_TOKEN"))},
	{Name: "support", Client: mustNewClient("./data/support/session", "./data/support/peers.db", os.Getenv("SUPPORT_TOKEN"))},
	{Name: "growth",  Client: mustNewClient("./data/growth/session",  "./data/growth/peers.db",  os.Getenv("GROWTH_TOKEN"))},
}
bots := []Bot{
	{Name: "alerts",  Client: mustNewClient("./data/alerts/session",  "./data/alerts/peers.db",  os.Getenv("ALERTS_TOKEN"))},
	{Name: "support", Client: mustNewClient("./data/support/session", "./data/support/peers.db", os.Getenv("SUPPORT_TOKEN"))},
	{Name: "growth",  Client: mustNewClient("./data/growth/session",  "./data/growth/peers.db",  os.Getenv("GROWTH_TOKEN"))},
}

Two reasons this matters even though the library is safe on its own:

  • Operational clarity. When something goes wrong with one bot, you can nuke only that account's state without touching the others. A single shared cache file makes the blast radius of a bad rollback all-or-nothing.
  • Backups.One tar per session lets you restore an individual bot without affecting the rest. Handy for A/B rollouts, staged migrations, and "something is weird with alerts, roll it back but keep support running."

Routing updates

Updates are scoped to the client they arrived on. There is no cross-client dispatcher. Each client's handlers see only its own updates.

For a fan-in pattern — one logical handler that processes incoming events from many accounts — register the same closure on every client. Capture the client by value inside the closure so each handler can answer back through the right session.

Sharing peer cache

Each client has its own peer cache, which is usually what you want — user A's peer list is not the same as user B's. If you have a use case where shared peer hashes are safe (multiple bots in the same channel), you can plug the same custom CACHEimplementation into both clients. Access hashes are per-session in general — do not assume one bot's cached hash works for another.