diff --git a/lib/workers/repository/model/commit-message.ts b/lib/workers/repository/model/commit-message.ts
index b909f5bc1d837da52d340f71af9147d1b35549ca..0344e5b6471fc0ee8eb2f321ec58d84ae11edfb6 100644
--- a/lib/workers/repository/model/commit-message.ts
+++ b/lib/workers/repository/model/commit-message.ts
@@ -60,10 +60,6 @@ export abstract class CommitMessage {
     this._footer = this.normalizeInput(value);
   }
 
-  get subject(): string {
-    return this._subject;
-  }
-
   set subject(value: string) {
     this._subject = this.normalizeInput(value);
     this._subject = this._subject?.replace(
diff --git a/lib/workers/repository/model/semantic-commit-message.spec.ts b/lib/workers/repository/model/semantic-commit-message.spec.ts
index 57e74b1bfc56e8a73467a1c8f8f96fa21b3350d8..db76829ba5b9571cfc7e7026a2e18e521462ba8b 100644
--- a/lib/workers/repository/model/semantic-commit-message.spec.ts
+++ b/lib/workers/repository/model/semantic-commit-message.spec.ts
@@ -25,15 +25,6 @@ describe('workers/repository/model/semantic-commit-message', () => {
     expect(message.toString()).toBe('fix(scope): test');
   });
 
-  it('should transform to lowercase only first letter', () => {
-    const message = new SemanticCommitMessage();
-    message.subject = 'Update My Org dependencies';
-    message.type = 'fix';
-    message.scope = 'deps ';
-
-    expect(message.toString()).toBe('fix(deps): update My Org dependencies');
-  });
-
   it('should create instance from string without scope', () => {
     const instance = SemanticCommitMessage.fromString('feat: ticket 123');
 
diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts
index a27b4015a589e6b29aa0ae585a702e14a8cbe85b..25ded125464fbf6aa4b92d7695446901abab7a88 100644
--- a/lib/workers/repository/updates/generate.spec.ts
+++ b/lib/workers/repository/updates/generate.spec.ts
@@ -504,7 +504,7 @@ describe('workers/repository/updates/generate', () => {
         }),
       ];
       const res = generateBranchConfig(branch);
-      expect(res.prTitle).toBe('chore: update dependency some-dep to v1.2.0');
+      expect(res.prTitle).toBe('chore(): update dependency some-dep to v1.2.0');
     });
 
     it('scopes monorepo commits with nested package files using parent directory', () => {
@@ -908,17 +908,5 @@ describe('workers/repository/updates/generate', () => {
         '`1.1.1` (+1)',
       ]);
     });
-
-    it('fixes commit message with body', () => {
-      const branch = [
-        partial<BranchUpgradeConfig>({
-          ...defaultConfig,
-          commitMessage: 'update to vv1.2.0',
-          commitBody: 'some body',
-        }),
-      ];
-      const res = generateBranchConfig(branch);
-      expect(res.commitMessage).toBe('Update to v1.2.0\n\nsome body');
-    });
   });
 });
diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts
index 777b77763ff0034542e420af5285adab767a85e8..f3df4f73124313e503d41289d04ef7ce885deff3 100644
--- a/lib/workers/repository/updates/generate.ts
+++ b/lib/workers/repository/updates/generate.ts
@@ -5,12 +5,11 @@ import semver from 'semver';
 import { mergeChildConfig } from '../../../config';
 import { CONFIG_SECRETS_EXPOSED } from '../../../constants/error-messages';
 import { logger } from '../../../logger';
-import { regEx } from '../../../util/regex';
+import { newlineRegex, regEx } from '../../../util/regex';
 import { sanitize } from '../../../util/sanitize';
 import * as template from '../../../util/template';
 import type { BranchConfig, BranchUpgradeConfig } from '../../types';
-import { CommitMessageFactory } from '../model/commit-message-factory';
-import { SemanticCommitMessage } from '../model/semantic-commit-message';
+import { CommitMessage } from '../model/commit-message';
 
 function isTypesGroup(branchUpgrades: BranchUpgradeConfig[]): boolean {
   return (
@@ -167,24 +166,28 @@ export function generateBranchConfig(
     } else if (semver.valid(toVersions[0])) {
       upgrade.isRange = false;
     }
-    const commitMessageFactory = new CommitMessageFactory(upgrade);
-    const commitMessage = commitMessageFactory.create();
     // Use templates to generate strings
-    if (SemanticCommitMessage.is(commitMessage)) {
+    if (upgrade.semanticCommits === 'enabled' && !upgrade.commitMessagePrefix) {
       logger.trace('Upgrade has semantic commits enabled');
-      commitMessage.scope = template.compile(
-        upgrade.semanticCommitScope,
-        upgrade
-      );
+      let semanticPrefix = upgrade.semanticCommitType;
+      if (upgrade.semanticCommitScope) {
+        semanticPrefix += `(${template.compile(
+          upgrade.semanticCommitScope,
+          upgrade
+        )})`;
+      }
+      upgrade.commitMessagePrefix = CommitMessage.formatPrefix(semanticPrefix);
+      upgrade.toLowerCase =
+        regEx(/[A-Z]/).exec(upgrade.semanticCommitType) === null &&
+        !upgrade.semanticCommitType.startsWith(':');
     }
     // Compile a few times in case there are nested templates
-    commitMessage.subject = template.compile(
+    upgrade.commitMessage = template.compile(
       upgrade.commitMessage || '',
       upgrade
     );
-    commitMessage.subject = template.compile(commitMessage.subject, upgrade);
-    commitMessage.subject = template.compile(commitMessage.subject, upgrade);
-    upgrade.commitMessage = commitMessage.formatSubject();
+    upgrade.commitMessage = template.compile(upgrade.commitMessage, upgrade);
+    upgrade.commitMessage = template.compile(upgrade.commitMessage, upgrade);
     // istanbul ignore if
     if (upgrade.commitMessage !== sanitize(upgrade.commitMessage)) {
       logger.debug(
@@ -193,14 +196,23 @@ export function generateBranchConfig(
       );
       throw new Error(CONFIG_SECRETS_EXPOSED);
     }
-    commitMessage.subject = commitMessage.subject.replace(
+    upgrade.commitMessage = upgrade.commitMessage.trim(); // Trim exterior whitespace
+    upgrade.commitMessage = upgrade.commitMessage.replace(regEx(/\s+/g), ' '); // Trim extra whitespace inside string
+    upgrade.commitMessage = upgrade.commitMessage.replace(
       regEx(/to vv(\d)/),
       'to v$1'
     );
-    upgrade.commitMessage = commitMessage.formatSubject();
+    if (upgrade.toLowerCase) {
+      // We only need to lowercase the first line
+      const splitMessage = upgrade.commitMessage.split(newlineRegex);
+      splitMessage[0] = splitMessage[0].toLowerCase();
+      upgrade.commitMessage = splitMessage.join('\n');
+    }
     if (upgrade.commitBody) {
-      commitMessage.body = template.compile(upgrade.commitBody, upgrade);
-      upgrade.commitMessage = commitMessage.toString();
+      upgrade.commitMessage = `${upgrade.commitMessage}\n\n${template.compile(
+        upgrade.commitBody,
+        upgrade
+      )}`;
     }
     logger.trace(`commitMessage: ` + JSON.stringify(upgrade.commitMessage));
     if (upgrade.prTitle) {
@@ -222,7 +234,7 @@ export function generateBranchConfig(
         upgrade.prTitle = upgrade.prTitle.toLowerCase();
       }
     } else {
-      upgrade.prTitle = commitMessage.title;
+      [upgrade.prTitle] = upgrade.commitMessage.split(newlineRegex);
     }
     upgrade.prTitle += upgrade.hasBaseBranches ? ' ({{baseBranch}})' : '';
     if (upgrade.isGroup) {