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

  1. Omit the 't' field (or set t: undefined) when constructing the message for chat.sendMessage.
  2. 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.
  3. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/3827f2c644f6f998. Report an issue: GitHub.