diff --git a/README.md b/README.md
index 839ba3e489296ca830685ba812ab61314fc3c271..ad580115223f10535af040245928e7f8892609c5 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ A Gitlab bot for Matrix. It uses
 	* [x] Read issues
 	* [x] Create/close/reopen issues
 	* [ ] Read comments on issues
-	* [ ] Comment on issues
+	* [x] Comment on issues
 
 ## Usage
 Configure the server by copying `example-config.json` to `config.json` and
diff --git a/commands.go b/commands.go
index 9af74b766ca9be16d2df870fd8acef943ecbf8e7..5d5edc9444b66994080efe7a328942bbe0d514fe 100644
--- a/commands.go
+++ b/commands.go
@@ -265,6 +265,28 @@ func commandCreateIssue(git *gitlab.Client, room *mautrix.Room, sender string, a
 	}
 }
 
+func commandCommentOnIssue(git *gitlab.Client, room *mautrix.Room, sender string, args []string, lines []string) {
+	if len(args) < 2 {
+		room.Send("Usage: !gitlab issue comment <repo> <issue id> [\\n] <body>")
+		return
+	}
+
+	issueID, err := strconv.Atoi(args[1])
+	if err != nil {
+		room.Sendf("Invalid issue ID: %s", args[1])
+	}
+
+	var body string
+	if len(args) > 2 {
+		body = strings.Join(args[2:], " ") + "\n"
+	}
+	body += strings.Join(lines, "\n")
+
+	git.Notes.CreateIssueNote(args[0], issueID, &gitlab.CreateIssueNoteOptions{
+		Body: &body,
+	})
+}
+
 func commandIssue(git *gitlab.Client, room *mautrix.Room, sender string, args []string, lines []string) {
 	if len(args) == 0 {
 		room.SendHTML("Unknown subcommand. Try <code>!gitlab help issue</code> for help.")
@@ -310,6 +332,7 @@ func commandIssue(git *gitlab.Client, room *mautrix.Room, sender string, args []
 			room.Sendf("Failed to %s issue: %s", subcommand, err)
 		}
 	case "comment":
+		commandCommentOnIssue(git, room, sender, args, lines)
 	case "read-comments":
 	default:
 		room.SendHTML("Unknown subcommand. Try <code>!gitlab help issue</code> for help.")