github commit notifier.


why build this?

i wanted instant notifications when code got pushed to my repos - just simple telegram messages with the commit details. no waiting for emails, no checking dashboards, just push and see it show up in telegram.


what does it do?

it sends telegram notifications whenever commits get pushed to a repository, including the pusher's name, branch, commit count, and clickable links to each commit:

šŸ”” username/repo
šŸ‘¤ conrad
🌿 main
šŸ“¦ 3 commit(s)

• a3f9b12 fix authentication bug
• 8c2d4e1 add user settings page
• 1f7a8b3 update readme

View Changes

no servers to maintain, no polling, just github actions doing its thing.


what's it built with?

original approach:

  • rust + axum — webhook server
  • teloxide — telegram bot library
  • fly.io — was planning to host it there

what i ended up with:

  • github actions — workflow automation
  • bash + curl — telegram api calls
  • jq — json parsing in workflows

how did it evolve?

this started as a proper rust service with a webhook server, signature verification, and all the proper error handling. it worked great locally, but then i realized i'd need to keep a vps running 24/7 just for a notification bot.

so i scrapped it and rewrote the whole thing as a github action instead. then spent a while fixing markdown escaping and making the messages actually useful with commit links.

the rust version was more interesting to build, but the actions version makes way more sense for what it actually needs to do.


→ markdown escaping

telegram's markdownv2 format escapes almost every special character, and getting this right took a few tries:

escape() {
  echo "$1" | sed 's/\\/\\\\/g' | sed 's/_/\\_/g' | \
    sed 's/\*/\\*/g' | sed 's/\[/\\[/g' | \
    sed 's/\]/\\]/g' | sed 's/(/\\(/g' | \
    sed 's/)/\\)/g' | sed 's/~/\\~/g' | \
    sed 's/`/\\`/g' | sed 's/>/\\>/g' | \
    sed 's/#/\\#/g' | sed 's/+/\\+/g' | \
    sed 's/=/\\=/g' | sed 's/|/\\|/g' | \
    sed 's/{/\\{/g' | sed 's/}/\\}/g' | \
    sed 's/\./\\./g' | sed 's/!/\\!/g' | \
    sed 's/-/\\-/g'
}

every character in commit messages needs escaping - miss one and telegram rejects the whole message.

→ workflow structure

i wanted it to be reusable across repos, so the workflow accepts secrets and can be called from anywhere:

on:
  workflow_call:
    secrets:
      TELEGRAM_TOKEN:
        required: true
      TELEGRAM_CHAT_ID:
        required: true

define it once, use it everywhere by calling it from other repos and passing through the secrets.

→ commit links

making the commit hashes clickable turned out to be pretty useful - instead of copying hashes you can just click through:

HASH=$(echo "$line" | jq -r '.id[0:7]')
MESSAGE=$(echo "$line" | jq -r '.message | split("\n")[0]')
COMMIT_URL=$(echo "$line" | jq -r '.url')

ESCAPED_COMMITS="${ESCAPED_COMMITS}• [$(escape "$HASH")](${COMMIT_URL}) $(escape "$MESSAGE")
"

this extracts the short hash, first line of the commit message, and the full url, then formats them as markdown links.


what did i learn?

  • sometimes the boring solution is the right one - the rust version was technically cooler but practically worse for this use case
  • hosting costs add up for things that don't need to run constantly, and github actions is free for this kind of thing
  • telegram's markdown escaping is way more annoying than you'd expect
  • reusable workflows save a lot of time once you have them set up
  • throwing away working code for a simpler approach isn't failure, it's just part of figuring out what you actually need

final thoughts

the final version only runs when there's actually something to do, costs nothing, and doesn't need any maintenance. for a notification bot, that matters more than having an interesting tech stack.