Message overview
Message structure
| Property | Type | Description |
|---|---|---|
| channelType | ChannelType? | Channel type |
| channelId | String? | Channel ID |
| subChannelId | String? | Business identifier for the channel (max 20 characters). Only applies to community channels. |
| messageType | MessageType? | Message type |
| clientId | int? | Unique local message ID (database index) |
| messageId | String? | Unique server message ID (globally unique within an App Key) |
| direction | MessageDirection | Message direction |
| receivedTime | int | Receive time (Unix timestamp in milliseconds) |
| sentTime | int | Send time (Unix timestamp in milliseconds) |
| receivedStatus | ReceivedStatus | Receive status |
| sentStatus | SentStatus | Send status |
| senderUserId | String | Sender user ID |
| metadata | Map? | Metadata key-value pairs. Set them at send time. After sending, they can't be changed through this field. Supported only in direct channels and group channels. Key length ≤ 32, value length ≤ 4096, max 20 pairs per update, max 300 total per message. |
| offLine | bool | Whether this is a missed message. Only valid in the message receive callback. true for missed messages, false otherwise. |
| groupReadReceiptInfo | GroupReadReceiptInfo | Group read receipt status |
| userInfo | UserInfo | User info attached to the message |
| mentionedInfo | MentionedInfo? | Mention (@) information |
| extra | String? | Additional field attached to the message. The receiver can access this data. |
Message types and constructors
The Chat SDK MessageType enum provides predefined types for text, voice, image, and other messages. Not every enum value has a dedicated Flutter constructor. The current Flutter wrapper exposes registered custom messages through CustomMessage and CustomMediaMessage.
By default, all message types are stored locally and counted toward the unread count, except:
- Recall (
MessageType.recall): Stored locally but not counted as unread. - Command (
MessageType.command): Not stored locally, not counted as unread. - Command notification (
MessageType.commandNotification): Stored locally but not counted as unread. - Custom (
MessageType.custom): Reserved enum value. The current Flutter wrapper does not provide a dedicated constructor for this type. - User custom (
MessageType.userCustom): Reserved enum value for app-defined custom handling. - Registered custom (
MessageType.customMessage): Storage controlled byCustomMessagePersistentFlag. - Registered custom media (
MessageType.customMediaMessage): Storage controlled byCustomMessagePersistentFlag.
Use registerCustomMessage() and registerCustomMediaMessage() to register interoperable custom message types before connecting. After registration, send them with CustomMessageParams or CustomMediaMessageParams.
Text message
Type: MessageType.text
| Property | Type | Description |
|---|---|---|
| text | String | Text content |
Build a text message
final channel = DirectChannel('<target-id>');
await channel.sendMessage(SendMessageParams(
messageParams: TextMessageParams(
text: '<message-text>',
),
));
| Parameter | Type | Description |
|---|---|---|
| text | String | Text content |
Voice message
Record voice messages in AAC format with a bitrate of at least 56 kbps for quality and compatibility.
Type: MessageType.voice
| Property | Type | Description |
|---|---|---|
| duration | int | Voice duration in seconds |
Build a voice message
final channel = DirectChannel('<target-user-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: HDVoiceMessageParams(
path: path,
duration: duration,
),
));
| Parameter | Type | Description |
|---|---|---|
| path | String | Local path to the voice file (must be valid) |
| duration | int | Voice message duration |
Image message
Type: MessageType.image
| Property | Type | Description |
|---|---|---|
| thumbnailBase64String | String | Base64 thumbnail data |
| thumWidth | int | Thumbnail width (pixels) |
| thumHeight | int | Thumbnail height (pixels) |
| original | bool | Whether this is the original image |
Build an image message
final channel = DirectChannel('<target-user-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: ImageMessageParams(
path: path,
),
));
| Parameter | Type | Description |
|---|---|---|
| path | String | Local path to the image file (must be valid) |
File message
Type: MessageType.file
| Property | Type | Description |
|---|---|---|
| name | String | File name |
| fileType | String | File type |
| size | int | File size in bytes |
Build a file message
final channel = DirectChannel('<target-user-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: FileMessageParams(
path: path,
),
));
| Parameter | Type | Description |
|---|---|---|
| path | String | Local path to the file (must be valid) |
Short video message
Type: MessageType.sight
| Property | Type | Description |
|---|---|---|
| duration | int | Video duration |
| size | int | Video size |
| name | String | Video name |
| thumbnailBase64String | String | Thumbnail data |
Build a short video message
final channel = DirectChannel('<target-user-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: ShortVideoMessageParams(
path: path,
duration: duration,
),
));
| Parameter | Type | Description |
|---|---|---|
| path | String | Local path to the video file (must be valid) |
| duration | int | Video duration. The server default maximum is 2 minutes. Contact your sales representative to adjust this limit. |
GIF message
Type: MessageType.gif
| Property | Type | Description |
|---|---|---|
| dataSize | int | GIF size in bytes |
| width | int | GIF width |
| height | int | GIF height |
Build a GIF message
final channel = DirectChannel('<target-user-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: GIFMessageParams(
path: path,
),
));
| Parameter | Type | Description |
|---|---|---|
| path | String | Local path to the GIF file |
Combine message
Type: MessageType.combineV2
| Property | Type | Description |
|---|---|---|
| sourceChannelType | ChannelType? | Source channel type of the forwarded messages |
| summaryList | List<String>? | Summary lines shown in the combined message preview |
| nameList | List<String>? | Sender display names included in the combined message |
| msgNum | int? | Number of messages included in the combined message |
| msgList | List<CombineMessageInfo>? | Original message metadata included in the package |
| jsonMsgKey | String? | Key used to retrieve the full combined payload |
Build a combine message
final channel = GroupChannel('<group-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: CombineMessageParams(
sourceChannelType: ChannelType.direct,
summaryList: ['Alice: Hello', 'Bob: Hi'],
nameList: ['Alice', 'Bob'],
msgList: [
CombineMessageInfo(
fromUserId: 'alice',
channelId: 'alice',
timestamp: 1710000000000,
objectName: 'NC:TxtMsg',
content: {'content': 'Hello'},
),
CombineMessageInfo(
fromUserId: 'bob',
channelId: 'bob',
timestamp: 1710000005000,
objectName: 'NC:TxtMsg',
content: {'content': 'Hi'},
),
],
),
));
| Parameter | Type | Description |
|---|---|---|
| sourceChannelType | ChannelType | Source channel type of the combined messages |
| summaryList | List<String> | Preview text shown in the combined message |
| nameList | List<String> | Sender display names in the preview |
| msgList | List<CombineMessageInfo> | Message metadata included in the combined message |
Recall message
Recall messages cannot be constructed manually. Use the recall message API instead.
Type: MessageType.recall
| Property | Type | Description |
|---|---|---|
| admin | bool | Whether the operation was performed by an admin |
| deleted | bool | Whether the message is deleted |
| recallTime | int | Original message send time (milliseconds) |
| recallActionTime | int | Time of the recall action (milliseconds) |
| originalMessage | Message | The original recalled message |
Reference message
Type: MessageType.reference
| Property | Type | Description |
|---|---|---|
| text | String | Reference text |
| referenceMessage | Message | The referenced message |
Build a reference message
final channel = DirectChannel('<target-user-id>');
await channel.sendMessage(SendMessageParams(
messageParams: ReferenceMessageParams(
referenceMessage: referenceMessage,
text: text,
),
));
| Parameter | Type | Description |
|---|---|---|
| referenceMessage | Message | The message to reference |
| text | String | Reference text content |
Location message
Type: MessageType.location
| Property | Type | Description |
|---|---|---|
| longitude | double | Longitude |
| latitude | double | Latitude |
| poiName | String | POI name |
| thumbnailPath | String | Thumbnail path |
Build a location message
final channel = DirectChannel('<target-user-id>');
await channel.sendMessage(SendMessageParams(
messageParams: LocationMessageParams(
longitude: longitude,
latitude: latitude,
poiName: poiName,
thumbnailPath: thumbnailPath,
),
));
| Parameter | Type | Description |
|---|---|---|
| longitude | double | Longitude |
| latitude | double | Latitude |
| poiName | String | POI name |
| thumbnailPath | String | Local thumbnail path (must be valid) |
Command message
Type: MessageType.command
| Property | Type | Description |
|---|---|---|
| name | String | Command name |
| data | String | Command data. Can be any string, such as JSON data. |
The Flutter SDK does not provide a create method for CommandMessage. It only supports receiving this message type. To send command messages from the client, define a wrapper class in the Flutter layer. See How to send command messages in the Flutter SDK.
Command notification message
Type: MessageType.commandNotification
| Property | Type | Description |
|---|---|---|
| name | String | Notification name |
| data | String | Notification data. Can be any string, such as JSON data. |
The Flutter SDK does not provide a create method for CommandNotificationMessage. It only supports receiving this message type. To send command notification messages from the client, define a wrapper class in the Flutter layer. See How to send command messages in the Flutter SDK.
Group notification message
Type: MessageType.groupNotification
Group notification messages are used for group-related operations such as member join, leave, and group dissolution.
| Property | Type | Description |
|---|---|---|
| operation | String | Operation type |
| operatorUserId | String | Operator user ID |
| data | String | Operation-related data |
| message | String | Operation message content |
Group notification messages are typically sent automatically by the server during group operations. The client primarily receives and parses these notifications. The Flutter SDK does not provide a method to create group notification messages.
Registered custom message
Type: MessageType.customMessage
| Property | Type | Description |
|---|---|---|
messageIdentifier | String | Custom message identifier |
searchableWords | List<String>? | Search keywords used for local message search |
fields | Map<String, String> | Key-value pairs |
Build a registered custom message
Register custom messages after initialization but before connecting by calling registerCustomMessage.
Register a custom message
await NCEngine.registerCustomMessage(messageIdentifier, persistentFlag);
| Parameter | Type | Description |
|---|---|---|
| messageIdentifier | String | Unique message identifier |
| persistentFlag | CustomMessagePersistentFlag | Storage policy |
messageIdentifier corresponds to the server-side objectName parameter and uniquely identifies a custom message type. Follow these guidelines:
- Format: Use
prefix:messageType, e.g.,app:txtcontent - Length: Keep it under 16 characters to minimize impact on message size
- Restrictions:
- Do not start with
RC:(reserved for built-in messages) - Do not use built-in message identifiers
- Do not start with
- Examples:
app:order— Order messagecustom:gift— Gift messagemyapp:notice— Notice messageRC:TxtMsg— Incorrect: uses a reserved prefix
Send a registered custom message
final channel = GroupChannel('<group-id>');
await channel.sendMessage(SendMessageParams(
messageParams: CustomMessageParams(
messageIdentifier: messageIdentifier,
fields: fields,
),
));
| Parameter | Type | Description |
|---|---|---|
messageIdentifier | String | Unique message identifier |
fields | Map<String, String> | Key-value pairs for message content |
Registered custom media message
Type: MessageType.customMediaMessage
| Property | Type | Description |
|---|---|---|
fields | Map? | Key-value pairs for message content |
searchableWords | List<String>? | Searchable keywords |
messageIdentifier | String | Unique message identifier |
Build a registered custom media message
Register custom media messages after initialization but before connecting by calling registerCustomMediaMessage.
Register a custom media message
await NCEngine.registerCustomMediaMessage(messageIdentifier, persistentFlag);
| Parameter | Type | Description |
|---|---|---|
| messageIdentifier | String | Unique message identifier |
| persistentFlag | CustomMessagePersistentFlag | Storage policy |
Send a registered custom media message
final channel = GroupChannel('<group-id>');
await channel.sendMediaMessage(SendMediaMessageParams(
messageParams: CustomMediaMessageParams(
messageIdentifier: '<message-identifier>',
path: '<local-media-path>',
fields: {'key': 'value'},
),
));
| Parameter | Type | Description |
|---|---|---|
| messageIdentifier | String | Unique message identifier |
| path | String | Local path to the media file (must be valid) |
| fields | Map | Key-value pairs for message content |
Unknown type
When the SDK receives an unrecognized message, it becomes an unknown type. This type cannot be constructed or sent.
Type: MessageType.unknown
| Property | Type | Description |
|---|---|---|
| rawData | String | Raw message data |
| objectName | String | Message identifier |