XMPP Chat Features
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Chatterbox v1 defines twelve envelope types (see
Message Design and Serialization). Every one of
them already has a standard XMPP counterpart, usually a small XML element added to a message or an iq request. This page
walks through those extensions one at a time. Each section gives the specification and its status, the stanzas on the
wire, the @xmpp/client code that produces and consumes them, and the matching Chatterbox feature. The complete client
that wires them together is on XMPP in the Browser; the servers
and the Docker Compose file are on XMPP Servers; the protocol background is
on XMPP Fundamentals.
Statuses were read from the XEP list on 2026-10-24, and the stanzas were exchanged with ejabberd 26.09 using
@xmpp/client 0.14.0 (Node.js 24). The statuses matter because several extensions that chat clients rely on are still
Experimental, and their wire format can change between versions; where a namespace carries a version suffix
(urn:xmpp:message-retract:1), use the one your server advertises in service discovery.
| Feature | Status | XEP | Chatterbox v1 equivalent |
|---|---|---|---|
Multi-user chat |
Draft |
XEP-0045 (1.35.5) |
|
Message archive |
Draft |
XEP-0313 (1.1.3) |
|
Message carbons |
Draft |
XEP-0280 (1.0.1) |
multi-device sync |
Chat states |
Final |
XEP-0085 (2.1) |
|
Delivery receipts |
Draft |
XEP-0184 (1.4.0) |
|
Displayed markers |
Draft |
XEP-0333 (1.0.0) |
|
Last message correction |
Draft |
XEP-0308 (1.2.1) |
|
Message retraction |
Proposed |
XEP-0424 (0.5.0) |
delete (not in v1) |
Message moderation |
Experimental |
XEP-0425 (0.3.0) |
moderator delete |
Message reactions |
Experimental |
XEP-0444 (0.2.1) |
|
Message replies |
Experimental |
XEP-0461 (0.2.1) |
|
HTTP File Upload |
Draft |
XEP-0363 (1.2.0) |
|
User avatar |
Draft |
XEP-0084 (1.1.5) |
profile picture |
Blocking command |
Draft |
XEP-0191 (1.3) |
block a user |
Push notifications |
Deferred |
XEP-0357 (0.4.1) |
offline push |
Multi-user chat (XEP-0045)
A multi-user chat (MUC) room is addressed as room@conference.chat.example.test, and each participant has an
occupant JID, room@conference.chat.example.test/nickname. The room is a service of the server, a component, so it is
discovered with disco#items on the server (it appeared as conference.chat.example.test).
Joining is a directed presence to the occupant JID with the MUC namespace. The optional history element controls how
much recent history the room replays on join; maxstanzas="0" asks for none, which is what a client does when it
reads history through the archive instead:
<presence to="general@conference.chat.example.test/alice">
<x xmlns="http://jabber.org/protocol/muc">
<history maxstanzas="0"/>
</x>
</presence>
The room answers with the presence of every occupant, then its own presence for the new occupant with status code 110
("this is you"). The first join of a room that does not exist creates it. Depending on the server it stays locked until the owner
submits the configuration form, and an empty submitted form accepts the defaults:
await xmpp.send(xml("presence", { to: `${room}/alice` }, xml("x", { xmlns: "http://jabber.org/protocol/muc" })));
await xmpp.iqCaller.request(xml("iq", { type: "set", to: room },
xml("query", { xmlns: "http://jabber.org/protocol/muc#owner" }, xml("x", { xmlns: "jabber:x:data", type: "submit" }))));
Sending a room message is a message of type="groupchat" addressed to the room (the bare room JID), not to an
occupant; the room reflects it to every occupant, including the sender. Leaving is a directed presence with
type="unavailable".
Roles and affiliations
MUC separates what a person is in the room permanently from what they may do in this session:
| Affiliation (persistent) | Meaning |
|---|---|
|
Configures and destroys the room, assigns other owners and admins. |
|
Manages members and moderators; cannot change owners. |
|
Allowed into a members-only room. |
|
An ordinary visitor of an open room. |
|
Banned. |
| Role (per session) | Meaning |
|---|---|
|
Can kick, grant and revoke voice, and moderate messages. |
|
Can send messages (has "voice") in a moderated room. |
|
Can read but not speak in a moderated room. |
|
Not in the room. |
Affiliations are changed with an iq set to the muc#admin namespace, naming a JID; roles with an item naming a
nickname:
<iq type="set" to="staff@conference.chat.example.test" id="a1">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item affiliation="admin" jid="bob@chat.example.test"/>
</query>
</iq>
<iq type="set" to="staff@conference.chat.example.test" id="r1">
<query xmlns="http://jabber.org/protocol/muc#admin">
<item nick="bob" role="visitor"><reason>cool down</reason></item>
</query>
</iq>
Both requests returned an iq result on ejabberd, and the room broadcast a presence update for the occupant with the new
affiliation or role in the muc#user extension. A role of none kicks the occupant, and an affiliation of outcast
bans the JID. Rooms are also semi-anonymous by default: occupants see nicknames, and only moderators see real JIDs.
Rooms advertise the extra features the server provides in their disco#info; the test room listed
http://jabber.org/protocol/muc#stable_id, urn:xmpp:occupant-id:0 (a stable anonymous identifier for each occupant),
urn:xmpp:mam:2, urn:xmpp:message-retract:1 and urn:xmpp:message-moderate:1.
Source: XEP-0045: Multi-User Chat. In Chatterbox terms a room is the same
room name, join is the join presence, the member list is the occupant presence list, and the moderation roles map to the
moderation features of
History, Offline Delivery and Moderation.
MUC rather than MIX
Mediated Information eXchange (MIX, XEP-0369) was designed as a successor to MUC, built on publish-subscribe and MAM, with channels that a user’s server joins on the user’s behalf. It has stayed at Experimental status (version 0.14.6, last updated in December 2020), it is not part of the XMPP Compliance Suites 2023 (XEP-0479), and few clients and servers implement it. MUC (XEP-0045) therefore remains the room protocol to build on, and every Chatterbox room on this page is a MUC room.
Message archive management (XEP-0313)
MAM stores messages on the server and lets a client query them. It replaces the room history replay and the offline queue
with one mechanism: a query is an iq set whose result is a sequence of message stanzas wrapping the archived stanza in
forwarded, followed by a final iq with a fin element. The page of results is selected with Result Set Management
(RSM, XEP-0059): max limits the page, before (empty for the newest page) or after set the cursor, and the fin
reports first, last and count.
<iq type="set" to="general@conference.chat.example.test" id="m1">
<query xmlns="urn:xmpp:mam:2" queryid="f27">
<set xmlns="http://jabber.org/protocol/rsm"><max>50</max><before/></set>
</query>
</iq>
<!-- one result, repeated for each archived message -->
<message to="alice@chat.example.test/web">
<result xmlns="urn:xmpp:mam:2" queryid="f27" id="1792877206962405">
<forwarded xmlns="urn:xmpp:forward:0">
<delay xmlns="urn:xmpp:delay" stamp="2026-10-24T21:26:46.962405Z"/>
<message type="groupchat" from="general@conference.chat.example.test/alice" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA">
<body>Hello, Chatterbox</body>
</message>
</forwarded>
</result>
</message>
<iq type="result" id="m1">
<fin xmlns="urn:xmpp:mam:2" complete="false">
<set xmlns="http://jabber.org/protocol/rsm"><first>1792877206962405</first><last>1792877219221967</last><count>4</count></set>
</fin>
</iq>
To go further back, repeat the query with <before> set to the first id of the previous page, until complete="true".
To catch up after a disconnect, ask for messages after the last archive id you stored. A one-to-one archive is queried
by sending the iq without a to (the user’s own archive), and filtered with a data form:
const field = (name, value) => xml("field", { var: name }, xml("value", {}, value));
const result = await xmpp.iqCaller.request(xml("iq", { type: "set" },
xml("query", { xmlns: "urn:xmpp:mam:2", queryid: "q1" },
xml("x", { xmlns: "jabber:x:data", type: "submit" },
xml("field", { var: "FORM_TYPE", type: "hidden" }, xml("value", {}, "urn:xmpp:mam:2")),
field("with", "bob@chat.example.test"), field("start", "2026-01-01T00:00:00Z")),
xml("set", { xmlns: "http://jabber.org/protocol/rsm" }, xml("max", {}, "50")))), 15000);
const fin = result.getChild("fin", "urn:xmpp:mam:2");
console.log("complete:", fin.attrs.complete, "next cursor:", fin.getChild("set").getChildText("first"));
Source: XEP-0313: Message Archive Management and
XEP-0059: Result Set Management. The ejabberd run returned a page of two with
count 4 and complete="false". Details to remember:
-
The archive id (the
idofresult) is assigned by the archive, is monotonic, and is also stamped on live messages as a stanza-id (XEP-0359,urn:xmpp:sid:0). It is the XMPP counterpart of the Chatterboxseq: store the last one per room and use it as theaftercursor to catch up. -
Archives hold everything the client sent: edits, reactions and retractions appear as extra messages (a reaction has no
body). The client folds them into the messages they target. -
Archiving must be enabled (
default: alwaysin the ejabberd configuration) and retention limited, because the archive grows without bound.
Message carbons (XEP-0280)
When a user is online with several clients (the browser, a phone), a message sent to the bare JID goes to the resource the server picks, and a message sent from one client is invisible to the others. Carbons copy both directions to the other resources. A client opts in with one request after binding:
await xmpp.iqCaller.request(xml("iq", { type: "set" }, xml("enable", { xmlns: "urn:xmpp:carbons:2" })));
ejabberd answered with an iq result. Carried copies arrive wrapped in sent or received elements containing a
forwarded message; the client must unwrap them and must ignore a carbon whose from is not its own bare JID (spoofing).
Carbons are for one-to-one chats; rooms already deliver to every joined resource. Source:
XEP-0280: Message Carbons. Bind 2 (see the fundamentals page) can enable them
inline in the login. This is Chatterbox’s multi-device sync.
Chat states (XEP-0085)
Chat states are the typing indicators. They are empty elements in the http://jabber.org/protocol/chatstates namespace
added to a message (active, composing, paused, inactive, gone), usually in a body-less message:
<message to="general@conference.chat.example.test" type="groupchat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>
Send composing when the user starts typing, paused after a few seconds of inactivity, and nothing more for a long
while; throttle sends (Chatterbox uses at most one per three seconds) and expire a composing on the receiving side after
a timeout, because the sender may have lost its connection before sending paused. Chat states are not archived by
default. Source: XEP-0085: Chat State Notifications. Maps to typing
(start is composing, stop is paused).
Delivery receipts (XEP-0184)
A sender asks for a receipt by adding request; the receiving client answers with a received message naming the id:
<message to="bob@chat.example.test" type="chat" id="a1">
<body>psst</body>
<request xmlns="urn:xmpp:receipts"/>
</message>
<message to="alice@chat.example.test/web" type="chat">
<received xmlns="urn:xmpp:receipts" id="a1"/>
</message>
In the test, Bob’s client answered received as soon as it handled the message and Alice’s client matched it to the id.
A receipt means the other client got it, which is stronger than the server accepting the stanza (a Stream Management
ack). Receipts are not sent for rooms. Source: XEP-0184: Message Delivery
Receipts. Maps to receipt with kind delivered.
Displayed markers (XEP-0333)
Markers are the read-state companion, and the only way to implement "read" in a room. The sender marks a message
markable; a client reports progress with received, displayed or acknowledged markers:
<message to="general@conference.chat.example.test" type="groupchat" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA">
<body>Hello, Chatterbox</body>
<markable xmlns="urn:xmpp:chat-markers:0"/>
</message>
<message to="general@conference.chat.example.test" type="groupchat">
<displayed xmlns="urn:xmpp:chat-markers:0" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA"/>
</message>
A displayed marker is cumulative: it means "everything up to this message", which is how Chatterbox’s receipt with
upTo works. In a room the marker is reflected to every occupant, so a receipt costs one stanza per reader and per
message; throttle it and send it for the newest visible message only. Source:
XEP-0333: Displayed Markers. Maps to receipt with kind read.
Last message correction (XEP-0308)
Editing sends a new message that carries the old id in a replace element; receivers swap the text of the original and
show an "edited" mark:
<message to="general@conference.chat.example.test" type="groupchat" id="b2">
<body>Hello, Chatterbox!</body>
<replace xmlns="urn:xmpp:message-correct:0" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA"/>
</message>
The room delivered the correction to the other participant, whose client logged the replaced id, and the archive stored
both messages. Rules: only the original sender may correct, and only the last message of a conversation, in the
specification; many clients and servers relax that to any message of the author. Keep the original id as the message’s
identity and treat the correction as a revision; the archive in the test returned the original and the corrected message as two entries. Source:
XEP-0308: Last Message Correction. Maps to edit.
Message retraction and moderation (XEP-0424, XEP-0425)
Retraction lets an author delete a message for everyone, and moderation lets a room moderator do it for someone else with a reason. Both are still young (XEP-0424 Proposed, XEP-0425 Experimental), and their namespaces were bumped when the format changed, so check what the server advertises.
<!-- an author retracts their own message (urn:xmpp:message-retract:1) -->
<message to="staff@conference.chat.example.test" type="groupchat">
<retract xmlns="urn:xmpp:message-retract:1" id="b9a1c2d3"/>
</message>
<!-- a moderator removes someone else's message; the id is the stanza-id of the target -->
<iq type="set" to="staff@conference.chat.example.test" id="mod1">
<moderate xmlns="urn:xmpp:message-moderate:1" id="1792878250097176">
<retract xmlns="urn:xmpp:message-retract:1"/>
<reason>spam</reason>
</moderate>
</iq>
ejabberd 26.09 accepted both: the author’s retraction reached the other occupant carrying the original id, and the
moderator’s iq returned a result while every occupant received a retract element carrying the room-assigned id plus
a moderated child naming the moderator. The server also sent a second copy in an older format (apply-to with
urn:xmpp:fasten:0 and a message-moderate:0 child) for older clients, so a client that listens for the old and the new
format may see two events for one removal; deduplicate on the target id. An author’s retraction names the id the sender chose; the moderator request names the stanza-id (the archive id)
that the room stamped on the target. Remember that a retraction is a request: clients
that already rendered the message hide it, and the server may rewrite or drop the stored copy, but nothing guarantees
that a screenshot or a third-party client forgets it. Sources: XEP-0424:
Message Retraction and XEP-0425: Moderated Message Retraction. This is the
delete feature of the moderation page, absent from Chatterbox v1.
Message reactions (XEP-0444)
A reaction is a message with a reactions element naming the target and the complete current set of emoji of the
sender; to remove a reaction, send the set without it:
<message to="general@conference.chat.example.test" type="groupchat" id="c3">
<reactions xmlns="urn:xmpp:reactions:0" id="01JAB3W7Q8Z4N5R6T7V8X9Y0ZA">
<reaction>👍</reaction>
<reaction>🎉</reaction>
</reactions>
<store xmlns="urn:xmpp:hints"/>
</message>
store is a processing hint asking the server to archive the message even though it has no body. The set-replace
semantics differ from the custom protocol: Chatterbox sends add and remove operations, while XMPP sends the whole set
and the client computes the difference. Rooms deliver a reaction to every occupant, and a client applies it to the target
by id (in the ejabberd test the reaction named the sender-chosen id of the original message and was delivered unchanged). Source:
XEP-0444: Message Reactions. Maps to react.
Message replies (XEP-0461)
A reply references the message it answers with reply, optionally with a fallback that tells older clients which part
of the body is quoted text to hide when the client can render the reply itself:
<message to="staff@conference.chat.example.test" type="groupchat" id="d4">
<body>> bob: spam link
Thanks, removed</body>
<reply xmlns="urn:xmpp:reply:0" to="staff@conference.chat.example.test/bob" id="1792878250097176"/>
<fallback xmlns="urn:xmpp:fallback:0" for="urn:xmpp:reply:0"><body start="0" end="17"/></fallback>
</message>
The room delivered the message and the quoted text intact. The start and end offsets count characters of the body
and mark the quoted prefix; XEP-0461 is Experimental, and Fallback Indication (XEP-0428) is the mechanism behind the
fallback element. Maps to replyTo. Source: XEP-0461: Message Replies.
HTTP File Upload (XEP-0363)
Attachments are not sent through the XMPP stream. The client asks an upload component for a slot, receives a pair of
URLs, uploads the file with an HTTP PUT to the first, and shares the second in the message:
<iq type="get" to="upload.chat.example.test" id="u1">
<request xmlns="urn:xmpp:http:upload:0" filename="note.txt" size="12" content-type="text/plain"/>
</iq>
<iq type="result" id="u1">
<slot xmlns="urn:xmpp:http:upload:0">
<put url="http://localhost:5280/upload/f0a9.../note.txt"><header name="Authorization">placeholder</header></put>
<get url="http://localhost:5280/upload/f0a9.../note.txt"/>
</slot>
</iq>
const slot = (await xmpp.iqCaller.request(xml("iq", { type: "get", to: "upload.chat.example.test" },
xml("request", { xmlns: "urn:xmpp:http:upload:0", filename: file.name, size: String(file.size), "content-type": file.type })))
).getChild("slot", "urn:xmpp:http:upload:0");
const put = slot.getChild("put");
const headers = { "Content-Type": file.type };
for (const h of put.getChildren("header")) headers[h.attrs.name] = h.text(); // copy only the allowed headers
await fetch(put.attrs.url, { method: "PUT", body: file, headers });
// Share the GET URL in the message (out-of-band data, XEP-0066), with the URL also in the body for older clients
const url = slot.getChild("get").attrs.url;
await xmpp.send(xml("message", { to: room, type: "groupchat", id: crypto.randomUUID() },
xml("body", {}, url), xml("x", { xmlns: "jabber:x:oob" }, xml("url", {}, url))));
The slot request on ejabberd returned a PUT URL under /upload, PUT returned 201 and a GET of the second URL
returned the stored content. The service enforces a size limit and a quota (Prosody’s defaults are 10 MiB per file and 100 MiB
per day per user) and a retention time, and the specification says that only the headers Authorization, Cookie and
Expires may be copied from the slot. Validate the size and type on the client before asking for the slot, and see the
file handling in
History, Offline Delivery and Moderation. Source:
XEP-0363: HTTP File Upload. Maps to attachments.
Avatars (XEP-0084 over PEP, XEP-0163)
A user avatar is stored in the user’s own Personal Eventing Protocol (PEP) pub/sub nodes: the image in
urn:xmpp:avatar:data and its description in urn:xmpp:avatar:metadata, with the SHA-1 of the image as the item id.
Contacts subscribe by advertising interest in the node in their capabilities and receive a notification when it changes.
const sha = createHash("sha1").update(png).digest("hex");
await xmpp.iqCaller.request(xml("iq", { type: "set" },
xml("pubsub", { xmlns: "http://jabber.org/protocol/pubsub" },
xml("publish", { node: "urn:xmpp:avatar:data" },
xml("item", { id: sha }, xml("data", { xmlns: "urn:xmpp:avatar:data" }, png.toString("base64")))))));
await xmpp.iqCaller.request(xml("iq", { type: "set" },
xml("pubsub", { xmlns: "http://jabber.org/protocol/pubsub" },
xml("publish", { node: "urn:xmpp:avatar:metadata" },
xml("item", { id: sha }, xml("metadata", { xmlns: "urn:xmpp:avatar:metadata" },
xml("info", { bytes: String(png.length), id: sha, type: "image/png", height: "64", width: "64" })))))));
Both publishes returned a result on ejabberd. Publish the data first, then the metadata, because the metadata announcement is what triggers other clients to fetch. Keep avatars small (a few kilobytes) because the notification fan-out is paid per contact. Source: XEP-0084: User Avatar and XEP-0163: Personal Eventing Protocol. The same PEP mechanism carries bookmarks and OMEMO device lists (see OMEMO Encryption).
Blocking (XEP-0191)
Blocking is a server-side list: blocked JIDs cannot send stanzas to the user. It is two requests plus a retrieval:
await xmpp.iqCaller.request(xml("iq", { type: "set" }, xml("block", { xmlns: "urn:xmpp:blocking" }, xml("item", { jid: "spam@chat.example.test" }))));
const list = await xmpp.iqCaller.request(xml("iq", { type: "get" }, xml("blocklist", { xmlns: "urn:xmpp:blocking" })));
await xmpp.iqCaller.request(xml("iq", { type: "set" }, xml("unblock", { xmlns: "urn:xmpp:blocking" }))); // no items: unblock all
ejabberd listed spam@chat.example.test in the blocklist and accepted the unblock. The user’s other clients are notified
by an iq push, so the block takes effect everywhere. Blocking applies to one-to-one stanzas; blocking someone in a room is
done with room bans (the outcast affiliation). Source: XEP-0191: Blocking
Command.
Push notifications (XEP-0357)
A web page cannot keep a WebSocket open when the browser is closed, so a mobile or Web Push companion needs the server to wake the device. With XEP-0357 the client registers an app server (the push service) and a node, and the XMPP server sends it a small notification, without content, when a message arrives while the client is offline:
<iq type="set" id="p1">
<enable xmlns="urn:xmpp:push:0" jid="push.example.test" node="device-token-placeholder"/>
</iq>
ejabberd answered the request with a result. The push service then delivers to APNs, FCM or, for a web page, a Web Push
endpoint with VAPID (see History, Offline
Delivery and Moderation); the XMPP side only needs the enable request and the server-side module (mod_push in
ejabberd, cloud_notify in Prosody). The specification status is Deferred, but the mechanism is in wide use on mobile
clients and is part of the Mobile Compliance Suite. Source: XEP-0357: Push
Notifications.
Security
-
Transport. Use
wss://and anOriginallow-list in the proxy; ejabberd accepts the WebSocket upgrade from any origin. Authenticate with SCRAM or a token (see XMPP Fundamentals). -
Rooms and spam. A public room invites flooding and unsolicited invitations. Use members-only or moderated rooms when the audience is known, restrict who can create rooms, limit message rates and nickname changes, and keep a ban list (the
outcastaffiliation). Moderators need a reason field and an audit trail for retractions. -
Account enumeration. Open registration and answers to presence subscriptions or room joins can reveal which accounts exist. Rate-limit them and do not return different errors for a missing account and a refused one.
-
Real addresses in rooms. Rooms are semi-anonymous by default, but a non-anonymous room shows every occupant’s real JID to everyone. Say so in the user interface before joining, and use the occupant-id extension instead of nicknames to recognise an occupant.
-
Archive privacy and retention. MAM stores every message on the server. Decide whether the archive is on by default, how long it is kept and who may query a room archive, delete the archive when a user deletes the account, and tell users. A retraction is a request, so a copy may remain in backups and third-party clients.
-
Roster and presence. Presence, avatars and last-activity data go to every subscribed contact, and Carbons copy messages to all of a user’s clients. Show which devices are connected and let the user remove one.
-
File upload. The upload URL is the only protection of the file, so make it unguessable, keep the retention short, and enforce size, quota and type limits in the upload service and the proxy. Serve downloads with a safe content type and
Content-Disposition, scan them if users share files widely, and copy only the three allowed slot headers. -
Notifications. A push payload travels through APNs or FCM; send no message content in it.
-
End-to-end encryption. None of these features encrypts the body from the server. See OMEMO Encryption.
Version notes
-
Compliance. XEP-0479 (Compliance Suites 2023) is the newest published list of the extensions a current client and server are expected to support; it names MUC, MAM, Carbons, HTTP File Upload and the blocking command, and it is the baseline for the table at the top of this page.
-
Rooms. MIX (XEP-0369) is Experimental (0.14.6, 2020) and has no wide deployment, so MUC (XEP-0045) remains the room protocol on current servers and clients.
-
Encryption. OMEMO in XEP-0384 version 0.9.1 uses the namespace
urn:xmpp:omemo:2and is Experimental. It coexists with the legacyeu.siacs.conversations.axolotlnamespace, so a client may need to support both to reach older contacts. -
Push. XEP-0357 is Deferred, but it is widely deployed and part of the Mobile suite.
-
Young extensions. Retraction (XEP-0424, Proposed), moderation (XEP-0425), reactions (XEP-0444) and replies (XEP-0461) are not yet Draft, and the retraction namespace changed (
apply-toandmessage-moderate:0becamemessage-retract:1andmessage-moderate:1). ejabberd 26.09 sends both forms, so read the namespaces indisco#infoand deduplicate. -
Archive namespace. Use
urn:xmpp:mam:2; Carbons useurn:xmpp:carbons:2.
Common errors and pitfalls
-
Sending a room message to the occupant JID. A
groupchatmessage goes to the bare room JID. Sent toroom/nickwithtype="groupchat"it is an error; to send a private message usetype="chat". -
Joining with a nickname that is taken. The room answers with a
conflicterror presence; choose another nickname. -
Applying a correction from anyone. Verify that the correction’s sender is the sender of the original, and, in a room, the same occupant (use the occupant-id extension where the room provides it).
-
Forgetting that history contains corrections, reactions and retractions. Render by folding them into their targets.
-
Treating
deliveredasread. Receipts and markers are different stanzas; a client may implement one and not the other. -
Reactions that overwrite each other. Every reaction message replaces the sender’s whole set for that target.
-
Receipts for every message of every room. A cumulative
displayedfor the newest visible message is enough. -
Assuming the archive is complete. Check
completeinfin, and remember the server enforces limits on the page size. -
Copying every header from an upload slot. Only the three allowed ones.
-
Using a namespace version the server does not advertise. Read
disco#infofor the version before sending.