Envelope and Message Content
Packet message bodies keep a small stable wire format:
export type PacketMail = {
subject?: string;
message: PacketContent | PacketContent[] | string;
};
export type PacketContent = {
contentType: string;
encoding: "utf8" | "base64";
content: string;
};
export type PacketEnvelopeValue = PacketMail | PacketContent | string;
Everything is still a string on the wire. Binary data is represented as PacketContent with encoding: "base64" and a MIME contentType. Clients decide how to render it.
Packet envelopes are meant to be uploaded/encrypted or kept small. Do not put large envelopes directly into the compressed message account. Inline payloads larger than about 128 bytes can make transactions fail; upload the body and store a pointer instead.
Building payloads
Use PacketEnvelope when you want explicit control over the wire shape:
import { PacketEnvelope, type PacketContent } from "xpkt-sdk";
const textPart: PacketContent = {
contentType: "text/markdown",
encoding: "utf8",
content: "Here is the report.",
};
const filePart: PacketContent = {
contentType: "image/png",
encoding: "base64",
content: pngBase64,
};
const body = new PacketEnvelope()
.mail("Report")
.content(textPart)
.content(filePart)
.encode();
PacketEnvelope supports three shapes:
| Builder call | Result |
|---|---|
new PacketEnvelope().text("hello").encode() | Bare string body. |
new PacketEnvelope().content(part).encode() | One PacketContent. |
new PacketEnvelope().mail(subject).content(partA).content(partB).encode() | PacketMail with one or more PacketContent parts. |
Call mail() before multiple content() calls. A plain content().content() chain is rejected because a multi-part envelope needs a PacketMail wrapper.
Use buildPacketEnvelopePayload for the common single-part helper path before encryption or upload:
import { buildPacketEnvelopePayload } from "xpkt-sdk";
const body = buildPacketEnvelopePayload({
content: "Hello",
contentType: "text/plain",
encoding: "utf8",
subject: "Greeting",
});
If raw: true is passed with plain UTF-8 text and no subject, the result is the bare string. Otherwise the SDK wraps content as PacketContent, or PacketMail when a subject is present.
For multiple parts, build a PacketMail containing a PacketContent[]. CLI and MCP do this for repeated text/file inputs.
PacketEnvelope.decode(raw) returns a PacketEnvelopeValue when the raw text is a valid envelope JSON, or the original string when it is plain text. It throws when JSON is valid but not a Packet envelope.
Parsing and rendering
import {
isPacketContent,
isPacketMail,
isPacketEnvelopeValue,
parsePacketEnvelopeText,
renderPacketContent,
isTextualMime,
packetContentBytes,
} from "xpkt-sdk";
const parsed = parsePacketEnvelopeText(rawText);
console.log(parsed.subject);
console.log(parsed.message); // display string
console.log(parsed.parts); // PacketContent[] when available
parsePacketEnvelopeText accepts legacy bodies:
- bare strings
PacketContentPacketMail- legacy
{ subject, message: string }
Invalid JSON that is not a Packet envelope is treated as plain text for display instead of failing the reader.
renderPacketContent is MIME-aware:
- textual MIME types decode to text
- binary MIME types render as metadata like
[binary image/png, ~12 KB base64]
Use packetContentBytes(part) when the UI/agent wants the actual bytes for a download or MCP resource block.
Textual MIME detection
isTextualMime(mime) returns true for:
text/*- JSON (
application/json,*+json) - XML (
application/xml,*+xml) - SVG (
image/svg+xml) - YAML
- JavaScript/TypeScript
Image formats like image/png, image/jpeg, archives, and arbitrary octet streams are treated as binary.
Pointers and resource loading
The SDK normalizes message content kinds:
type PacketMessageContentKind =
| "text"
| "url"
| "irys"
| "ipfs"
| "arweave";
Helpers:
parsePacketSendContentType("auto");
resolvePacketSendContent(input, "auto");
messageTypeForPacketContent("irys");
packetContentKindForMessageType(MessageType.Irys);
appendPacketGatewayIfNeeded(MessageType.Irys, irysId);
resolvePacketSendContent accepts URLs and IDs for Irys/IPFS/Arweave and returns the canonical storage value to put on-chain. For example, an Irys gateway URL is stored as its Irys id.
loadPacketMessageContent fetches external resources and preserves both bytes and Content-Type. It only populates .text for textual MIME types.
Parsed message loading
MessageClient.loadContent() resolves the raw content:
const loaded = await message.loadContent();
loaded.bytes; // always available
loaded.text; // only for textual MIME
loaded.contentType; // from inline default or HTTP header
loaded.url; // for external resources
MessageClient.loadParsedContent() performs the higher-level reader flow:
const parsed = await message.loadParsedContent({ decrypt: true });
console.log(parsed.encrypted);
console.log(parsed.subject);
console.log(parsed.message);
console.log(parsed.mediaKind); // "text" | "binary"
It:
- loads inline or external content
- decrypts textual encrypted JSON when requested
- parses the Packet envelope
- returns display text plus raw bytes/text metadata
Binary external content is not force-decoded. It returns a binary preview string and keeps rawBytes available.