bots

Don’t Write Release Notes – Use Release-Relay

You know that feeling.
It’s Friday afternoon.
The sun is shining (or the rain is pouring, depending on where you live), and your team has just wrapped up a sprint.
You’ve deployed code, fixed bugs, and maybe even sneaked in a feature or two.
You’re ready to close your laptop and grab a cold beverage.

But wait.
A Slack notification pops up.

“Hey, can you send out the release notes?”

The dread sets in.
You open GitHub.
You scroll through the closed Pull Requests. “Fix typo,” “Update dependency,” “WIP,” “Revert ‘WIP’,” “Actually fix the thing,” “Merge branch ‘main’ into ‘feature/fix-typo’.” It’s a mess. Organizing this into something your manager (or your users) can actually read is a task that sucks the soul right out of your weekend.

Meet the “Release Notes Generator”

I built a tool—let’s call it Release-Relay.
It’s a CLI tool that does the heavy lifting for you.
It connects to your GitHub repository, grabs all the merged PRs between two dates, and turns them into a beautiful, structured Markdown report.

But it’s not just a git log dump.
Oh no, we have standards here.

The “Brain”

In an era where everyone is slapping an LLM on a toaster to make it “smart,” this tool is refreshingly deterministic.
I could have used GPT-5.2 to write sonnets about our bug fixes, but honestly?
I just want to know what changed.
Sometimes, you don’t need a sledgehammer; you just need a really precise screwdriver.

The tool uses a logic-based categorizer (built in TypeScript) that looks at your PR labels and titles.
It’s simple, fast, and remarkably effective.

  • New Features: Labeled feature or title says feat. It’s mostly the stuff sales cares about.
  • Bug Fixes: Labeled bug or title says fix. The stuff customer support cares about.
  • Security: Labeled security or title says CVE. The stuff your CISO cares about.
  • Refactoring: Labeled refactor, chore, infra. The stuff we care about, but nobody else understands.

It sorts everything into semantic buckets so your stakeholders can see the value immediately, without wading through “chore: update eslint” commits.

Gamifying the Grind

One of my favorite features?
The Contributors Leaderboard. The tool automatically counts who merged the most PRs and generates a sorted list.
Nothing motivates a team quite like seeing their name at the top of the list.
(…just remember it’s not about quantity… Quality over quantity! But mostly, it’s fun to see).

The “Notion Challange”

Let’s be real: if you send an email, it gets buried.
If you post on Slack, it scrolls away (psst… we do support it)

But Notion?
Notion is forever. It’s where knowledge goes to… sit nicely in a database.

I wanted these notes to land directly in our team’s Notion workspace.
But have you ever looked at the Notion API payload structure for a “block”?
It’s… verbose.
A simple paragraph is wrapped in three layers of JSON objects.

So, I did what any (sane) engineer would do: I wrote a bash script using jq.

Yes, jq.
The command-line JSON processor that everyone knows exists but nobody remembers how to use without Googling “jq filter array select” first. The script (scripts/post-to-notion.sh) takes the generated Markdown, parses it line by line, and constructs a massive JSON payload to send to the Notion API.

It handles headers, bullet points, and even code blocks.
It’s a beautiful, terrifying piece of shell scripting that somehow works perfectly.

Now, every Thursday (or whenever you schedule the GitHub Action), a fresh page appears in your Release Notes database.

  • Title: Repository Name + Date Range
  • Status: Published
  • Content: Beautifully formatted headers, bullet points, and code blocks.

It’s magic. You wake up, check Notion, and the documentation is done.
Btw, it is also very helpful for your SOC-2…

The Tech Stack

For the fellow developers reading this, here’s how the sausage is made:

  • TypeScript: Because I value my sanity and type safety.
  • Octokit: GitHub’s official SDK. It handles the messy parts of the GitHub API, like pagination.
    Did you know you have to paginate if you have more than 100 merged PRs? I found that out the hard way.
  • Vitest: For testing. It’s fast, it’s compatible with Jest, and it just works.
  • Pino: For structured logging.
    Because console.log("here") is not a debugging strategy.
    Pino gives us beautiful JSON logs that we can ingest anywhere.
  • GitHub Actions: The cron job that runs this whole operation.
    It’s set to run weekly, so I don’t even have to click a button.

Why You Should Care

  1. Consistency: Your release notes will always look the same.
    No more “Dave writes them this way, and Sarah writes them that way.”
  2. Visibility: Stakeholders love “Executive Summaries.” This tool generates stats on code delta (+10,000 lines / -5,000 lines), file changes, and focus areas automatically.
    You look professional with zero effort.
  3. Sanity: You save 30-60 minutes every week.
    That’s 2-4 hours a month. That’s… a lot of coffee breaks.
    Or time to actually fix those bugs in the backlog.

Future Plans?

  1. I’ll add AI summarization one day.
    It would be cool to have a “TL;DR” generated by an LLM that reads the PR descriptions.
  2. Other messaging apps integration, so a bot can announce “Release Notes are up!” in the channels.
  3. Pushing these markdown reports to other systems.

But for now, it does one thing, and it does it well.

Try It Yourself

The code is open source (obviously).
You can run it locally on your machine to generate a Markdown file, or set it up as a GitHub Action to run on a schedule.

# It's as simple as:
npm run dev -- \
--owner my-org \
--repo my-cool-app \
--from 2026-02-01 \
--to 2026-02-14

Stop writing release notes.
Start automating them.

Check out the code here: https://github.com/greenido/-Release-Relay

(P.S. If you find a bug in the bug-categorizer… is that irony? Or just recursion?)


Discover more from Ido Green

Subscribe to get the latest posts sent to your email.

Standard

Leave a comment