9 min de cititIntegrări

Shorten Links in Release Notes and Track Every Download

Shorten links in release notes with a stable latest link you repoint per release, one tagged link per channel, and click data that shows what drove downloads.

Marius Voß
DevRel · edge infra
Pixel-style cover showing how to shorten links in release notes: one stable latest link repointed from v2.3 to v2.4, with separate tagged links for Slack, X and the newsletter

Release notes travel further than almost anything an engineering team writes. Nobody measures them. You paste a download link into the release body, someone copies it into Slack, marketing drops it into the newsletter, a maintainer posts it on X. Six months later half of those links point at a file that no longer exists and none of them told you anything. To shorten links in release notes properly, you need three things: one stable "latest" short link that you repoint on every release, a separate tagged link per channel for each version, and a workflow on the release: published event that creates both so nobody has to remember.

That's the whole answer. The rest of this post is how to wire it without making a mess, and what the click data can and can't tell you afterwards.

I've watched a lot of projects handle GitHub release notes links by hand, and the failure mode is always the same: someone links to a versioned asset, the link gets quoted in a forum thread or a Stack Overflow answer, and the next release silently orphans it. If you already manage links as code, the approach below slots next to short links managed in Terraform, with the difference that release links change on a schedule you don't control by hand.

Two separate problems hide here. They need different fixes.

The first is link rot. GitHub gives you stable URLs for the release page (/releases/latest) and for files, via /releases/latest/download/asset-name, but the second only works when the asset keeps an identical name across releases, according to GitHub's own docs on linking to releases. Most build pipelines stamp the version into the filename, so app-2.3.0.dmg becomes app-2.4.0.dmg and the "latest" download URL that worked last week now returns a 404. Docs links rot too, whenever a docs site gets reorganized. The broader patterns are in our link rot prevention strategy; release notes are just the place it bites hardest, because the links travel furthest.

The second problem is attribution, and it's quieter. GitHub's REST API does report a download_count on each release asset, which is more than most people realize. What it doesn't report is where the download came from. A spike of 4,000 downloads the day after release could be the newsletter, a Hacker News thread or a single enterprise customer's CI pulling the binary on a loop. Links pasted into Slack and DMs strip the referrer entirely, which is the dark social attribution problem in miniature.

Create one short link, say get.example.dev/latest, and treat it as a pointer. Every doc page, README badge and install script uses it. On each stable release, you update where it points. The slug never changes, so nothing that quoted it ever breaks.

In Elido that pointer is an ordinary link. You create it once with POST /v1/workspaces/{workspace_id}/links, passing the domain_id of your branded domain, the slug, and the destination_url. Save the id from the 201 response. Repointing is a PATCH /v1/workspaces/{workspace_id}/links/{link_id} with a new destination_url and nothing else; the slug, tags and click history stay put.

Keep it a 302. Elido links default to 302, and there's a reason not to change it for this one: a 301 is cacheable by default under RFC 9110, so a browser that saw last month's redirect may never ask again. A pointer that browsers remember forever is no longer a pointer. More in 301 vs 302 redirects for short links.

Diagram of a stable latest short link being repointed from the v2.3.0 download to the v2.4.0 download when a release is published, while the per-release links for v2.3.0 keep pointing at their own tag

Decide one thing up front. Should the latest link point at the file, or at the release page? I'd point it at the release page for anything with more than one platform build, and keep per-platform latest links (/latest-mac, /latest-linux) only if your install docs really need a direct file. Fewer moving pointers means fewer things to repoint wrong.

The latest link answers "does the link still work". It can't answer "which channel worked", because everyone clicks the same slug. For that, each release gets its own small set of links, one per channel, created at publish time.

Here's the part most UTM guides skip. Tacking utm_source=slack onto a github.com URL does nothing useful, because you'll never see GitHub's analytics. UTMs earn their place only when the destination is a site you measure, like your docs or your own download page. When the destination is GitHub, the separate short link per channel is the attribution: the click is counted at the redirect, before GitHub ever sees it.

ChannelSlug for v2.4.0DestinationWhat the clicks tell you
Slack communityv2-4-0-slackGitHub release pageClicks from your own community
X / Mastodonv2-4-0-socialGitHub release pageReach beyond existing users
Newsletterv2-4-0-newsDocs upgrade guide + UTMsClicks plus on-site behaviour in your analytics
Latest (stable)latestCurrent release, repointedTotal demand across every version

Tag each per-release link with the version and the channel (["release", "v2.4.0", "slack"]), because tags are how you'll pull the set back out later: GET .../links?tags=v2.4.0 lists everything for one release. Keep any UTM values boring and identical between releases. The UTM naming conventions guide has the rules I'd copy.

A sibling guide covers generic CI link creation, so this section stays on the release-specific parts. The trigger is release with the published activity type. According to GitHub's list of workflow events, published fires for stable releases and pre-releases alike, including pre-releases published from a draft, which is exactly why the repoint step below checks the prerelease flag.

name: release-links
on:
  release:
    types: [published]

jobs:
  links:
    runs-on: ubuntu-latest
    env:
      API: https://api.elido.app/v1/workspaces/${{ vars.ELIDO_WORKSPACE_ID }}
      DOMAIN_ID: ${{ vars.ELIDO_DOMAIN_ID }}
      TAG: ${{ github.event.release.tag_name }}
      PAGE: ${{ github.event.release.html_url }}
      ELIDO_TOKEN: ${{ secrets.ELIDO_TOKEN }}
    steps:
      - name: Create one link per channel
        run: |
          v=$(echo "$TAG" | tr '.' '-')
          for ch in slack social news; do
            body=$(jq -n --argjson d "$DOMAIN_ID" --arg s "$v-$ch" \
              --arg u "$PAGE" --arg t "$TAG" --arg c "$ch" \
              '{domain_id:$d, slug:$s, destination_url:$u, tags:["release",$t,$c]}')
            code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$API/links" \
              -H "Authorization: Bearer $ELIDO_TOKEN" \
              -H "Content-Type: application/json" -d "$body")
            case "$code" in 201|409) ;; *) echo "create $ch failed: $code"; exit 1;; esac
            echo "- $ch: https://get.example.dev/$v-$ch" >> "$GITHUB_STEP_SUMMARY"
          done

      - name: Repoint the latest link
        if: ${{ !github.event.release.prerelease }}
        run: |
          curl -sf -X PATCH "$API/links/${{ vars.ELIDO_LATEST_LINK_ID }}" \
            -H "Authorization: Bearer $ELIDO_TOKEN" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg u "$PAGE" '{destination_url:$u}')"

The job summary gives whoever posts the announcement a ready list of links, and a 409 on re-run means the slug already exists, so a retried workflow doesn't fail or duplicate anything. The newsletter link would point at your docs with UTMs in the destination; I left it on the release page here to keep the example short.

Three gotchas that cost me an afternoon

The first one is silent. If your release pipeline publishes the release using the default GITHUB_TOKEN, this workflow never runs, because events created with GITHUB_TOKEN don't trigger new workflow runs. No error, no skipped job, nothing. Publish with a GitHub App token instead.

Second, dots. I convert v2.4.0 to v2-4-0 for the slug because version strings with dots look like file extensions in chat previews, and some clients linkify them oddly.

Third, don't let the workflow edit the release body unless you have to. It works (gh release edit --notes-file), but it rewrites text a human just approved, and it fires edited events that other automations may react to. The step summary is less clever and much safer. Retries get their own post: API rate limits and idempotency.

If you're still pasting release links by hand, the workflow above is about twenty minutes of setup. Start a free Elido workspace, point a branded domain at it, and let the next tag create its own links.

How to Track Clicks on Release Notes by Channel

After two or three releases, the data starts answering questions GitHub's counter can't.

Per-channel comparison is the simple one. Pull the links tagged with a version, then read each link's click summary scoped by link_id. If v2-4-0-news beats v2-4-0-social by five to one for three releases running, you've learned where your users actually are, and it's rarely where the team assumed. The announcement with the most likes is often not the one that sends people to the download, so expect pushback the first time you show the numbers, and wait for the third release in a row before anyone rewrites the launch plan around them.

The latest link has a less obvious trick. Every click records the destination it resolved to at that moment, so the analytics breakdown by destination, scoped to the latest link, splits its traffic by version. After a repoint you can watch the old destination's share fall off and see how long stragglers keep arriving on cached pages and old bookmarks. That's your real upgrade curve, measured at the top of the funnel.

Diagram showing how to track clicks on release notes: Slack, social and newsletter links for one release feed per-link click counts, while the latest link breaks down clicks by destination version

Two honest limits. Clicks aren't downloads: someone can click through to the release page and leave, and GitHub's download_count stays the source of truth for completed fetches. And bots do click release links, especially link-preview fetchers in chat apps, so read trends across releases rather than trusting any single day. The analytics feature page lists which breakdowns are available on each plan.

Per-release links never move. v2-3-0-slack points at the v2.3.0 tag in March and still points there in five years, which is what someone reading an old forum thread expects. Only the latest link changes, and only on stable releases.

The one case where you should touch an old link is a pulled release. If v2.4.0 ships with a data-loss bug, don't delete its links; repoint every v2-4-0-* link to v2.4.1 with the same PATCH call and a short note in the release body. Deleting gives people who saved the link a dead end at exactly the moment they most need the fix. A newer version beats a 404. Every time.

For projects that also keep old release links in READMEs, install scripts and package manager metadata, the developer-focused guide to URL shorteners covers where else short links earn their keep. The full REST surface is on the API and SDKs page.

Read the cornerstone → Manage your short links as Terraform

Întrebări frecvente

How do I link to the latest GitHub release?

GitHub supports /releases/latest for the release page and /releases/latest/download/asset-name for a file, as long as the asset keeps the same name in every release. If your asset names carry the version number, put a short link in front and repoint it on each release instead.

Can you track clicks on GitHub release downloads?

Partly. The GitHub REST API reports a download_count per release asset, but it has no referrer, country or channel data, so it cannot tell you whether the download came from Slack, X or a newsletter. A short link per channel in front of the asset gives you that split.

Should a latest-release short link use a 301 or a 302 redirect?

Use a 302. Browsers may cache a 301 indefinitely, so a reader who clicked last month can keep landing on the old version after you repoint the link. Elido links default to 302, which keeps the destination under your control on every click.

Why doesn't my release workflow run when another workflow publishes the release?

Events created with the repository's GITHUB_TOKEN do not start new workflow runs, apart from workflow_dispatch and repository_dispatch. If a release pipeline publishes with GITHUB_TOKEN, the release: published trigger never fires. Publish with a GitHub App token or a fine-grained personal access token instead.

Do UTM parameters work on links to github.com?

They pass through, but they do nothing for you, because you never see GitHub's analytics. UTMs only pay off when the destination is a site you measure, like your docs or download page. For github.com destinations, the separate short link per channel is the attribution.

What happens to old release links when a new version ships?

Nothing, if you set it up this way. Per-release links keep pointing at their own tag forever, and only the one latest link moves. If a release is pulled, repoint its links to the fixed version rather than deleting them, so people who saved the old link still land somewhere useful.

Încearcă Elido

Lipește un URL, obții un link scurt funcțional

Fără înregistrare. Linkul este activ timp de 30 de zile. Înregistrează-te ca să-l păstrezi pentru totdeauna.

Gratuit, fără înregistrare · 2 pe zi

Încearcă Elido

Scurtător de URL-uri găzduit în UE, cu domenii personalizate, analiză avansată și un API deschis. Nivel gratuit - fără card bancar.

Etichete
shorten links in release notes
github release notes links
track clicks on release notes
github actions
release automation
link rot

Continuă lectura