XMessage Specification
The XMessage specification is used by tools in the Communication package ecosystem. It is a subset of the far larger XMPP and also contains a few additional features not found in the XMPP specification.
The purpose of this specification is to provide a common message description standard that many different kinds of compatible tools can be based on. Using a single, shared message description standard has the following advantages:
- 1.Users in the Communication package can mix and match tools and reassess which they use based on their changing needs. In particular, they don't get locked in to tools that may become deprecated or for which an attractive replacement becomes available.
- 2.Tool implementors in the Communication ecosystem can benefit from feedback from a broad range of collaborators when designing new core functionality.
- 3.Tool implementors in the Communication ecosystem can share core implementations.
- 4.Enables building a message transport protocol between services.
This document is intended primarily for developers who build message processing engines (Transformers) or rule engines (Orchestrator).
The document assumes at least a fair understanding of XML. It is also useful to refer to XMPP for details about shared features. The same features can be built over json as well.
Even though JSON is tempting we chose XML for data interchange for the following reasons,
- 1.Its is a document markup language which is what most of the text is for various modes of communication
- 2.It has a schema and allows for validation of the same
- 3.Building contracts among servers are a breeze
- 4.It has namespaces which are used when combining different format. (Although we are not using namespaces right now, this spec will evolve into using namespaces for various sections as it expands)
The current structure of XMessages is divided into following sections.
- Sender/Reciever Information
- Channel/Provider Information
- Transformers that need to act on this message
- Context related parameters
- Message body
Let's dive deeper into all of these, but before that, this is the complete xml schema (this is used for validation currently)
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/xmlSchema"
targetNamespace="https://www.samagra.io"
xmlns:sam="https://www.samagra.io"
elementFormDefault="qualified">
<xs:simpleType name="timestamp">
<xs:restriction base="xs:negativeInteger">
<xs:pattern value="^(\d{13})?$"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="messageID">
<xs:sequence>
<xs:element name="id" type="xs:string"/>
<xs:element name="gupshupMessageID" type="xs:string"/>
<xs:element name="whatsappMessageID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="thread">
<xs:sequence>
<xs:element name="offset" type="xs:positiveInteger"/>
<xs:element name="startDate" type="sam:timestamp"/>
<xs:element name="lastMessage" type="sam:messageID"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="senderReceiverInfo">
<xs:sequence>
<xs:element name="bot" type="xs:boolean"/>
<xs:element name="broadcast" type="xs:boolean"/>
<xs:element name="userID" type="xs:string"/>
<xs:element name="campaignID" type="xs:string"/>
<xs:element name="formID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="conversationStage">
<xs:sequence>
<xs:element name="stage" type="xs:string"/>
<xs:element name="state" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
<xs:element name="country" type="xs:string"/>
<xs:element name="countryCode" type="xs:positiveInteger"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="contactCard">
<xs:sequence>
<xs:element name="address" type="sam:address"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="locationParams">
<xs:sequence>
<xs:element name="latitude" type="xs:double"/>
<xs:element name="longitude" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="messageMedia">
<xs:sequence>
<xs:element name="category" type="xs:string"/>
<xs:element name="text" type="xs:string"/>
<xs:element name="url" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="xMessagePayload">
<xs:sequence>
<xs:element name="text" type="xs:string"/>
<xs:element name="media" type="sam:messageMedia"/>
<xs:element name="location" type="sam:locationParams"/>
<xs:element name="contactCard" type="sam:contactCard"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="transformer">
<xs:sequence>
<xs:element name="id" type="xs:string"/>
<xs:element name="meta" type="xs:anyType"/>
</xs:sequence>
</xs:complexType>
<xs:element name="xMessage">
<xs:complexType>
<xs:sequence>
<xs:element name="app" type="xs:string"/>
<xs:element name="messageID" type="xs:string"/>
<xs:element name="channelURI" type="xs:string"/>
<xs:element name="providerURI" type="xs:string"/>
<xs:element name="timestamp" type="sam:timestamp"/>
<xs:element name="userstate" type="xs:string"/>
<xs:element name="encryptionProtocol" type="xs:string"/>
<xs:element name="thread" type="sam:thread"/>
<xs:element name="to" type="sam:senderReceiverInfo"/>
<xs:element name="from" type="sam:senderReceiverInfo"/>
<xs:element name="payload" type="sam:xMessagePayload"/>
<xs:element name="conversationStage" type="sam:conversationStage"/>
<xs:element name="messageState" type="xs:string"/>
<xs:element name="lastMessageID" type="xs:string"/>
<xs:element name="transformers" type="sam:transformer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
The
from
and to
construct uses the structure show below.<xs:complexType name="senderReceiverInfo">
<xs:sequence>
<xs:element name="bot" type="xs:boolean"/>
<xs:element name="broadcast" type="xs:boolean"/>
<xs:element name="userID" type="xs:string"/>
<xs:element name="campaignID" type="xs:string"/>
<xs:element name="formID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
Parameter | Description |
---|---|
bot | Refers to if the response/reply from/to the user is from a bot or an actual user |
broadcast | Is for use case where the user or sever is sending the same message to multiple users or a single one |
userID | Current User ID; When the message is still not parsed, it could be his phone number but should generally be a UUID which represents the user in the database |
campaignID | ID of the conversation campaign the message is part of |
formID | Ff the reply is for a ODK Form based bot, this represents the ID of that form |
<xs:element name="channelURI" type="xs:string"/>
<xs:element name="providerURI" type="xs:string"/>
Parameter | Description |
---|---|
contentURI | Channel for the message (SMS, Email, WhatsApp etc) |
providerURI | Provider for the message (Gupshup, AmazonSES, Twilio, etc) |
Each message can be acted on by multiple transformers to modify/reply the payload/channel etc. This happens through
Transformers
. Each transformer is assigned an ID when it is registered.<xs:complexType name="transformer">
<xs:sequence>
<xs:element name="id" type="xs:string"/>
<xs:element name="meta" type="xs:anyType"/>
</xs:sequence>
</xs:complexType>
Parameter | Description |
---|---|
id | Transformer's ID (genereated at the time of registration) |
meta | Meta data for the transformer |
This section describes the
<xs:element name="app" type="xs:string"/>
<xs:element name="userstate" type="xs:string"/>
<xs:element name="conversationStage" type="sam:conversationStage"/>
<xs:element name="messageState" type="xs:string"/>
<xs:element name="lastMessageID" type="xs:string"/>
<xs:element name="thread" type="sam:thread"/>
Parameter | Description |
---|---|
app | App is a global namespace the message is part of. For example an app may correspond to multichannel conversation that many users are part of. Any segreation of conversation is defined as an app |
userstate | Any of the following => typing , offline , unregistered , registered , active , inactive |
messageState | State of a message sent to the user. Any of the following => FAILED_TO_DELIVER , DELIVERED , QUEUED , READ , REPLIED |
conversationStage | Stage of the current conversation. Any of the following => STARTING , ONGOING , COMPLETED |
lastMessageID | ID of the last message sent for this conversation thread. |
thread | The thread which the message belongs to |
Message body referes to the actual message body. The current list of message types includes plain text, xHTML, location, media, or a contactCard.
<xs:complexType name="contactCard">
<xs:sequence>
<xs:element name="address" type="sam:address"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="locationParams">
<xs:sequence>
<xs:element name="latitude" type="xs:double"/>
<xs:element name="longitude" type="xs:double"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="messageMedia">
<xs:sequence>
<xs:element name="category" type="xs:string"/>
<xs:element name="text" type="xs:string"/>
<xs:element name="url" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="xMessagePayload">
<xs:sequence>
<xs:element name="text" type="xs:string"/>
<xs:element name="media" type="sam:messageMedia"/>
<xs:element name="location" type="sam:locationParams"/>
<xs:element name="contactCard" type="sam:contactCard"/>
</xs:sequence>
</xs:complexType>
<xs:element name="payload" type="sam:xMessagePayload"/>
<payload>
<text>
(Two spans, both )(*alike in dignity*)
</text>
</payload>
Should be styled as => (Two spans, both )(alike in dignity)
<payload>
<text>
Everyone ~dis~likes cake
</text>
</payload>
Should be styled as => Everyone dislikes cake
<payload>
<text>
The full title is _Twelfth Night, or What You Will_ but _most_ people shorten it.
</text>
</payload>
Should be styled as => The full title is Twelfth Night, or What You Will but most people shorten it.
Text enclosed by a '`' (U+0060 GRAVE ACCENT) is a preformatted span SHOULD be displayed inline in a monospace font. A preformatted span may only contain a single plain span.
<payload>
<text>
Wow, I can write in `monospace`!
</text>
</payload>
Should be styled as => Wow, I can write in
monospace
!Any text inside of a block that is not part of another span is implicitly considered to be inside of a "plain text" span.
<payload>
<text>
(There are three blocks in this body marked by parens,)
(but there is no *formatting)
(as spans* may not escape blocks.)
</text>
</payload>
On rare occasions styling hints may conflict with the contents of a message. For example, if the user sends the emoji
"> _ <"
it would be interpreted as a block quote. Senders may indicate to the receiver that a particular message SHOULD NOT be styled by adding an empty <unstyled>
element.<payload>
<text>
<unstyled>> _ <</unstyled>
</text>
</message>
This document does not define a regular grammar and thus styling cannot be matched by a regular expression. Instead, a simple parser can be constructed by first parsing all text into blocks and then recursively parsing the child-blocks inside block quotations, the spans inside individual lines, and by returning the text inside preformatted blocks without modification.
It is RECOMMENDED that styling directives be displayed and formatted in the same manner as the text they apply to. For example, the string "emphasis" would be rendered as "emphasis".
This specification does not provide a mechanism for removing styling from individual spans or blocks within a styled message. Implementations are free to implement their own workarounds, for example by inserting Unicode non-printable characters to invalidate styling directives, but no specific technique is known to be widely supported.
To be updated based on incoming feedback. Feel free to write into [email protected] in case you have questions, feedback or want to know more!
Last modified 9mo ago