diff --git a/.forgejo/workflows/release-notes-assistant.yml b/.forgejo/workflows/release-notes-assistant.yml
new file mode 100644
index 0000000000..3b520cbada
--- /dev/null
+++ b/.forgejo/workflows/release-notes-assistant.yml
@@ -0,0 +1,31 @@
+on:
+  pull_request_target:
+    types:
+      - opened
+      - reopened
+      - edited
+      - synchronize
+      - labeled
+
+jobs:
+  release-notes:
+    if: ${{ !startsWith(vars.ROLE, 'forgejo-') && contains(github.event.pull_request.labels.*.name, 'worth a release-note') }}
+    runs-on: docker
+    container:
+      image: 'docker.io/node:20-bookworm'
+    steps:
+      - uses: https://code.forgejo.org/actions/checkout@v3
+
+      - uses: https://code.forgejo.org/actions/setup-go@v4
+        with:
+          go-version-file: "go.mod"
+
+      - name: apt install jq
+        run: |
+         export DEBIAN_FRONTEND=noninteractive
+         apt-get update -qq
+         apt-get -q install -y -qq jq
+
+      - name: release-notes-assistant preview
+        run: |
+          go run code.forgejo.org/forgejo/release-notes-assistant@1.0.0 --config .release-notes-assistant.yaml --storage pr --storage-location ${{ github.event.pull_request.number }}  --forgejo-url $GITHUB_SERVER_URL --repository $GITHUB_REPOSITORY --token $GITHUB_TOKEN preview ${{ github.event.pull_request.number }}
diff --git a/.release-notes-assistant.yaml b/.release-notes-assistant.yaml
new file mode 100644
index 0000000000..d7499adb51
--- /dev/null
+++ b/.release-notes-assistant.yaml
@@ -0,0 +1,22 @@
+categorize: './release-notes-assistant.sh'
+branch-development: 'forgejo'
+branch-pattern: 'v*/forgejo'
+branch-find-version: 'v(?P<version>\d+\.\d+)/forgejo'
+branch-to-version: '${version}.0'
+branch-from-version: 'v%[1]d.%[2]d/forgejo'
+tag-from-version: 'v%[1]d.%[2]d.%[3]d'
+branch-known:
+  - 'v7.0/forgejo'
+cleanup-line: 'sed -Ee "s/.*?:\s*//g" -e "s;\[(UI|BUG|FEAT|v.*?/forgejo)\]\s*;;g"'
+render-header: |
+
+  ## Draft release notes
+comment: |
+  <details>
+  <summary>Where does that come from?</summary>
+  The following is a preview of the release notes for this pull request, as they will appear in the upcoming release. They are derived from the content of the `%[2]s/%[3]s.md` file, if it exists, or the title of the pull request. They were also added at the bottom of the description of this pull request for easier reference.
+
+  This message and the release notes originate from a call to the [release-notes-assistant](https://code.forgejo.org/forgejo/release-notes-assistant).
+  </details>
+
+  %[1]s
diff --git a/release-notes-assistant.sh b/release-notes-assistant.sh
new file mode 100755
index 0000000000..4e15975340
--- /dev/null
+++ b/release-notes-assistant.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+# Copyright twenty-panda <twenty-panda@posteo.com>
+# SPDX-License-Identifier: MIT
+
+payload=$(mktemp)
+pr=$(mktemp)
+trap "rm $payload $pr" EXIT
+
+cat >$payload
+#
+# If this is a backport, refer to the original PR to figure
+# out the classification.
+#
+if $(jq --raw-output .IsBackportedFrom <$payload); then
+  jq --raw-output '.BackportedFrom[0]' <$payload >$pr
+else
+  jq --raw-output '.Pr' <$payload >$pr
+fi
+
+labels=$(jq --raw-output '.labels[].name' <$pr)
+
+#
+# Was this PR labeled `worth a release note`?
+#
+if echo "$labels" | grep --quiet worth; then
+  worth=true
+else
+  worth=false
+fi
+
+#
+# If there was no release-notes/N.md file and it is not
+# worth a release note, just forget about it.
+#
+if test -z "$(jq --raw-output .Draft <$payload)"; then
+  if ! $worth; then
+    echo -n ZA Included for completness but not worth a release note
+    exit 0
+  fi
+fi
+
+case "$labels" in
+*bug*)
+  if $(jq --raw-output .IsBackportedTo <$payload); then
+    #
+    # if it has been backported, it was in the release notes of an older stable release
+    # and does not need to be in this more recent release notes
+    #
+    echo -n ZB Already announced in the release notes of an older stable release
+    exit 0
+  fi
+  ;;
+esac
+
+case "$labels" in
+*breaking*)
+  case "$labels" in
+  *feature*) echo -n AA Breaking features ;;
+  *bug*) echo -n AB Breaking bug fixes ;;
+  *) echo -n ZC Breaking changes without a feature or bug label ;;
+  esac
+  ;;
+*forgejo/ui*)
+  case "$labels" in
+  *feature*) echo -n BA User Interface features ;;
+  *bug*) echo -n BB User Interface bug fixes ;;
+  *) echo -n ZD User Interface changes without a feature or bug label ;;
+  esac
+  ;;
+*feature*) echo -n CA Features ;;
+*bug*) echo -n CB Bug fixes ;;
+*) echo -n ZE Other changes without a feature or bug label ;;
+esac