Skip to main content

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 callResult
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
  • PacketContent
  • PacketMail
  • 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:

  1. loads inline or external content
  2. decrypts textual encrypted JSON when requested
  3. parses the Packet envelope
  4. 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.