{"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":920,"sourceCode":"\t\t\trateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 1000, bypassPermissions: ['send-many-messages'] },\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":902,"sourceCodeEnd":938,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/e4b8178b205510181a96ceefee043d0abcd13e5a/apps/meteor/server/api/v1/chat.ts#L902-L938","documentation":"Thrown by POST chat.sendMessage (apps/meteor/server/api/v1/chat.ts:915) when MessageTypes.isSystemMessage(this.bodyParams.message) is true - i.e. the payload's message object carries a `t` field containing a known system-message type (isSystemMessage = t !== undefined && MessageTypes.includes(t), see packages/core-typings/src/IMessage/IMessage.ts:286). System messages (user-joined, message-pinned, subscription-role-added, etc.) are generated by the server itself; clients are not allowed to forge them, so the endpoint throws before executeSendMessage. This is a plain Error, not a Meteor.Error with an error-* code.","triggerScenarios":"POST /api/v1/chat.sendMessage with body {\"message\": {\"rid\": \"...\", \"t\": \"uj\"}} (or rm, message_pinned, subscription-role-added, utc, ...). The body schema does not whitelist message.t, so any recognized system type passes validation and is rejected only here. Often hit by forwarding/cloning real message objects that were originally system messages.","commonSituations":"Bots echoing back message objects fetched via the API without stripping `t`; importers trying to recreate system events ('X joined') as messages; copy-pasting an IMessage from the docs that contains a t field; testing with fixtures seeded from system messages.","solutions":["Strip the `t` field from the message payload before sending: send only { rid, msg (or blocks/attachments), alias, emoji, avatar, tmid, tshow }.","If you actually need a system event to happen, trigger the underlying action (e.g. have the user join the room via their own flow) instead of forging the message.","Sanitize forwarded/cloned message objects: destructure out _id, t, u, ts, edits, and server-only fields before reuse.","Validate outgoing payloads client-side with a type guard that rejects any message containing `t`."],"exampleFix":"// before\nconst { _id, u, ts, ...rest } = fetchedMessage;\nawait rc.post('/api/v1/chat.sendMessage', { message: rest }); // rest still has t: 'uj'\n\n// after\nconst { _id, u, ts, t, ...sendable } = fetchedMessage;\nawait rc.post('/api/v1/chat.sendMessage', { message: sendable });","handlingStrategy":"type-guard","validationCode":"const SYSTEM_TYPES = new Set(['uj', 'ul', 'ru', 'au', 'rm', 'ut', 'subscription-role-added', 'subscription-role-removed', 'room-archived', 'room-unarchived', 'message_pinned', 'rtc', 'utc']); // non-exhaustive: any registered MessageTypes value counts\nconst hasSystemType = (m: { t?: unknown }) => typeof m.t === 'string' && SYSTEM_TYPES.has(m.t);\nif (hasSystemType(message)) throw new Error('strip message.t before chat.sendMessage');\nawait rc.post('/api/v1/chat.sendMessage', { message });","typeGuard":"const isSendableMessage = (m: Record<string, unknown>): m is { rid: string; msg?: string } =>\n  typeof m.rid === 'string' && m.rid.length > 0 && m.t === undefined;","tryCatchPattern":"try {\n  await rc.post('/api/v1/chat.sendMessage', { message });\n} catch (e) {\n  if (/Cannot send system messages/i.test(e?.error ?? '')) {\n    const { t, ...clean } = message as { t?: string } & Record<string, unknown>;\n    await rc.post('/api/v1/chat.sendMessage', { message: clean }); // retry once without t\n  } else throw e;\n}","preventionTips":["When cloning/forwarding fetched IMessage objects, whitelist fields (rid, msg, blocks, attachments, alias, emoji, avatar, tmid, tshow) instead of blacklisting.","Never seed test fixtures from real system messages without stripping t, _id, u, ts."],"tags":["rocket-chat","rest-api","send-message","system-message","payload-validation","chat-sendmessage"],"backgroundTag":"unsupported-message-type","analyzedSha":"e4b8178b205510181a96ceefee043d0abcd13e5a","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}