diff --git a/modules/steps.py b/modules/steps.py
index 0dc365c..4ab9272 100644
--- a/modules/steps.py
+++ b/modules/steps.py
@@ -19,6 +19,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 #
 
+import json
 import os
 import shutil
 import re
@@ -80,6 +81,16 @@ def _make_commit_msg(group):
     versions = list(dict.fromkeys(_get_version(p) for p in group['pkgs']))
     return "{}: upgrade {}".format(pn, ",".join(versions))
 
+def _get_changelog_metadata(metadata_path):
+    """Return the metadata devtool wrote next to the changelog, or an
+    empty dict if it is missing or unreadable (e.g. older devtool)."""
+    try:
+        with open(metadata_path, 'r', encoding='utf-8') as f:
+            return json.load(f)
+    except (OSError, ValueError):
+        return {}
+
+
 def _get_changelog(pkg_ctx, workdir):
     """Copy the changelog generated by devtool upgrade to workdir and
     store the changelog data in pkg_ctx."""
@@ -97,6 +108,7 @@ def _get_changelog(pkg_ctx, workdir):
             return
         if text:
             pkg_ctx['changelog_text'] = text
+            pkg_ctx['changelog_metadata'] = _get_changelog_metadata(base_path + '.json')
 
 
 def _append_changelog_to_commit_msg(group, opts):
@@ -105,6 +117,10 @@ def _append_changelog_to_commit_msg(group, opts):
     for p in group['pkgs']:
         if 'changelog_text' not in p:
             continue
+        # Changelog stays attached to the email, only the commit message skips it
+        if p['changelog_metadata'].get('changelog_skip'):
+            I(" %s: changelog not added to commit message (RECIPE_UPGRADE_CHANGELOG_SKIP)" % p['PN'])
+            continue
         text = p['changelog_text'].strip()
         # Group recipes may share a source and produce identical changelogs
         # (e.g. mesa, mesa-native); skip proven duplicates, keep the rest.
