RocketChat/Rocket.Chat · error · Error
Cannot send system messages using 'chat.sendMessage'
Error message
Cannot send system messages using 'chat.sendMessage'
What it means
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.
Source
Thrown at apps/meteor/server/api/v1/chat.ts:915
authRequired: true,
body: isChatSendMessageProps,
response: {
200: ajv.compile<{ message: IMessage }>({
type: 'object',
properties: {
message: { $ref: '#/components/schemas/IMessage' },
success: { type: 'boolean', enum: [true] },
},
required: ['message', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
if (MessageTypes.isSystemMessage(this.bodyParams.message)) {
throw new Error("Cannot send system messages using 'chat.sendMessage'");
}
const sent = await applyAirGappedRestrictionsValidation(() =>
executeSendMessage(this.user, this.bodyParams.message as Pick<IMessage, 'rid'>, { previewUrls: this.bodyParams.previewUrls }),
);
const [message] = await normalizeMessagesForUser([sent], this.userId);
return API.v1.success({
message,
});
},
)
.get(
'chat.ignoreUser',
{
authRequired: true,
query: isChatIgnoreUserProps,
response: {View on GitHub (pinned to f9d3ec372b)
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.
Example fix
// before
await POST('/api/v1/chat.sendMessage', { message: { rid, msg, t: 'rm' } });
// after
await POST('/api/v1/chat.sendMessage', { message: { rid, msg } }); Defensive patterns
Strategy: validation
Validate before calling
function buildSendMessage(rid: string, msg: string) {
// never set 't' on user-sent messages via chat.sendMessage
return { rid, msg };
}
await POST('/api/v1/chat.sendMessage', { message: buildSendMessage(rid, text) }); Type guard
const isPlainUserMessage = (m: { t?: string }): boolean =>
!m.t || m.t === 'o' || m.t === undefined;
// stronger: reject any registered system alias
const SYSTEM_T = new Set(['rm','r','ut','ul','au','ad','wm','uj','ut','sub','ru','tc','tn','tm','jp','jl']);
const isSystemMessageT = (t?: string): boolean => typeof t === 'string' && SYSTEM_T.has(t); Try / catch
try {
await POST('/api/v1/chat.sendMessage', { message });
} catch (e) {
if (String((e as any)?.reason ?? e).includes('Cannot send system messages')) {
// strip t/action/system fields and retry as a plain message
const { t, action, system, ...plain } = message as any;
await POST('/api/v1/chat.sendMessage', { message: plain });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- error-emoji-param-not-provided
- error-param-required
- error-lastUpdate-param-invalid
- error-roomId-param-not-provided
- error-searchText-param-not-provided
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/3827f2c644f6f998.
Report an issue: GitHub.