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 (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.
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`.
Example fix
// before
const { _id, u, ts, ...rest } = fetchedMessage;
await rc.post('/api/v1/chat.sendMessage', { message: rest }); // rest still has t: 'uj'
// after
const { _id, u, ts, t, ...sendable } = fetchedMessage;
await rc.post('/api/v1/chat.sendMessage', { message: sendable }); Defensive patterns
Strategy: type-guard
Validate before calling
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
const hasSystemType = (m: { t?: unknown }) => typeof m.t === 'string' && SYSTEM_TYPES.has(m.t);
if (hasSystemType(message)) throw new Error('strip message.t before chat.sendMessage');
await rc.post('/api/v1/chat.sendMessage', { message }); Type guard
const isSendableMessage = (m: Record<string, unknown>): m is { rid: string; msg?: string } =>
typeof m.rid === 'string' && m.rid.length > 0 && m.t === undefined; Try / catch
try {
await rc.post('/api/v1/chat.sendMessage', { message });
} catch (e) {
if (/Cannot send system messages/i.test(e?.error ?? '')) {
const { t, ...clean } = message as { t?: string } & Record<string, unknown>;
await rc.post('/api/v1/chat.sendMessage', { message: clean }); // retry once without t
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- error-emoji-param-not-provided
- error-invalid-message
- error-invalid-role
- error-lastUpdate-param-invalid
- error-livechat-visitor-registration
AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18).
Data as JSON: /api/errors/3827f2c644f6f998.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/server/api/v1/chat.ts:920
rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 1000, bypassPermissions: ['send-many-messages'] },
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 e4b8178b20)