XMPP From Zero to Production Book A Complete Developer's Guide Build real-time messaging that survives 2G — from your first stanza to a clustered Ejabberd deployment. Running example throughout: MaasaiChat , a chat app for Maasai communities across rural Tanzania and Kenya — 2G/3G links, low-end Android phones, connections that drop constantly. Every technical decision in this book is shaped by that reality. First Edition · 2026 Written for backend engineers, mobile developers, and architects who need to own their messaging stack. No prior XMPP knowledge assumed. By the final chapter you will have deployed a production Ejabberd server, implemented every XEP MaasaiChat needs on Android and iOS, and hardened it for real traffic. How to use this book Part 1 teaches the protocol from absolute zero. Read it in order. Part 2 is a reference — one chapter per XEP, all built on the same six-part template. Read the Core and Messaging chapters, then dip into the rest as you need them. Part 3 is hands-on Ejabberd. Keep a terminal open. Part 4 is the mobile SDK guide (Android Smack, iOS XMPPFramework). Part 5 is production: security, database, scaling, network tuning, pitfalls. Every XEP chapter answers the same six questions in the same order: 1. What problem does it solve? 2. The XML stanzas, explained line by line 3. A real MaasaiChat example 4. How Ejabberd handles it 5. What the mobile dev implements 6. What the backend dev implements Every chapter ends with a What you learned box and a checkpoint before the next one. Conventions: terminal commands are shown in fenced blocks, XML stanzas are complete and copy-pasteable, and the four MaasaiChat users appear in every example: ole@maasaichat.com Ole Saitoti — elder naserian@maasaichat.com Naserian — young warrior enkiama@maasaichat.com Enkiama — village chief nkeri@maasaichat.com Nkeri — cattle trader Table of Contents Part 1 — Foundation 1 What is XMPP? 2 The XMPP Vocabulary — Key Terms & Definitions 3 XML Basics for XMPP 4 The JID Address System 5 The Three Stanzas 6 Namespaces & XEPs Explained 7 The XMPP Stream Lifecycle Part 2 — XEPs Complete Reference Core 8 XEP-0030 Service Discovery 9 XEP-0115 Entity Capabilities 10 XEP-0199 XMPP Ping 11 XEP-0198 Stream Management (critical for 2G/3G) 12 XEP-0280 Message Carbons 13 XEP-0313 Message Archive Management (MAM) Messaging 14 XEP-0085 Chat State Notifications 15 XEP-0184 Message Delivery Receipts 16 XEP-0333 Chat Markers 17 XEP-0308 Last Message Correction 18 XEP-0424 Message Retraction 19 XEP-0444 Message Reactions 20 XEP-0297 Stanza Forwarding 21 XEP-0461 Message Replies 22 XEP-0359 Stable & Unique Stanza IDs 23 XEP-0334 Message Processing Hints Group Chat 24 XEP-0045 Multi-User Chat (MUC) 25 XEP-0249 Direct MUC Invitations 26 XEP-0317 Hats 27 XEP-0425 Message Moderation 28 XEP-0490 Message Displayed Synchronization File & Media 29 XEP-0363 HTTP File Upload (the one you'll actually use) 30 XEP-0065 SOCKS5 Bytestreams 31 XEP-0234 Jingle File Transfer 32 XEP-0264 Jingle Content Thumbnails Calls 33 XEP-0166 Jingle 34 XEP-0167 Jingle RTP Sessions 35 XEP-0176 Jingle ICE-UDP Transport 36 XEP-0177 Jingle Raw UDP Transport 37 XEP-0215 External Service Discovery (TURN/STUN credentials) 38 XEP-0320 DTLS-SRTP in Jingle Push & Notifications 39 XEP-0357 Push Notifications (FCM on Android) Security & Encryption 40 XEP-0384 OMEMO Encryption 41 XEP-0388 Extensible SASL Profile 42 XEP-0440 SASL Channel Binding Presence & Roster 43 XEP-0054 vcard-temp 44 XEP-0153 vCard-Based Avatars 45 XEP-0292 vCard4 Over XMPP 46 XEP-0083 Nested Roster Groups 47 XEP-0144 Roster Item Exchange PubSub 48 XEP-0060 Publish-Subscribe 49 XEP-0163 Personal Eventing Protocol (PEP) History & Archive 50 XEP-0059 Result Set Management 51 XEP-0313 MAM in depth (querying, paging) 52 XEP-0430 Inbox Enterprise 53 XEP-0050 Ad-Hoc Commands 54 XEP-0004 Data Forms 55 XEP-0055 Jabber Search 56 XEP-0077 In-Band Registration 57 XEP-0133 Service Administration Federation 58 XEP-0220 Server Dialback 59 XEP-0288 Bidirectional Server-to-Server Part 3 — Ejabberd in Practice 60 Architecture & the Erlang/BEAM Foundation 61 Docker Setup, Step by Step 62 ejabberd.yml — The Complete Guide 63 ejabberdctl — Every Command You'll Use 64 REST API — Complete Reference 65 OAuth Authentication 66 The Auth Bridge (HTTP auth → your backend as gatekeeper) 67 Clustering Two Nodes 68 MUC Administration 69 Monitoring & Logging Part 4 — Mobile SDK Guide 70 Android with Smack — connect, auth, send/receive, all XEPs, custom stanzas 71 iOS with XMPPFramework — same coverage Part 5 — Production 72 Security Hardening 73 PostgreSQL Backend 74 Scaling Path 75 East African Network Optimization 76 Common Pitfalls 77 Monitoring Setup Part 1 — Foundation Chapter 1 — What is XMPP? 1.1 The one-sentence answer XMPP is an open protocol for sending small pieces of XML from one address to another, in real time, over a long-lived connection. That's it. Everything else in this book is detail on top of that sentence. Each word was chosen deliberately: Open protocol — nobody owns it. It's an IETF standard (RFC 6120 for the core, RFC 6121 for instant messaging and presence). You don't ask permission or pay a license. Contrast this with WhatsApp's protocol, which is closed — you cannot legally build a server that speaks it. Small pieces of XML — the unit of communication is a stanza , a short XML fragment. A single chat message is a few hundred bytes. From one address to another — every user and service has an address called a JID (Jabber ID). Routing is built into the protocol. In real time — messages arrive the instant they're sent, not when the client next polls. Over a long-lived connection — the phone opens one TCP connection to the server and keeps it open for hours. Messages flow both ways over that single pipe. XMPP originally stood for eXtensible Messaging and Presence Protocol . It started in 1999 under the name Jabber , created by Jeremie Miller. You'll still see "Jabber" everywhere — in the JID name, in library names, in old docs. Treat "Jabber" and "XMPP" as the same thing. 1.2 Why a long-lived connection matters (and why HTTP doesn't fit) To understand XMPP, understand the problem it was built to avoid. The web runs on HTTP, which is request/response . The client asks, the server answers, the connection closes. Perfect for loading a web page. Terrible for chat, because chat is server-initiated : the server needs to push a message to you the moment someone sends it, and it has no idea when that will be. There are three ways to force chat onto HTTP, and all three are bad on a rural network: Approach How it works Problem on 2G/3G -------------------------------------------------------------------------- Short polling Ask "any messages?" every 3s Wastes battery + data, message lag up to 3s Long polling Ask, server holds request open Constant reconnects, until a message arrives, repeat each carries TCP+TLS cost WebSocket over One socket, but you build the You reinvent routing, raw HTTP entire chat protocol yourself presence, offline, MUC... XMPP solves this at the protocol level. The phone opens one connection, authenticates once , then both sides send stanzas whenever they want. No repeated handshakes. No polling. On a 2G link where a new TCP+TLS handshake costs several round-trips over 300–800 ms latency, "connect once and stay connected" is the difference between a usable app and an unusable one. MaasaiChat phone maasaichat.com server (Naserian's Tecno) (Ejabberd) | | |------ open TCP connection -------------->| |<----- keep it open, both directions -----| | | | message to ole@maasaichat.com ------->| routes it | | |<------ message from enkiama@... ---------| pushed instantly | | | (connection stays open | | for hours) | One pipe. Messages flow both directions. That's the core idea. 1.3 What actually travels down the pipe Once connected, the phone and server exchange stanzas . There are exactly three kinds (Chapter 4 goes deep). For now, just see them. A chat message from Naserian to the elder Ole: Elder, the cattle are safe at the river. Naserian telling the server she is online: chat Herding near Ngorongoro Naserian asking the server a question and expecting an answer: Three stanza types — message , presence , iq — and everything XMPP does is one of those three, possibly with extra XML tucked inside. That extra XML is what a XEP is, and it's why XMPP can grow reactions, calls, and file upload without ever changing the core (Chapter 5). 1.4 XMPP is the rulebook, Ejabberd is the builder Here is the single most common confusion for newcomers, and clearing it up now makes the rest of the book effortless: XMPP and Ejabberd are not the same thing, and one is not "inside" the other. XMPP is a specification — a set of documents written by the XMPP Standards Foundation. It is pure guidance. It says things like "a chat message must have a element," "addresses look like user@domain/resource ," "a typing indicator uses this namespace." That's all it is. Rules on paper. The XMPP spec, by itself, cannot route a single message — in the same way a rulebook cannot play the game. Ejabberd is a builder that read those rules and wrote the code. The team at ProcessOne read every relevant document and implemented it in Erlang, producing an actual running server that obeys the rules. Ejabberd is the thing that accepts connections, routes stanzas, and stores offline messages. The cleanest way to hold this: Building code (the rules) The builder (follows the rules) ------------------------- ------------------------------ "walls must be 30cm thick" --> reads the code, "doors must be 2m high" actually builds the house, "foundation must be concrete" following every instruction The building code is just paper. The house is real and you It builds nothing by itself. can live in it. XMPP = the building code Ejabberd = the builder Developers already know this pattern from tools they use every day: The language / rules The thing that speaks it -------------------- ------------------------ HTTP --> Nginx, Apache, Tomcat SQL --> PostgreSQL, MySQL XMPP --> Ejabberd, Prosody, Openfire Nobody says "HTTP is inside Nginx." HTTP is the rulebook; Nginx is a program that follows it. XMPP and Ejabberd relate the exact same way. The chain: who writes the rules, who builds Because XMPP is open guidance, many different teams read the same documents and each build their own piece — and because they all follow the same rules, all the pieces interoperate: XMPP Standards Foundation writes the guidance (the XEPs) │ "delivery receipts work like THIS" ▼ ProcessOne reads it, writes Erlang → Ejabberd (server) Gajim team reads it, writes Python → Gajim (desktop client) Smack team reads it, writes Java → Smack (Android library) sendxmpp author reads it, writes Perl → sendxmpp (CLI tool) Different code. Same guidance. They all understand each other. ✅ This is why the Gajim desktop app on Ole's laptop, the Smack-powered MaasaiChat app on Naserian's Tecno, and a sendxmpp script on a server can all exchange messages through Ejabberd without anyone coordinating — they are all following the same rulebook. And it's the reason you write almost no protocol code yourself. Ejabberd is already built. Smack is already built. You read the XEPs to understand , then use those implementations, and only write custom code for MaasaiChat's own extensions (its own namespaces for things the standard doesn't cover). The 25-year-old rulebook, and the battle-tested builders who followed it, do the rest. You (MaasaiChat) read XEPs to understand the rules use Ejabberd (server, already built) use Smack (Android, already built) write custom code ONLY for your own app-specific extensions ✅ Catch it in one line: XMPP is the language, Ejabberd speaks it, Gajim and Smack also speak it — everyone understands each other because everyone follows the same rulebook. 1.5 Who uses XMPP today XMPP is not a museum piece. It quietly runs a large chunk of the messaging world: WhatsApp was built on a heavily modified fork of Ejabberd — the exact server this book deploys. The protocol you're learning is the ancestor of the app two billion people use. Nintendo Switch uses XMPP/Ejabberd for online chat and presence. Zoom acquired the XMPP-based team-chat product now sold as Zoom Team Chat. Google Talk was XMPP for its entire life and even federated with outside XMPP servers. Jitsi, HCL Sametime, and many carrier, ISP, and government messaging systems run on XMPP today. The common thread: when an organization needs self-hosted, standards-based, massively scalable real-time messaging that they fully control, XMPP keeps being the answer. That is exactly MaasaiChat's position — you cannot depend on someone else's servers for a community tool in rural Tanzania and Kenya, so you run your own. 1.6 The messaging landscape — XMPP and its alternatives XMPP is not the only way to build a chat app. Before committing, you should know what else exists, who runs on it, and the honest trade-offs. There are six real families of choice. 1. XMPP (this book) Open IETF standard. Servers: Ejabberd (what we use), Prosody, Openfire. Clients everywhere. Pros Cons -------------------------------------------------------------- Open standard, no vendor lock-in Learning curve — it's a real Self-hosted, you own the data protocol with real depth Routing, presence, offline, MUC, XML is verbose vs binary archive, push are built in (mitigated by compression) Federation across servers Some XEPs are optional/uneven Proven to millions of connections across servers Free (Ejabberd Community) You assemble the client stack Who uses it: WhatsApp (originally), Nintendo Switch, Google Talk, Jitsi. 2. Matrix The main modern open-standard rival. Server: Synapse (also Dendrite, Conduit). Client: Element . Instead of XMPP's live XML stream, Matrix syncs a replicated JSON event graph over HTTP — every message is an event, and history is a shared, eventually-consistent room state. Pros Cons -------------------------------------------------------------- Open standard, federated Heavier — Synapse is resource- Strong built-in E2E encryption hungry vs Ejabberd JSON over HTTP — familiar to devs Sync model uses more bandwidth, Great for team/community chat worse fit for strict 2G budgets Rich ecosystem, bridges galore Younger, protocol still evolving Who uses it: the French government (Tchap), the German armed forces (BwMessenger), Mozilla , KDE , and many privacy-focused communities. It's the serious open alternative — but its "replay the room's event history" model is more bandwidth-hungry than XMPP's lean stanza stream, which matters when your users pay per megabyte on 2G. That single fact is a large part of why MaasaiChat chooses XMPP over Matrix. 3. MQTT A lightweight publish/subscribe protocol from the IoT world. Broker: Mosquitto , EMQX, HiveMQ. Pros Cons -------------------------------------------------------------- Extremely lightweight wire format Not a chat protocol — no roster, Tiny overhead, ideal for low presence, offline history, MUC bandwidth / battery You build ALL chat semantics Great pub/sub fan-out on top yourself Simple to reason about No federation, no identity model Who uses it: sensors, cars, smart devices — and famously Facebook Messenger , which used MQTT for years to get fast, low-overhead delivery on poor mobile networks. MQTT is a fantastic transport , but it gives you a pipe, not a chat system. You'd rebuild everything XMPP already provides. (Note: Ejabberd itself speaks MQTT natively, so you can even use both.) 4. Proprietary binary protocols The big consumer apps mostly rolled their own closed protocols: WhatsApp — started on Ejabberd/XMPP, later moved to a custom binary protocol using the Noise Protocol Framework for its handshake, with the Signal Protocol for end-to-end encryption. Signal — the Signal Protocol over its own service; the gold standard for E2E encryption. Telegram — its own MTProto protocol. Discord — a custom WebSocket gateway protocol, backed by Elixir/Erlang (the same platform Ejabberd runs on). Pros Cons -------------------------------------------------------------- Fully optimized for one app You must design + maintain the Smallest possible wire format entire protocol yourself Total control No standard, no federation Years of engineering Wrong choice unless you're at massive scale with a big team Who uses it: WhatsApp, Signal, Telegram, Discord. Great if you're a well-funded platform. Not a starting point for a community app. 5. Hosted / Backend-as-a-Service Buy chat as an API. Firebase (Firestore + Cloud Messaging), Stream , Sendbird , PubNub , Twilio Conversations . Pros Cons -------------------------------------------------------------- Fastest to ship Pay per user / per message forever No servers to run You don't own the data Handles scale for you Vendor can change pricing or Nice SDKs cut you off Data lives outside your country Costs balloon as you grow Who uses it: startups that want chat live this week. Wrong fit for a self-reliant community tool where every user is cost-sensitive and independence is the point — a pricing change in San Francisco should never be able to shut down messaging in Ngorongoro. 6. Raw WebSocket + your own protocol Open a WebSocket, invent your own message format, build the rest by hand. Pros Cons -------------------------------------------------------------- Total freedom You reimplement routing, presence, Simple to start ("just a socket") offline, groups, receipts, archive, Familiar to web devs reconnection — for years No standard, no interoperability Every bug is yours to discover Who uses it: Slack and Discord built custom protocols on top of WebSocket — but with large engineering teams. For a solo or small team, this is the "reinvent XMPP, badly" path. The verdict for MaasaiChat Own data Low 2G Built-in Free / Effort to & control bandwidth chat feats cheap ship -------------------------------------------------------------------------- XMPP/Ejabberd YES Excellent YES YES Medium Matrix YES Fair YES Cheap-ish Medium MQTT YES Excellent NO YES High Proprietary YES Best NO No Very high Hosted (SaaS) NO Varies YES No Low Raw WebSocket YES Good NO YES Very high XMPP is the only row that is yes on ownership, excellent on bandwidth, yes on built-in chat features, and free — at merely medium effort. For a self-hosted community app on 2G in East Africa, no other option matches on all four. That is why the rest of this book is XMPP. 1.7 Why XMPP fits rural East Africa specifically Every decision in this book is shaped by one reality: MaasaiChat users are on 2G/3G, low-end Android, with connections that drop constantly . XMPP earns its place for concrete reasons. Tiny messages. A stanza is a few hundred bytes. On a metered 2G plan where users pay per megabyte, that matters. No fat envelopes, no HTTP headers per message. Built for connections that drop. Stream Management (XEP-0198, Chapter 10) is designed for exactly this. When Naserian rides out of coverage near Ngorongoro and back ten minutes later, her session resumes — messages that arrived while she was gone are delivered, and messages she sent that didn't quite make it are re-sent, with no full reconnect and re-authentication. This single extension is worth the whole protocol on a rural network. Offline delivery is standard. If Ole's phone is off when Enkiama messages him, the server holds the message and delivers it when Ole reconnects. You don't build this. One server, huge capacity. A single Ejabberd node has handled two million concurrent connections in production. MaasaiChat across two countries won't come close to stressing it — so it runs on modest, affordable infrastructure. You own it. Self-hosted, open source, no per-message fees, no vendor who can cut you off. Rural constraint XMPP answer --------------------------------------------------------------- Expensive metered data --> Tiny stanzas, no polling Connection drops constantly --> Stream Management (resume, XEP-0198) Phone often off/asleep --> Server-side offline storage + push Low-end hardware --> Lightweight client, one connection Must be self-run --> Open protocol, free Ejabberd 1.8 The mental model to carry forward Before the next chapter, lock in this picture: Each user has an address (JID): naserian@maasaichat.com . Each device opens one long-lived connection to maasaichat.com . Over it flow stanzas — small XML fragments. There are exactly three stanza types : message , presence , iq . New features are added as extra XML inside stanzas , defined by XEPs , never by changing the core. The server ( Ejabberd ) handles routing, offline storage, presence, groups, and archive so you don't have to. Everything from here builds on those six facts. ✅ What you learned in this chapter XMPP is an open, standardized protocol for exchanging small XML stanzas in real time over one long-lived connection . It exists because HTTP request/response can't push server-initiated messages efficiently — fatal on metered, high-latency rural links. The three stanza types are message , presence , iq ; features are added via XEPs without changing the core. XMPP is the rulebook; Ejabberd is a builder that followed it — like HTTP↔Nginx or SQL↔PostgreSQL. Gajim, Smack, and sendxmpp are other builders following the same rules, which is why they all interoperate. Real, current users include WhatsApp (built on Ejabberd), Nintendo Switch, Zoom, and Google Talk . The alternatives are Matrix, MQTT, proprietary binary protocols, hosted SaaS, and raw WebSocket — each with real trade-offs; XMPP uniquely wins on ownership + bandwidth + built-in features + cost for a self-hosted 2G community app. XMPP fits rural East Africa : tiny messages, Stream Management for dropping connections, standard offline delivery, one high-capacity self-hosted server, and full ownership. MaasaiChat runs its own Ejabberd on maasaichat.com — no dependence on outside messaging providers. Ready for next chapter? (Chapter 2 — The XMPP Vocabulary : every key term you'll meet in this book — stanza, JID, resource, stream, namespace, roster, presence, MUC, MAM, SASL, and the rest — each defined plainly with a MaasaiChat example, so no word is ever a mystery in the chapters ahead.) Chapter 2 — The XMPP Vocabulary Key Terms & Definitions XMPP has a lot of vocabulary, and the deep-dive chapters ahead assume you know it. So this chapter is a dictionary you read once and refer back to forever. Every term gets a plain definition, an analogy, and — where it helps — a MaasaiChat example. You'll meet each of these again in depth later; the goal here is that no word is ever a mystery. The terms are grouped the way they actually relate, not alphabetically: A. The absolute core XMPP, stanza, stream, JID, resource B. Addressing details bare/full JID, message types C. Connecting & login TLS, SASL, bind, stream features D. Staying connected Stream Management, ping, keepalive E. Messaging features receipts, markers, chat states, IDs, carbons F. Presence & contacts presence, roster, subscription G. Group chat MUC, affiliation vs role, occupant H. Storage & history offline messages, MAM, RSM, inbox I. Discovery & extensions namespace, XEP, disco, caps, PubSub, PEP J. Media, calls, push, E2E file upload, Jingle, push, OMEMO K. Federation & transports s2s, dialback, BOSH, WebSocket L. Ejabberd-specific mod_, ejabberdctl, Mnesia, vhost, ACL, Erlang cookie, Erlang distribution A one-page Quick Reference Card sits at the end of the chapter — tear it out (metaphorically) and keep it beside you. A. The absolute core XMPP The rulebook. Extensible Messaging and Presence Protocol , an open IETF standard (RFC 6120/6121) created in 1999 as "Jabber." It defines how chat works: the shape of messages, the address format, how you log in, how features extend the core. It runs nothing by itself — a server like Ejabberd implements it. (See Chapter 1.) Stanza The basic unit of communication — one small XML fragment, like one sentence in a conversation. Everything you send or receive is a stanza. There are exactly three types: , , . The cattle arrived safely at the river. Stream The single long-lived connection between client and server. A tunnel that stays open; every stanza flows through it. Opening it is like starting a phone call; closing it is hanging up. ... all stanzas flow here for hours ... JID (Jabber ID) The address of every entity in XMPP — users, servers, group rooms. Like an email address, but for real-time chat. Format: user@domain/resource . ole@maasaichat.com/android a specific device ole@maasaichat.com the person (any device) maasaichat.com the server itself warriors@conference.maasaichat.com a group room Resource The device-identifier part of a JID, after the slash. It exists because one person logs in from several devices at once, and the server must tell them apart. ole@maasaichat.com/phone Ole's Tecno ole@maasaichat.com/tablet Ole's tablet Send to the bare JID ( ole@maasaichat.com ) and Ejabberd chooses the best device; send to a full JID ( .../phone ) and it goes to that one device only. B. Addressing details Bare JID vs Full JID Bare JID — ole@maasaichat.com . Identifies the person . Used for offline messages, roster entries, MUC membership. Full JID — ole@maasaichat.com/android . Identifies a specific connected device/session . Used to reach exactly one device, e.g. during a call. Rule of thumb: person → bare, device → full. Message types The type attribute on a tells the server and client how to treat it: chat one-to-one conversation (Naserian → Ole) groupchat a MUC room message (to warriors@conference...) normal a single message, no ongoing chat (system notices) headline broadcast/alert, never stored offline (announcements) error something failed; carries an child Using the wrong type causes real bugs — e.g. a headline won't be saved for an offline user, so MaasaiChat uses chat / groupchat for anything that must survive a dropped connection. C. Connecting & login TLS / STARTTLS Encryption of the stream. Before any password is sent, the client upgrades the plain TCP connection to an encrypted one (STARTTLS), or connects to an already-encrypted port (Direct TLS, 5223). No TLS = passwords and messages travel in the clear. MaasaiChat requires TLS always. SASL Simple Authentication and Security Layer — the login system, i.e. how you prove who you are. It supports several mechanisms : PLAIN sends the password directly (only safe inside TLS) SCRAM-SHA-1 secure challenge/response, no password on the wire SCRAM-SHA-256 stronger SCRAM-SHA-512 strongest classic option (what Gajim used) X-OAUTH2 log in with an OAuth token instead of a password With SCRAM, Ole's phone never sends his password — it solves a cryptographic challenge that proves it knows the password. (See also §J, channel binding.) Stream features Right after the stream opens, the server sends a list — "here's what's available/required next": STARTTLS, which SASL mechanisms, resource binding, Stream Management, and so on. The client walks through them in order. It's the server announcing the login menu. Bind (resource binding) After SASL proves who you are, binding assigns which session — it hands you your full JID by attaching a resource. android ole@maasaichat.com/android Now Ole's full JID exists and messages can be routed to this exact session. D. Staying connected (vital on 2G/3G) Stream Management (SM) — XEP-0198 Makes delivery reliable on bad networks with an acknowledgement counter, and lets a dropped session resume instead of fully reconnecting. "please confirm how many of my stanzas you got" "confirmed — I have received up to stanza 32" "I dropped and reconnected — resend from where we left off" MaasaiChat example: Ole sends message #30 on 2G near the boma, the signal drops, he reconnects, the server sees he only acked #29, and re-sends #30. Zero loss. This is the single most important extension for rural networks. Ping — XEP-0199 A tiny "are you still there?" iq . The client or server pings periodically to detect a silently-dead connection (common when a mobile network drops without closing the socket). Whitespace keepalive Even cheaper than a ping: the client sends a single space character down the stream now and then, just to keep NATs and carrier gateways from closing an "idle" connection. Common on mobile. E. Messaging features Delivery Receipt — XEP-0184 Proof a message reached the recipient's device — the single grey/blue tick "delivered." Are the warriors ready? Chat Markers — XEP-0333 Proof a message was received and read — the "read" tick. Distinct from a delivery receipt: delivery = it arrived on the device; marker (displayed) = the human actually saw it. received landed on the device displayed shown to the user (the "read" tick) acknowledged app-level handled Chat State Notifications — XEP-0085 The "typing…" experience. Tiny signals about what the other side is doing: active looking at the chat composing typing right now ("Naserian is typing…") paused stopped typing but still there inactive tab/chat idle gone left the conversation Stable & Unique Stanza IDs — XEP-0359 Gives every message two dependable IDs so all devices agree on identity: an origin-id set by the sender and a stanza-id assigned by the server/room. Essential for edits, reactions, replies, and de-duplication when the same message arrives via several paths. Last Message Correction — XEP-0308 Editing a sent message. The new stanza points at the old one's ID with , and clients swap the display in place (the "edited" label). Message Retraction — XEP-0424 "Delete for everyone." A stanza that tells clients to remove a previously-sent message, referenced by its ID. Message Reactions — XEP-0444 Emoji reactions attached to a message ID (👍 on Ole's cattle update), rather than a new separate message. Message Carbons — XEP-0280 Multi-device sync for one-to-one chats. When Naserian messages Ole, a copy is delivered to every one of Ole's connected devices, and copies of what Ole sends also appear on his other devices — so phone and tablet stay identical (like WhatsApp Web mirroring your phone). F. Presence & contacts Presence An announcement of availability, broadcast to those allowed to see it. available online (the default) away stepped away xa extended away (gone a while) dnd do not disturb unavailable offline chatHerding near Ngorongoro When Ole closes the app, the server sends unavailable on his behalf, and his contacts see him go offline. Presence priority When Ole is online on several devices, each presence carries a numeric . Messages to his bare JID go to the highest-priority device. Negative priority means "never auto-deliver here." Roster Your contact list — stored on the server , synced to every device. Switch phones, log in, and all contacts reappear instantly. Each entry holds a JID, a display name, subscription state, and groups. Ole's roster: naserian@maasaichat.com (Warriors) enkiama@maasaichat.com (Elders) nkeri@maasaichat.com (Traders) Presence Subscription Permission to see someone's presence — like a mutual follow. You must ask and be approved. subscribe "I want to see your status" subscribed "granted — you may see mine" unsubscribe "stop seeing my status" unsubscribed "denied / revoked" Ole sends subscribe to Naserian; she returns subscribed ; now Ole sees when she's online. Roster push When your roster changes (you add Nkeri), the server pushes the update to all your logged-in devices automatically, so they stay in sync without asking. G. Group chat MUC (Multi-User Chat) — XEP-0045 Group chat rooms. Each room is itself a JID on the conference service: warriors@conference.maasaichat.com ^room ^MUC service domain Send with type='groupchat' and every occupant receives it. Occupant & nickname Inside a room you appear under a nickname , and your in-room address is room@service/nickname (a full JID whose "resource" is your nick). Your real JID may be hidden depending on room settings. Affiliation vs Role (the classic MUC confusion) Two different permission systems in every MUC: AFFILIATION long-term membership status (persists across visits) owner created the room, full control admin can manage members/admins member allowed into a members-only room outcast banned none no special standing ROLE what you can do RIGHT NOW, this visit moderator can kick, grant voice, moderate participant can speak visitor can only read (no voice) none not in the room Affiliation is who you are to the room over time ; role is what you may do in this session . Enkiama the chief might be an owner (affiliation) who is currently acting as moderator (role). Message Moderation — XEP-0425 Lets a room moderator retract/hide someone else's message in the room (spam control), distinct from a user deleting their own. H. Storage & history Offline Messages — XEP-0160 If the recipient is offline, the server stores the message and delivers it on reconnect — voicemail for chat. Enkiama is out of signal in the bush; Ole's message waits on the server and lands when Enkiama's phone finds a tower. MAM (Message Archive Management) — XEP-0313 The server keeps a searchable archive of conversations; clients fetch history on demand. Buy a new phone, log in, ask for "the last 7 days," and your full history appears. Without MAM, a new device starts empty. RSM (Result Set Management) — XEP-0059 Paging for large result sets — "give me 20 messages before this point," then the next 20. MAM uses RSM so a phone on 2G loads history in small chunks instead of one huge download. Inbox — XEP-0430 A server-built list of your conversations with the latest message and unread count for each — the "chat list" screen, computed server-side so it's instant and consistent across devices. IQ (Info/Query) The request/response stanza — XMPP's version of an HTTP GET/POST. Always in pairs, always with a matching id : get ask for information result success (may carry data) set do or change something error it failed ...contacts... Roster fetch, ping, disco, bind, MAM queries — all are IQs. I. Discovery & extensions Namespace (xmlns) A unique string that says which extension an element belongs to . Without it, is meaningless; with xmlns='urn:xmpp:receipts' everyone knows it's a delivery receipt. Think of it as the department stamp on a memo. See xmlns="urn:xmpp:something" → it's a XEP. Search that string → you find the exact XEP instantly. XEP XMPP Extension Protocol — a document that adds a feature on top of core XMPP, each with a number: XEP-0045 (group chat), XEP-0184 (receipts), XEP-0166 (calls). Base XMPP is a basic phone; XEPs bolt on the camera, caller-ID, and voicemail. (Full treatment in Chapter 6.) Service Discovery (disco) — XEP-0030 How one entity asks another "what are you, and what can you do?" A client discos the server to learn which features and services (MUC, file upload, push) exist. Entity Capabilities (caps) — XEP-0115 An optimization on top of disco: each client advertises a short hash of its feature set in presence, so others cache "a client with hash X supports these XEPs" instead of re-asking every time. Saves bandwidth — good on 2G. PubSub (Publish-Subscribe) — XEP-0060 A general publish/subscribe system on the server: publishers post items to a node , subscribers get them. The backbone for many features (avatars, bookmarks, presence-like data). PEP (Personal Eventing Protocol) — XEP-0163 A simplified PubSub attached to a user's own account — "my nodes." Used for things like your current avatar, mood, or bookmarks, auto-broadcast to contacts who care. J. Media, calls, push, encryption HTTP File Upload — XEP-0363 How you send photos/files. The client asks the server for an upload slot (a PUT URL + a GET URL), uploads the file over HTTPS, then sends the GET URL in a normal message. This is the file-transfer method MaasaiChat actually uses — it works fine over flaky mobile links, unlike peer-to-peer transfer. Jingle — XEP-0166 (+0167/0176/0215/0320) The signaling framework for voice/video calls. Jingle negotiates the call (who, what codec, network path) inside XMPP , while the actual audio/video flows over WebRTC. Related XEPs handle RTP media (0167), ICE network traversal (0176), TURN/STUN discovery (0215), and encryption (0320). Push Notifications — XEP-0357 Wakes a sleeping phone. When a message arrives for an app that's backgrounded/disconnected, the server triggers a push (via FCM on Android) so the user gets notified without holding a socket open — essential for battery on low-end phones. OMEMO — XEP-0384 Modern end-to-end encryption for XMPP (built on the Signal Protocol's ideas). Messages are encrypted per-device so that not even the server can read them. Optional; a design decision for later MaasaiChat phases. SASL Channel Binding — XEP-0440 / Extensible SASL — XEP-0388 Hardening for login: channel binding ties the SASL authentication to the exact TLS connection, blocking a class of man-in-the-middle attacks. XEP-0388 modernizes how SASL mechanisms are negotiated. K. Federation & transports c2s and s2s Two connection kinds Ejabberd listens for: c2s (client-to-server, port 5222 — phones connecting) and s2s (server-to-server, port 5269 — other XMPP servers connecting for federation). Federation Different XMPP servers talking to each other, so naserian@maasaichat.com could message someone@another-server.org — the same way email crosses providers. Enabled by s2s. Server Dialback — XEP-0220 A verification handshake that lets a receiving server confirm a connecting server really owns the domain it claims, preventing spoofed federation. (Bidirectional s2s, XEP-0288, lets one connection carry traffic both ways.) BOSH and WebSocket Alternative transports for when a raw TCP stream isn't possible (e.g. a browser). BOSH tunnels XMPP over HTTP long-polling; WebSocket carries the XML stream over a WebSocket. Native mobile apps use raw TCP; web clients use WebSocket. L. Ejabberd-specific terms mod_ (module) A plugin that adds a feature to Ejabberd, switched on in ejabberd.yml — like browser extensions. mod_muc group chat mod_mam message archive mod_offline offline storage mod_http_api REST API mod_ping keepalive pings mod_mqtt MQTT protocol mod_push push notifications mod_admin_extra extra CLI commands No mod_muc → no group chat. Add it → group chat works. ejabberdctl Ejabberd's command-line control tool, run inside the container — the psql / redis-cli equivalent for Ejabberd. docker exec ejabberd ejabberdctl status docker exec ejabberd ejabberdctl registered_users maasaichat.com docker exec ejabberd ejabberdctl send_message chat ole@maasaichat.com \ naserian@maasaichat.com "" "Meeting at the manyatta tonight" vhost (virtual host) One Ejabberd process can serve several domains at once ( maasaichat.com , staging.maasaichat.com ), each with its own users and settings — like Nginx server blocks. ACL & Access Rules Ejabberd's permission system: an ACL defines who (e.g. "admins = admin@maasaichat.com"), and an access rule grants those groups rights (e.g. who may use the REST API). Misconfigured ACLs are the classic cause of Account does not have the right to perform the operation when calling the API. Mnesia Erlang's built-in database, bundled with Ejabberd, used for its internal live state — who's connected where, room state, roster, short-term offline spool. It does not hold MaasaiChat's business data; that lives in PostgreSQL. Erlang Cookie A shared secret string all nodes in an Ejabberd cluster must have identical — the "password" that proves two nodes belong to the same cluster. Match → they trust each other; mismatch → the node is rejected. Erlang Distribution (dist) Erlang's native node-to-node networking. In a cluster, if Ole is on Node 1 and Naserian is on Node 2, Node 1 hands the stanza straight to Node 2 over Erlang distribution — no Redis pub/sub needed. This is why an Ejabberd cluster is simpler than a hand-built socket cluster. Spool The queue where offline messages wait for a disconnected user. When Enkiama reconnects, Ejabberd flushes his spool to his device. Quick Reference Card Term Simple definition ──────────────────────────────────────────────────────────────── XMPP The language/rules of chat Stanza One unit of communication (message/presence/iq) Stream The single open connection (tunnel) JID Address: user@domain/resource Resource Device identifier (/phone /tablet) Bare / Full JID Person (bare) vs specific device/session (full) Message type chat / groupchat / normal / headline / error TLS / STARTTLS Encryption of the stream SASL Authentication system (SCRAM, PLAIN, OAuth) Stream features Server's "menu" of what to negotiate next Bind Claiming your resource → full JID Stream Management Reliable delivery + resume on bad networks (0198) Ping "Are you alive?" heartbeat (0199) Keepalive Whitespace to stop NAT/carrier timeouts Delivery Receipt "Delivered" tick (0184) Chat Markers "Read/displayed" tick (0333) Chat States typing / paused indicators (0085) Stanza IDs Stable message identity (0359) Correction Edit a sent message (0308) Retraction Delete for everyone (0424) Reactions Emoji on a message (0444) Carbons Multi-device 1:1 sync (0280) Presence Online/offline status Priority Which device gets bare-JID messages Roster Contact list (stored on server) Subscription Permission to see presence (mutual) MUC Group chat room (0045) Occupant / nick Your identity inside a room Affiliation Long-term membership (owner/admin/member/outcast) Role Current-session powers (moderator/participant/visitor) Offline Messages Stored when recipient offline (0160) MAM Server message archive/history (0313) RSM Paging for large results (0059) Inbox Server-built conversation list (0430) IQ Request/response stanza (get/set/result/error) Namespace (xmlns) Which XEP an element belongs to XEP A document adding a feature to XMPP disco Service discovery: "what can you do?" (0030) caps Cached capability hashes (0115) PubSub / PEP Publish-subscribe / personal eventing (0060/0163) HTTP File Upload Send files via upload slot (0363) Jingle Voice/video call signaling (0166) Push Wake a sleeping phone via FCM (0357) OMEMO End-to-end encryption (0384) c2s / s2s Client-to-server / server-to-server Federation Cross-server messaging (like email) Dialback Verifies a federating server (0220) BOSH / WebSocket XMPP over HTTP / over WebSocket (for browsers) mod_ Ejabberd plugin/module ejabberdctl Ejabberd CLI management tool vhost One server hosting multiple domains ACL / Access Rule Ejabberd permission system Mnesia Ejabberd's internal live-state database Erlang Cookie Cluster membership password Erlang dist Direct node-to-node communication Spool Queue of waiting offline messages ✅ What you learned in this chapter Stanza, stream, JID, resource are the four words the whole protocol stands on — one XML unit, one open connection, one address, one device tag. Addressing splits into bare (person) vs full (device) JIDs, and every message carries a type (chat/groupchat/normal/headline/error) that decides how it's handled. Login is a pipeline: TLS → SASL → bind → stream features , and staying connected on 2G relies on Stream Management, ping, and keepalives . Messaging polish is a stack of small XEPs — receipts (0184), markers (0333), chat states (0085), stable IDs (0359), carbons (0280) — plus edit/retract/react. Presence + roster + subscription run contacts and status; MUC runs groups, where affiliation (long-term) and role (this session) are different things . History is offline messages → MAM → RSM → inbox ; discovery is disco + caps ; extensions are identified by namespace and documented as XEPs . Ejabberd-specific: mod_ modules, ejabberdctl, vhosts, ACLs, Mnesia, the Erlang cookie, and Erlang distribution — the operational vocabulary for Part 3. Ready for next chapter? (Chapter 3 — XML Basics for XMPP : now that you know the words, we slow down and read the XML itself — elements, attributes, namespaces, and the wrapper — so every stanza in the book is effortless to parse by eye.)