DEV Community

Dakota Liu
Dakota Liu

Posted on

From Changelog Noise to Three Bullet Points: A Free-Tier Case Study

You don't need a paid infrastructure plan to turn a messy CHANGELOG into a useful morning digest. In this case study we walk through one concrete small project: a daily script that pulls the latest release section from a public repository, asks a free model to summarize it for non-developers, and posts the result to a chat webhook. The entire pipeline runs on a free server, and the summarization step uses free model tokens, which means the only real cost is your time.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Background: the changelog nobody reads

Your release notes are probably being ignored. A CHANGELOG.md with forty lines of dense, developer-facing details is not a handoff document for product managers, support agents, or the sales team. They want three bullets: what changed, where it matters, and whether a user must do something. A long list of dependency bumps does not tell them that. In this project, the goal is to give stakeholders a short, accurate summary every morning without adding another paid SaaS subscription to the stack.

Goal and non-goals

Goal: every day at 08:00, fetch a repository's CHANGELOG, extract the latest version section, produce at most three bullet points, and send them to a Slack or Discord webhook. Non-goals: no data warehouse, no user interface, no container orchestration, no new framework. The whole project should stay under one hundred lines and run inside a free server's cron scheduler. If the model call fails, the script should still produce something useful with a rule-based fallback.

Implementation: one Python script, two layers

The cleanest way to make the system honest is to separate the fetching and parsing logic from the actual summarization. That way you can test each layer independently and replace the model later without rewriting everything.

1. Fetch and parse

The standard library is enough for this step. urllib fetches the raw Markdown, and a simple regex splits on ## headers:

# digest.py - reproducible with Python 3.11+
import os
import re
import urllib.request

CHANGELOG_URL = os.environ.get(
    "CHANGELOG_URL",
    "https://raw.githubusercontent.com/octocat/Hello-World/main/CHANGELOG.md",
)
WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "")

def fetch_text(url: str) -> str:
    req = urllib.request.Request(url, headers={"User-Agent": "digest/0.1"})
    with urllib.request.urlopen(req, timeout=15) as resp:
        return resp.read().decode("utf-8", errors="replace")

def extract_latest_section(markdown: str) -> str:
    sections = re.split(r"\n## ", markdown)
    return ("## " + sections[1]).strip() if len(sections) > 1 else markdown[:2000]
Enter fullscreen mode Exit fullscreen mode

2. Summarize with free model tokens, fallback to heuristics

The interesting part is the summary function. MonkeyCode exposes free model tokens through its own access mechanism, so the code below leaves a clear hook for that call. The fallback is deliberately simple: it collects lines that mention meaningful keywords like fix, add, deprecat, or security. This does not produce beautiful prose, but it keeps the digest alive when the model is unavailable or you are still testing credentials.

def summarize_with_free_model(section: str) -> str:
    # Wire this function to MonkeyCode's free model access.
    # Keep the prompt strict: no new facts, match the source.
    raise NotImplementedError("Wire this to your MonkeyCode model endpoint")

def fallback_summarize(section: str) -> str:
    keywords = ("fix", "add", "deprecat", "security", "drop", "break")
    lines = [line.strip() for line in section.splitlines() if line.strip()]
    hits = [line for line in lines if any(k in line.lower() for k in keywords)]
    return "\n".join(hits[:3]) if hits else lines[0][:200]
Enter fullscreen mode Exit fullscreen mode

The fallback is a feature, not a hack. It lets you validate the rest of the pipeline before you spend a single token. Once the free model call is wired into summarize_with_free_model, the same program runs unchanged.

3. Deliver and schedule

The final function reads the environment variables, runs the fetch and summarize steps, and posts a simplified JSON payload to a webhook. If no webhook is configured, it prints the digest so you can inspect it locally.

def main() -> None:
    print("Fetching changelog...")
    raw = fetch_text(CHANGELOG_URL)
    latest = extract_latest_section(raw)
    print("Latest section:", latest[:200])
    try:
        digest = summarize_with_free_model(latest)
    except NotImplementedError:
        digest = fallback_summarize(latest)
    except Exception as exc:
        print(f"Model failed ({exc}), using fallback")
        digest = fallback_summarize(latest)
    print(digest)
    if WEBHOOK_URL:
        payload = f'{{"content": "{digest}"}}'.encode()
        req = urllib.request.Request(
            WEBHOOK_URL, payload, {"Content-Type": "application/json"}
        )
        urllib.request.urlopen(req)
    else:
        print("No webhook set; summary printed above.")

if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

On the free server, you can place this file in a directory and add a cron line. The exact syntax depends on your hosting control panel, but the command usually looks like this:

0 8 * * * cd /path/to/project && python3 digest.py >> digest.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Case study: running it end to end

Now let's trace the project from the free server's perspective. The server wakes up, the cron job calls digest.py, and the script fetches the raw changelog. If the model endpoint is not yet wired, the fallback runs. That is the point where most people stop testing and deploy — do not do that. You need to verify the prompt output against at least three different changelogs before you schedule it for your team.

Here is a small test matrix you can run manually:

CHANGELOG_URL=https://raw.githubusercontent.com/example/repo-a/main/CHANGELOG.md python3 digest.py
CHANGELOG_URL=https://raw.githubusercontent.com/example/repo-b/main/CHANGELOG.md python3 digest.py
CHANGELOG_URL=https://raw.githubusercontent.com/example/repo-c/main/CHANGELOG.md python3 digest.py
Enter fullscreen mode Exit fullscreen mode

Pick one normal release, one dependency-only release, and one emergency hotfix. Compare the model summary with the source section. The model will occasionally add a verb that wasn't there, or merge two unrelated fixes into one bullet. That is exactly why a human must look at the first few outputs before the script earns a place in your morning routine.

Decision table: which layer to trust

Situation Use free model summary Use fallback summary
Complex behavioral changes, e.g. auth logic rewrite Yes No
Long list of dependency bumps Maybe Yes
Need deterministic output with no API dependency No Yes
First deployment, still validating prompt quality No Yes, as a baseline
Compliance or audit trail requiring exact wording No No, use the raw changelog

The decision table is not a substitute for testing. It is a way to force the conversation about failure modes before anyone schedules the cron job.

What to watch for

A reproducible local run will expose three common failure modes. First, the model may hallucinate version numbers when the changelog section is vague. Second, the fallback may pick a pull-request title that contains the word fix but is actually a minor refactor. Third, the free server may sleep after inactivity, so the first cron run after a long pause can be slower or skipped. The fix for the sleep problem is to configure the server's wake-up behavior or to accept a small delay as the cost of staying on the free tier.

You also need to watch token consumption. Free model tokens are convenient, but they are not infinite, and every prompt adds to the account activity. A daily digest that sends one short prompt uses almost nothing, but a loop that retries on every error can burn more than you expect. Put a simple counter or log line around summarize_with_free_model so you can see the actual usage.

Limitations and who should skip this

This pattern is not for everyone. Do not use it if your changelog is the only source of truth for an external compliance report, because even a good model can omit an important nuance. Do not use it if your team expects perfectly formatted tickets, because the output is a chat message, not a curated release page. And do not use it if you cannot tolerate occasional wrong version numbers, because the model will make mistakes and the fallback will too.

The approach makes sense for small internal teams that need a gentle nudge, not a legal record. It also works for RSS feeds, internal event logs, or any text source with a predictable structure. The one-script-plus-cron shape is easy to extend and easy to delete, which is a feature.

The takeaway

A small internal tool does not need a paid infrastructure plan. With MonkeyCode's free server option and free model tokens, you can build a digest pipeline that is genuinely useful to non-developers, as long as you test the summary layer before trusting it. Start with the fallback, add the model later, and let the cron log tell you where the pipeline breaks. If your changelog has been ignored for months, this is a cheap way to see whether a three-bullet morning summary actually changes the conversation.

Top comments (0)