tgnotifier

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2025 License: MIT Imports: 9 Imported by: 0

README

Simple telegram notification service

A lightweight http client for tg, exposing only two methods: sendMessage and getMe.

Allows you to send telegram text messages to a small list of predefined users through a CLI, a REST API or as golang library.

It can be used to send various notifications to your personal account, or a group chat about pipeline errors/completions, some long running process finished, etc.

It's intended to be simple to deploy and use, without much configuration. At the same time it requires some actions from users prior to receiving notifications, so it can't be used to spam random people.

Initial Telegram Bot setup

To use the service you will need to connect a telegram bot via a bot token, which will send the notifications. You can create a bot through telegram's interface called BotFather, as described in their tutorial.

Besides that, you need to know telegram id's of users to whom you want to send the notifications. You can do that via the userinfobot or alternatively by by sending a message to your bot DMs and accessing the following url: https://api.telegram.org/bot<YOUR BOT TOKEN HERE>/getUpdates

You user id will be in result[0].message.from.id field of response (not to be confused with chat id: result[0].message.chat.id)

Before you can receive notifications from the bot, you must initiate the communication with it. Go to it's page (through the username, provided by the BotFather) and click on the "start" button. This applies to every user in your recipients list that want to get the notifications.

Installation as a standalone app

The easiest way to install is to grab a binary for you platform from the release section.

Alternatively if you have Golang available:

go install 'github.com/religiosa1/tgnotifier/cmd/tgnotifier@latest'

Please, refer to the App Config section of readme, to see what configuration options are available to you. At the very least, you must configure BOT_TOKEN and BOT_API_KEY, and BOT_RECIPIENTS.

It is strongly encouraged to do that in a config file only available for you to read, so you don't expose your tokens and keys through the environment variables for the whole system.

Usage

As a CLI util

You can use the app to send notification through CLI from your shell, scripts, cron, etc.

tgnotifier send "Your message goes here"
# or from stdin
cat message.md | tgnotifier send
# from stdin as HTML
cat message.html | tgnotifier -p HTML
# To a different recipient
tgnotifier send -r 123456789 "Your message goes here"
# To inform you some long-running task is done:
long-running-foo && tgnotifier send "foo is done!" || tgnotifier send "failed!"
# any shell magic you want:
long-running-foo; status=$?; tgnotifier "foo is done with exit status $status"
As a go library
go get 'github.com/religiosa1/tgnotifier@latest'
import "github.com/religiosa1/tgnotifier"

func main() {
  bot, err := tgnotifier.New("YOUR_BOT_TOKEN_FROM_BOTFATHER")
  if err != nil {
    log.Fatal(err)
  }
  recipientsList := []string {"recipientTgId"}
  err := bot.SendMessage("Hello world!", recipientsList)
  if err != nil {
    log.Fatal(err)
  }
}
As a HTTP service

After installing and configuring the app, to run the server:

tgnotifier

On start, application will try to check if the supplied bot token is correct, by querying the telegram API. If this process fails, the app immediately exits with the exit status 2.

If the query is successful, then it launches a HTTP server on the port, specified in the config (6000 by default), listening for the incoming REST requests.

If HTTP server failed to launch, application exits with the status 1.

There are only two endpoints:

To send notification:
curl -X POST \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{"message":"Your message"}' \
  http://localhost:6000/

message is a required field. Its contents can't be longer than 4096 characters.

You can pass optional parse_mode value, to modify, how the passed message is parsed:

{
	"message": "Your message",
	"parse_mode": "MarkdownV2", // OPTIONAL, defaults to MarkdownV2
	"recipients": ["userid1"] // OPTIONAL, defaults to recipients from config
}

Supported parse_mode values are:

  • MarkdownV2 (default)
  • HTML
  • Markdown

Please note, that the message should conform to the telegram formatting specs, as described in docs for example all of the following symbols must be escaped with a '\' character: _*[]()~`>#+-=|{}.!

recipients field in the request payload allows to override the default recipient list provided in the config. If default recipients list is not provided in the config, then recipients field in the payload is required, and attempts to provide a payload without it or with an empty array will result in 400 error.

Empty recipient array in the payload will always lead to 400 error.

Healthcheck request

If you want to check if the service is running ok, you can perform a GET request:

curl -X GET -H "x-api-key: YOUR_API_KEY" http://localhost:6000/

It will perform a query to telegram bot API, verifying that the bot token is still valid, there's no network outages etc.

Building app from source

To build the application you need Go version 1.22 or higher.

Using provided taskfile:

task build

Or manually:

go build github.com/religiosa1/tgnotifier/cmd/tgnotifier

Refer to the go docs on cross compilation and stuff.

App config

Configuration of the app can be provided as a yaml config file, or through environment variables.

Please, check the config file included in this repo, to see the available configuration values.

Supported environmental variables are:

  • BOT_TOKEN bot token as provided by BotFather
  • BOT_RECIPIENTS list of default recipients' telegram Ids, separated by comma
  • BOT_API_KEY your API Key (see bellow)
  • BOT_LOG_LEVEL verbosity level of logs, possible values are 'debug', 'info', 'warn', and 'error'
  • BOT_LOG_TYPE controls the logger output, possible values are "text" and "json"
  • BOT_ADDR address on which we're launching the http server, defaults to "localhost:6000"`
  • BOT_CONFIG_PATH path to configuration file

Upon launch, the service tries to load configuration in the following priority order:

  1. Config file explicitly specified via the --config flag:

    tgnotifier --config /path/to/config.yml
    
  2. Config file path specified in the BOT_CONFIG_PATH environment variable

  3. User-specific config file (platform-dependent):

    • Unix/Linux/macOS: $XDG_CONFIG_HOME/tgnotifier/config.yml (or ~/.config/tgnotifier/config.yml if XDG_CONFIG_HOME is not set)
    • Windows: %APPDATA%\tgnotifier\config.yml
  4. Global config file (platform-dependent):

    • Unix/Linux/macOS: /etc/tgnotifier.yml
    • Windows: %PROGRAMDATA%\tgnotifier\config.yml
  5. If no config file is found, the service will attempt to load all configuration from environment variables.

This allows you to have a system-wide configuration in the global location, while individual users can override settings with their own user-specific config files.

Overriding default config paths with ldflags during the build

You can use ldflags to override the default config file locations at build time.

Available variables:

in github.com/religiosa1/tgnotifier/internal/config:

  • UserConfigPath - User-specific config path
  • GlobalConfigPath - Global/system-wide config path

For Unix/Linux/macOS:

Using taskfile's variables:

# Notice the double slash before the variable to escape variable expansion inside of taskfile
task build USER_CONFIG="\\${XDG_CONFIG_HOME}/tgnotifier/config.yml" GLOBAL_CONFIG="/etc/tgnotifier.yml"

Or manually:

go build \
  -ldflags="-X 'github.com/religiosa1/tgnotifier/internal/config.UserConfigPath=\${XDG_CONFIG_HOME}/tgnotifier/config.yml' \
            -X 'github.com/religiosa1/tgnotifier/internal/config.GlobalConfigPath=/etc/tgnotifier.yml'" \
  github.com/religiosa1/tgnotifier/cmd/tgnotifier

For Windows:

go build \
  -ldflags="-X 'github.com/religiosa1/tgnotifier/internal/config.UserConfigPath=\${APPDATA}\\tgnotifier\\config.yml' \
            -X 'github.com/religiosa1/tgnotifier/internal/config.GlobalConfigPath=\${PROGRAMDATA}\\tgnotifier\\config.yml'" \
  github.com/religiosa1/tgnotifier/cmd/tgnotifier

Environment variable expansion:

The config paths support environment variable expansion at runtime using the ${VAR} or $VAR syntax. Any environment variable can be used as a placeholder:

  • Unix/Linux/macOS examples: ${XDG_CONFIG_HOME}, ${HOME}, ${USER}, etc.
  • Windows examples: ${APPDATA}, ${PROGRAMDATA}, ${USERPROFILE}, etc.
API KEY

You can use API key mechanism, to authorize the incoming request. It can be any string of characters, but better if it's cryptographically secure. You can generate an API key with the corresponding command:

tgnotifier generate-key # it will output a new key to your terminal
# Copy and paste it into the config/put it in env under the BOT_API_KEY

This key should be supplied with each http request to the bot via the header x-api-key or with the cookie X-API-KEY.

IMPORTANT! Make sure, you don't expose your API key (i.e. don't send those requests directly from a web page), as anyone who has network access to the service and has the key can send those notification requests.

If you don't specify an API-key in the config, env, or CLI authorization will be disabled and service will serve any request.

Please notice, there's no internal rate limiting inside of the service, you need to handle that, if you want to be safe.

Example configuration as a service on Linux

Below is an example of setting up the service on Ubuntu Server 22, assuming you placed the app binary in /usr/local/bin/tgnotifier.

Add a user to run the service:

sudo useradd --system --shell /usr/sbin/nologin tgnotifier

Add your configuration in /etc/tgnotifier.yml

Make this configuration readable only to the tgnotifier group:

chown root:tgnotifier /etc/tgnotifier.yml
chmod 640 /etc/tgnotifier.yml

Verify that tgnotifier can successfully send notifications:

# using which here to avoid potential path issues
sudo -u tgnotifier $(which tgnotifier) send -c /etc/tgnotifier.yml "test"

Create a systemd init file /etc/systemd/system/tgnotifier.service:

[Unit]
Description=Telegram Notification Service
After=network-online.target
Wants=network-online.target

[Service]
User=tgnotifier
ExecStart=/usr/local/bin/tgnotifier -c /etc/tgnotifier.yml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Enable the service:

sudo systemctl daemon-reload
sudo systemctl enable --now tgnotifier.service

Check status and logs:

systemctl status tgnotifier
journalctl -u tgnotifier -f

Get the healthcheck to verify it all worked:

curl -X GET -H "x-api-key: YOUR_API_KEY" http://localhost:6000/

By default app launches on localhost only, it will require a reverse proxy to be accessible from the outside world, such as caddy or nginx.

License

tgnotifier is MIT licensed.

Documentation

Index

Constants

View Source
const DefaultTimeout time.Duration = 30 * time.Second

DefaultTimeout is the timeout duration for the default Bot http client (the one created with New, not NewWithClient)

View Source
const MaxMsgLen int = 9500

MaxMsgLen is the maximum allowed message body length in bytes.

Note: Telegram limits messages to 4096 characters, but this value accounts for formatting and encoded entities. You may still receive an "entities too long" error if the message exceeds 4096 characters even though it's under MaxMsgLen.

See: https://stackoverflow.com/questions/68768069/telegram-error-badrequest-entities-too-long-error-when-trying-to-send-long-ma

Variables

View Source
var (
	ErrTokenEmpty       = errors.New("empty TG bot token")
	ErrRecipientsEmpty  = errors.New("empty recipients list")
	ErrMessageEmpty     = errors.New("tg message is empty")
	ErrMessageTooLong   = errors.New("tg message length exceeds maximum")
	ErrParseModeInvalid = errors.New("invalid parseMode value")
	ErrNotABot          = errors.New("we're not a bot according to getMe")
)

Functions

func IsValidParseMode added in v1.1.0

func IsValidParseMode(parseMode string) bool

IsValidParseMode reports whether the given parse mode is valid.

Types

type Bot

type Bot struct {
	// contains filtered or unexported fields
}

Bot is a Telegram notification bot.

func New

func New(token string) (*Bot, error)

New wraps NewWithClient using the default http.Client with Timeout: DefaultTimeout

func NewWithClient added in v1.1.0

func NewWithClient(token string, client *http.Client) (*Bot, error)

NewWithClient creates a new instance of Bot with the provided BOT API token and http client instance

func (*Bot) GetMe

func (bot *Bot) GetMe() (GetMeResponse, error)

GetMe wraps [GetMeWithContext] using context.Background.

func (*Bot) GetMeWithContext added in v1.1.0

func (bot *Bot) GetMeWithContext(ctx context.Context) (GetMeResponse, error)

GetMeWithContext calls `getMe` telegram endpoint for testing token and returns its response

See: https://core.telegram.org/bots/api#getme

func (*Bot) SendMessage

func (bot *Bot) SendMessage(message string, parseMode ParseMode, recipients []string) error

SendMessage wraps [SendMessageWithContext] using context.Background.

func (*Bot) SendMessageWithContext added in v1.1.0

func (bot *Bot) SendMessageWithContext(
	ctx context.Context,
	message string,
	parseMode ParseMode,
	recipients []string,
) error

SendMessage sends TG message in a given parseMode to one or more recipients

See: https://core.telegram.org/bots/api#sendmessage

type BotInterface added in v1.1.0

type BotInterface interface {
	SendMessage(message string, parseMode ParseMode, recipients []string) error
	SendMessageWithContext(ctx context.Context, message string, parseMode ParseMode, recipients []string) error
	GetMe() (GetMeResponse, error)
	GetMeWithContext(ctx context.Context) (GetMeResponse, error)
}

type GetMeResponse

type GetMeResponse struct {
	Id        int64  `json:"id"`
	IsBot     bool   `json:"is_bot"`
	FirstName string `json:"first_name"`

	LastName                string `json:"last_name,omitempty"`
	Username                string `json:"username,omitempty"`
	LanguageCode            string `json:"language_code,omitempty"`
	IsPremium               bool   `json:"is_premium,omitempty"`
	AddedToAttachmentMenu   bool   `json:"added_to_attachment_menu,omitempty"`
	CanJoinGroups           bool   `json:"can_join_groups,omitempty"`
	CanReadAllGroupMessages bool   `json:"can_read_all_group_messages,omitempty"`
	SupportsInlineQueries   bool   `json:"supports_inline_queries,omitempty"`
	CanConnectToBusiness    bool   `json:"can_connect_to_business,omitempty"`
	HasMainWebApp           bool   `json:"has_main_web_app,omitempty"`
}

GetMeResponse represents response payload of TG getMe endpoint -- the bot user.

See: https://core.telegram.org/bots/api#user

type ParseMode added in v1.1.0

type ParseMode = string
const (
	ParseModeMD       ParseMode = "MarkdownV2"
	ParseModeHTML     ParseMode = "HTML"
	ParseModeMDLegacy ParseMode = "Markdown"
)

type TgApiError added in v1.1.0

type TgApiError struct {
	TgCode      int
	Method      string
	Description string
}

TgApiError represents an error returned by the Telegram Bot API.

func (TgApiError) Error added in v1.1.0

func (e TgApiError) Error() string

Directories

Path Synopsis
cmd
tgnotifier command
internal
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL