Skip to main content

Sending Messages

Opening a thread

createThread creates the thread on-chain and sends the first message in one transaction. The thread ID is random by default.

const { receipt, client: thread } = await client.createThread({
to: recipientPublicKey,
messageType: MessageType.Text,
content: "Hey, let's talk.",
});

CreateThreadParams

FieldTypeRequiredDescription
toPublicKeyYesRecipient wallet address
messageTypeMessageTypeYesType of content
contentstringYesMessage body
threadIdnumberNoRandom u32 if omitted
targetInboxInbox | InboxClientNoRecipient's inbox (load it first to apply payment rules correctly)
paymentSendMsgPaymentParams | DisabledPaymentNoPayment override
skipInboxArchivalIxbooleanNoSkip inbox archival instruction

Passing targetInbox is important when the recipient has a payment-gated inbox. Without it, the thread creation won't update the inbox index or validate the payment rule. Load their inbox first:

const recipientInbox = await client.inbox(recipientInboxAddress);
const { client: thread } = await client.createThread({
to: recipientPublicKey,
targetInbox: recipientInbox.Inbox,
messageType: MessageType.Text,
content: "Hello!",
// payment is auto-filled from the inbox's payment rule
});

You can also use inbox.createThread(...) if you already have the InboxClient — it fills to and targetInbox automatically.


Sending a message

After a thread exists, use sendMessage:

const { receipt, client: message } = await thread.sendMessage({
messageType: MessageType.Text,
content: "Still there?",
});
FieldTypeRequiredDescription
messageTypeMessageTypeYesContent type
contentstringYesMessage body
msgSeqnumberNoAuto-incremented from last seq if omitted
paymentSendMsgPaymentParamsNoAttach a token payment to this message
skipInboxArchivalIxbooleanNoSkip inbox archival instruction

Message types

enum MessageType {
Text = 0, // inline UTF-8 string (stored on-chain)
Url = 1, // arbitrary URL, fetched by the reader
Ipfs = 2, // IPFS CID URL
Irys = 3, // Irys (Arweave) URL
Arweave = 4, // Arweave URL
}

For short messages, use Text — the content is stored directly in the compressed account. For longer payloads (images, encrypted blobs, long text), upload the data off-chain and store the URL using Url, Ipfs, Irys, or Arweave. The MessageClient.loadContent() method handles fetching transparently for all types.


Payments

You can attach a token transfer to any message. This is separate from the inbox payment rule — it's a one-off payment sent alongside a specific message.

await thread.sendMessage({
messageType: MessageType.Text,
content: "Here's payment for the invoice.",
payment: {
amount: new BN(1_000_000), // in token lamports
mint: usdcMint,
},
});

SendMsgPaymentParams

FieldTypeRequiredDescription
amountBNYesToken amount
mintPublicKeyYesToken mint address
fromTokenAccountPublicKeyNoSender's token account (auto-derived as ATA if omitted)
to{ type: "ata"; owner?: PublicKey } | { type: "raw"; address: PublicKey }NoRecipient token account (defaults to the recipient's ATA)
tokenProgramPublicKeyNoAuto-detected from the mint if omitted

Disabling payment (for payment-gated inboxes)

If you're opening a thread to a payment-gated inbox but want to explicitly skip paying — the transaction will still fail on-chain if the rule is enforced, but you can signal intent with:

payment: { disable: true }

This is useful for inboxes that allow free threads from specific senders via program-level allowlists.


Reading messages back

After sending, receipt.client is the loaded MessageClient for the message you just sent. To read the thread's history, see loading messages.