{"record":{"id":"3827f2c644f6f998","repo":"RocketChat/Rocket.Chat","slug":"cannot-send-system-messages-using-chat-sendmessag","errorCode":null,"errorMessage":"Cannot send system messages using 'chat.sendMessage'","messagePattern":"Cannot send system messages using 'chat\\.sendMessage'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/chat.ts","lineNumber":915,"sourceCode":"\t\t\tauthRequired: true,\n\t\t\tbody: isChatSendMessageProps,\n\t\t\tresponse: {\n\t\t\t\t200: ajv.compile<{ message: IMessage }>({\n\t\t\t\t\ttype: 'object',\n\t\t\t\t\tproperties: {\n\t\t\t\t\t\tmessage: { $ref: '#/components/schemas/IMessage' },\n\t\t\t\t\t\tsuccess: { type: 'boolean', enum: [true] },\n\t\t\t\t\t},\n\t\t\t\t\trequired: ['message', 'success'],\n\t\t\t\t\tadditionalProperties: false,\n\t\t\t\t}),\n\t\t\t\t400: validateBadRequestErrorResponse,\n\t\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t},\n\t\t},\n\t\tasync function action() {\n\t\t\tif (MessageTypes.isSystemMessage(this.bodyParams.message)) {\n\t\t\t\tthrow new Error(\"Cannot send system messages using 'chat.sendMessage'\");\n\t\t\t}\n\n\t\t\tconst sent = await applyAirGappedRestrictionsValidation(() =>\n\t\t\t\texecuteSendMessage(this.user, this.bodyParams.message as Pick<IMessage, 'rid'>, { previewUrls: this.bodyParams.previewUrls }),\n\t\t\t);\n\t\t\tconst [message] = await normalizeMessagesForUser([sent], this.userId);\n\n\t\t\treturn API.v1.success({\n\t\t\t\tmessage,\n\t\t\t});\n\t\t},\n\t)\n\t.get(\n\t\t'chat.ignoreUser',\n\t\t{\n\t\t\tauthRequired: true,\n\t\t\tquery: isChatIgnoreUserProps,\n\t\t\tresponse: {","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/chat.ts#L897-L933","documentation":"Thrown by POST chat.sendMessage when the submitted message object is identified by MessageTypes.isSystemMessage() as a system message (e.g. message-subscribe, room-changed-topic, jut). System messages are server-emitted and must not be injectable via the user-facing send API. Note this uses 'throw new Error(...)' rather than Meteor.Error, so the error code is the message text. Checked at chat.ts:914.","triggerScenarios":"POST /api/v1/chat.sendMessage with a message whose 't' field is set to a system type alias (e.g. t: 'rm' for room name change, t: 'user-muted'), or whose alias resolves to a registered system message type. Also triggered by replaying a captured system message verbatim.","commonSituations":"Client reusing an IMessage object from a subscription payload that already carries a system 't' value; building messages from a template that defaults t to something; importing archived system messages through the wrong endpoint (use a migration script / direct insert instead).","solutions":["Omit the 't' field (or set t: undefined) when constructing the message for chat.sendMessage.","If you genuinely need a system/auto message, use the appropriate server method or an app/bot that emits via the messaging bridge, not chat.sendMessage.","Strip all system-only fields (t, action, system) before forwarding a cloned message object."],"exampleFix":"// before\nawait POST('/api/v1/chat.sendMessage', { message: { rid, msg, t: 'rm' } });\n// after\nawait POST('/api/v1/chat.sendMessage', { message: { rid, msg } });","handlingStrategy":"validation","validationCode":"function buildSendMessage(rid: string, msg: string) {\n  // never set 't' on user-sent messages via chat.sendMessage\n  return { rid, msg };\n}\n\nawait POST('/api/v1/chat.sendMessage', { message: buildSendMessage(rid, text) });","typeGuard":"const isPlainUserMessage = (m: { t?: string }): boolean =>\n  !m.t || m.t === 'o' || m.t === undefined;\n\n// stronger: reject any registered system alias\nconst SYSTEM_T = new Set(['rm','r','ut','ul','au','ad','wm','uj','ut','sub','ru','tc','tn','tm','jp','jl']);\nconst isSystemMessageT = (t?: string): boolean => typeof t === 'string' && SYSTEM_T.has(t);","tryCatchPattern":"try {\n  await POST('/api/v1/chat.sendMessage', { message });\n} catch (e) {\n  if (String((e as any)?.reason ?? e).includes('Cannot send system messages')) {\n    // strip t/action/system fields and retry as a plain message\n    const { t, action, system, ...plain } = message as any;\n    await POST('/api/v1/chat.sendMessage', { message: plain });\n  }\n  throw e;\n}","preventionTips":["Construct outbound messages from a minimal { rid, msg } factory rather than cloning subscription payloads.","Strip t/action/system fields before forwarding any captured message.","For genuine system messages, use a server method or apps-engine bridge, never chat.sendMessage."],"tags":["api","rest","validation","system-messages","security","chat"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}