Go SDK messages
This page covers sending and receiving messages in the Go SDK, plus recall, delete, reactions, pinned messages, read status and typing indicators. For the conversation list and unread counts, see Conversations; for uploading files, see Files and images; for how events and the Store work, see Events and errors.
Where a message goes is always a Target, built with im.ToUser or im.ToGroup (exactly one):
im.ToUser("bob") // 发给用户 bob
im.ToGroup(groupID) // 发到一个群Below, c is the client from Initialization, sign-in and connection, and ctx is a context.Context.
| You want to | See |
|---|---|
| Send text or custom messages | Send text, Send custom messages |
| Mention someone or quote a reply | Mentions, Quote replies |
| Send images or files | Files and images |
| Receive messages and reply automatically | Receive messages |
| Mark read and send typing indicators | Read status and typing |
| Recall, delete, react, pin | Managing messages |
| Parse the body of each message type | Message types |
Send messages
| Method | Use | Key parameters |
|---|---|---|
SendText | Send a text message | text: the text; ext: extension fields |
Send | Send a message of any type | msgType: the type; body: the full message body JSON; onlineOnly: push to online devices only |
SendMedia | Send a media message | msgType: image, voice, video or file; info: the *FileInfo returned by upload |
SendReply | Quote reply | replySeq: seq of the quoted message |
SendWithMentions | Send a message with mentions | usernames: who to mention; all: mention everyone; exactly one of the two |
All return (*im.Message, error). For types and body structures, see Message types below.
Send text
m, err := c.SendText(ctx, im.ToUser("bob"), "你好", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(m.ConversationID, m.Seq)extis your own extension fields (map[string]any), stored and returned as is; good for things like business IDs. Passnilwhen you don't need it.- You can send without a conversation: when you send a user the first message, the server creates the one-to-one conversation.
Send custom messages
Send takes the full message body JSON ([]byte) as body, interpreted according to msgType. SendText is the same as calling it with type "text" and body {"text": ...}:
body, _ := json.Marshal(map[string]any{"card": "order", "order_id": "8812"})
m, err := c.Send(ctx, im.ToGroup(groupID), "custom", body, nil, false)- An empty
msgTypeis treated astext; anilbodysends an empty object. - The SDK generates
clientMsgIDautomatically (a random hex string); the send methods don't accept your own. The server uses it to de-duplicate repeated calls. - When the last parameter,
onlineOnly, istrue, the message isn't stored and only goes to online devices (customonly).
onlineOnly and the connection
While the connection is up, messages go over WS, and the online_only flag isn't sent; it only takes effect when sending falls back to REST. For real-time signals that must be online-only, make sure the connection is down when sending, or use another channel.
Mentions
Group messages can mention members or everyone. Put the "@Name" text into the body yourself; the SDK only submits mentions (exactly one of usernames and all):
// @ 指定成员
m, err := c.SendWithMentions(ctx, im.ToGroup(groupID), "text",
[]byte(`{"text":"@李四 请确认"}`),
[]string{"lisi"}, false, nil)
// @ 全体成员
c.SendWithMentions(ctx, im.ToGroup(groupID), "text",
[]byte(`{"text":"@所有人 明天放假"}`),
nil, true, nil)Mentioned users see "Someone mentioned you" through MentionSeq in the conversation state; see Conversations.
Quote replies
// 回复该群第 120 条消息
m, err := c.SendReply(ctx, im.ToGroup(groupID), 120, "text", []byte(`{"text":"同意"}`), nil)The sent message's ReplyTo is {seq, sender}. Recipients find the quoted message in the conversation by seq, and fetch it with MessageLookup if it isn't local; see below.
How messages are sent, and the local fallback
- While the connection is up, sending goes over WS (
message.send) for low latency. If the connection has just dropped (unavailable,timeout), it falls back to REST automatically; business errors are returned as is. - On success you get the server's message (with
Seq,MessageIDandCreatedAt), and it's written to the Store. - The server doesn't echo
message.receivedback to the sender, and doesn't always push conversation events to the sender for new conversations. So after a successful send, the SDK creates or advances the conversation state locally, making sure your own messages appear in the conversation list and your own read status has somewhere to land. That means don't wait formessage.receivedto add your message to the UI: the send method's return value is the message.
Receive messages
Messages from others arrive as message.received events. Each event is applied to the Store first, then your handler is called:
c.OnEvent(func(e im.Event) {
if e.Name != im.EventMessageReceived {
return
}
var m im.Message
if err := json.Unmarshal(e.Data, &m); err != nil {
return
}
if m.Type == "text" {
fmt.Println(m.Sender, m.TextBody())
}
})- You usually don't keep your own message list: in the callback, read the Store (
c.Store().Messages(conversationID), ascending byseq) and rebuild the UI after each event. - When messages arrive out of order, the SDK accepts them and fills the gap in the background. When
cleared_seqgrows, local messages up to it are removed automatically. For the rules, see the Store section of Events and errors. - Recalls, edits, reactions and pin changes update the message in the Store live through
message.changed.
Managing messages
| Method | Use | Key parameters |
|---|---|---|
Recall | Recall a message | conversationID, seq; only succeeds within the time limit and with permission |
DeleteMessages | Delete messages (for the user only) | seqs: the seqs to delete |
AddReaction / RemoveReaction | Add or remove a reaction | seq; key: the reaction key |
PinMessage / UnpinMessage | Pin or unpin a message | conversationID, seq |
MessageLookup | Get messages by seq in a batch | seqs: up to 100 at a time |
Read | Mark a conversation read | seq: read up to this message; 0 means up to the latest |
Typing | Send a typing indicator | conversationID |
Read status and typing
// 用户正在看这个会话:已读位置推进到最新一条
_ = c.Read(ctx, convID, 0)
// 只标记到某一条(如用户只看到了第 120 条)
_ = c.Read(ctx, convID, 120)
// 输入框每次变化时报告;长连接未连上时静默忽略
c.Typing(ctx, convID)Readalso prefers WS and falls back to REST. On success it advances the conversation'sReadSeqandUnreadCountin the Store.- In one-to-one chats the other person's read position is
ConversationState.PeerReadSeq, updated live bymessage.peer_readevents; see "Read receipts" in Conversations. - Typing indicators you receive go into the Store through
message.typingevents.c.Store().Typing(convID)returns the latest one from the last 10 seconds and expires it automatically.
Reactions
convID := m.ConversationID
seq := m.Seq
_ = c.AddReaction(ctx, convID, seq, "thumbs_up")
_ = c.RemoveReaction(ctx, convID, seq, "thumbs_up")- Each reaction's totals (how many, the first 3 people, and whether the user reacted) are in the message's
Reactions, updated live throughmessage.changedwhen others react:
for _, r := range m.Reactions {
fmt.Println(r.Key, r.Count, r.Users, r.Reacted)
}- To list everyone who reacted with a key, page through
ReactionUsers; see "Reaction users" in Conversations. - The client defines reaction
keys (such asthumbs_uporheart); keep them the same on every platform.
Recall, delete, pin and look up
_ = c.Recall(ctx, convID, 120) // 撤回
_ = c.DeleteMessages(ctx, convID, []int64{101, 102}) // 只删本人视角
_ = c.PinMessage(ctx, convID, 120) // 置顶,UnpinMessage 取消
msgs, _ := c.MessageLookup(ctx, convID, []int64{3, 57}) // 按 seq 取回- After a recall the message becomes recalled for everyone:
Recalledis{by, role, at}, and the body and extension fields are cleared. It updates throughmessage.changed. DeleteMessagesonly affects the user; their other devices delete the messages throughmessage.deletedevents.- When a message mentioned by a quote, a pin or a notification isn't local, fetch it from the server with
MessageLookup; the results are also written to the Store.
Message types
Message.Body (json.RawMessage) is interpreted by Type:
| Type | Body structure | How to read it |
|---|---|---|
text | {"text": "..."} | m.TextBody(); returns an empty string for non-text messages |
image | The FileInfo JSON returned by upload: url, thumbnail_url, width, height and so on | json.Unmarshal(m.Body, &info) into im.FileInfo |
voice | FileInfo, plus duration_ms | Same as above |
video | FileInfo | Same as above |
file | FileInfo | Same as above |
custom / tip | Your own JSON object | m.BodyMap(); returns nil if parsing fails |
The url of image, voice, video and file messages can't be requested directly. Exchange it for a download link first, or use Download; see Files and images.
Types
Message
| Field | Type | Description |
|---|---|---|
ConversationID | string | Conversation ID |
ConversationType | string | single or group |
Seq | int64 | Number within the conversation |
MessageID | string | Message ID, unique across the app |
ClientMsgID | string | De-duplication ID generated by the sender |
Sender | string | The sender's username |
SenderType | string | user or system |
Recipient | string | The recipient in a one-to-one chat |
Type | string | Message type; see the table above |
Body | json.RawMessage | Message body, interpreted by type |
Ext | map[string]any | Extension fields given when sending |
Mentions | *Mentions | Who is mentioned: Usernames, All |
ReplyTo | *ReplyTo | The quoted message: Seq, Sender |
Reactions | []Reaction | Reaction totals: Key, Count, Users, Reacted |
Pinned | *Pinned | Pin info: By, At |
Edited | *Edited | Edit info: At, Count |
Recalled | *Recalled | Recall info: By, Role, At |
Erased | bool | Whether the content was erased |
Via | string | How it was written: client, openapi, console, system |
CreatedAt | string | Server send time (RFC 3339) |
MessageVersion | int64 | Change version, for telling newer from older |
Helper methods: TextBody() returns the text; BodyMap() parses the body into a generic object. For *im.APIError and im.IsCode, see Events and errors.
See also
- Conversations: the conversation list, unread counts and read receipts.
- Files and images: upload files and send them with
SendMedia. - Events and errors:
message.*events, how the Store is maintained, and error codes.
