> ## Documentation Index
> Fetch the complete documentation index at: https://www.offlineprotocol.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs target Mesh SDK v0.27.0. Match the installed package and binding before generating code. Start at /getting-started/agents for task-specific reading paths.
> Call the company and product Offline Protocol, never Offline alone. Current packages: @offline-protocol/mesh-sdk 0.27.0 (React Native), @offline-protocol/id-react 0.2.0, @offline-protocol/id-react-native 0.3.3, @offline-protocol/pol 0.1.2 and @offline-protocol/cli 0.2.6. Canonical docs URLs start with https://www.offlineprotocol.com/docs.
> The Mesh SDK runs in a native app or gateway. A browser OfflineID SDK integration does not provide browser mesh transport. Local mesh operation does not require a portal API key.
> Service RPC is signed plaintext in v0.27.0. Message delivery, durable local acceptance and backend commit are distinct outcomes. Use the workflow guide for the required application logic.
> Offline Protocol CLI 0.2.6 is on npm (@offline-protocol/cli, command offline). Its local MCP server runs with offline mcp serve and requires no login or key. Hosted MCP is at https://mcp.offlineprotocol.com/mcp with an application API key in Authorization: Bearer and a matching x-app-id; organization keys are rejected. Follow /tools/overview for setup and do not invent commands beyond it. MCP provides integration context and planning, not mesh execution; file-writing tools are local only.
> Phone Wi-Fi Direct and MultipeerConnectivity carry no data in v0.27.0. Use BLE or a provisioned relay. The receiver core ACKs before application persistence; use application acceptance for durable workflows.
> Proof of Location is Sepolia testnet witness evidence, not zero-knowledge proof or proof of presence. The geohash is public onchain. Read /proof-of-location/security before integration.

# Messaging and receipts

> Send application messages, manage connection requests and inspect delivery outcomes.

Import `OfflineProtocol` from `@offline-protocol/mesh-sdk`. These signatures match **v0.27.0**. Read [the integration guide](/docs/guides/local-handoff) for prerequisites and complete usage.

## Methods on this page

* [`sendMessage`](#sendmessage)
* [`forwardMessage`](#forwardmessage)
* [`sendConnectionRequest`](#sendconnectionrequest)
* [`acceptConnectionRequest`](#acceptconnectionrequest)
* [`rejectConnectionRequest`](#rejectconnectionrequest)
* [`cancelConnectionRequest`](#cancelconnectionrequest)
* [`getMessageStats`](#getmessagestats)
* [`getDeliverySuccessRate`](#getdeliverysuccessrate)
* [`getMedianLatency`](#getmedianlatency)
* [`getMedianHops`](#getmedianhops)
* [`receiveMessage`](#receivemessage)
* [`sendPresenceUpdate`](#sendpresenceupdate)
* [`checkInternetPresence`](#checkinternetpresence)
* [`sendTypingIndicator`](#sendtypingindicator)
* [`sendReadReceipt`](#sendreadreceipt)
* [`blockUser`](#blockuser)
* [`unblockUser`](#unblockuser)
* [`getBlockedUsers`](#getblockedusers)
* [`isUserBlocked`](#isuserblocked)

## sendMessage

Sends a message

**returns:** Message ID

**throws:** Error if message fails to send

```typescript theme={null}
sendMessage(params: SendMessageParams): Promise<string>
```

## forwardMessage

Forwards a message to a new recipient with original sender attribution.

Creates a new message with the original content and attaches forwarding
metadata tracking the original sender, message ID, timestamp, and forward count.

**returns:** New message ID

**throws:** Error if forwarding fails

```typescript theme={null}
forwardMessage(params: ForwardMessageParams): Promise<string>
```

## sendConnectionRequest

Sends a connection request

`params.recipient` must be the target's canonical address (`off1…`): the value they derived from their own identity key, which is also what
`neighbor_discovered` reports as `peer_id`.

The returned message id is the correlation key for the request's
outcome events: `connection_request_undeliverable` (recipient offline
or retry budget exhausted), `message_delivered` (reached the
recipient's device), and `message_failed` (generic retry exhaustion,
fires alongside the typed event). The recipient's answer arrives as
`connection_accepted` / `connection_rejected`, which correlate by peer
id (`accepted_by` / `rejected_by`), not by message id.

**returns:** Message ID

**throws:** Error if request fails to send

```typescript theme={null}
sendConnectionRequest(params: SendConnectionRequestParams): Promise<string>
```

## acceptConnectionRequest

Accepts a connection request

**returns:** Message ID

**throws:** Error if acceptance fails to send

```typescript theme={null}
acceptConnectionRequest(params: AcceptConnectionRequestParams): Promise<string>
```

## rejectConnectionRequest

Rejects a connection request

**returns:** Message ID

**throws:** Error if rejection fails to send

```typescript theme={null}
rejectConnectionRequest(params: RejectConnectionRequestParams): Promise<string>
```

## cancelConnectionRequest

Cancels a previously sent connection request

**returns:** Message ID

**throws:** Error if cancellation fails to send

```typescript theme={null}
cancelConnectionRequest(params: CancelConnectionRequestParams): Promise<string>
```

## getMessageStats

Gets message delivery statistics

**returns:** Array of message delivery statistics

**throws:** Error if stats retrieval fails

```typescript theme={null}
getMessageStats(): Promise<MessageDeliveryStats[]>
```

## getDeliverySuccessRate

Gets the delivery success rate

**returns:** Success rate as a number between 0 and 1

**throws:** Error if retrieval fails

```typescript theme={null}
getDeliverySuccessRate(): Promise<number>
```

## getMedianLatency

Gets the median message delivery latency

**returns:** Median latency in milliseconds, or null if no data available

**throws:** Error if retrieval fails

```typescript theme={null}
getMedianLatency(): Promise<number | null>
```

## getMedianHops

Gets the median hop count for delivered messages

**returns:** Median hop count, or null if no data available

**throws:** Error if retrieval fails

```typescript theme={null}
getMedianHops(): Promise<number | null>
```

## receiveMessage

Polls for the next received message

**returns:** Message object if available, null otherwise

**throws:** Error if polling fails

```typescript theme={null}
receiveMessage(): Promise<MessageReceivedEvent | null>
```

## sendPresenceUpdate

Sends a presence update to a peer.

**returns:** Message ID

```typescript theme={null}
sendPresenceUpdate(recipient: string, status: 'online' | 'away' | 'offline'): Promise<string>
```

## checkInternetPresence

Asks the internet relay for a peer's presence (one-shot CheckPresence).

## Contract

* **Always fresh.** The SDK never throttles or dedupes manual checks:
  every accepted call sends a new `CheckPresence` frame to the relay,
  regardless of how recently the same peer was queried. (The automatic
  watch loop's tick/TTL policy does not apply here.)
* **Fire-and-event.** The answer arrives as a `presence_updated` event
  with `source: 'internet'` (including `last_seen_ms` when the relay
  knows it) rather than in the returned promise. Every relay answer
  re-emits the event **even when nothing changed**: safe to drive a
  chat-header refresh from. Subscribe before calling; events have no
  replay.
* **Exceptions.** The core suppresses presence for blocked peers and
  your own user id: for those, this resolves `true` (the query was
  sent) but no `presence_updated` follows. And `true` means the query
  reached the socket, not that an answer will arrive: a connection
  dropped before the relay replies loses the answer (call again).
* **Rate limiting is never bypassed**, force or not: the SDK's
  client-side limiter mirrors the relay's per-connection budget, and an
  over-budget frame would be dropped server-side *after* a locally
  "successful" write: strictly worse than deferring.

`options.force` is for chat open/focus: exactly when the app wants a
fresh header, the socket is often still resuming from background. A
non-forced call fails fast (`false`) in that window; a forced call is
parked and retried until the transport is authenticated and the
limiter admits it (up to \~8s), only then resolving `false`. On a
stopped transport (no reconnect coming) even forced calls fail fast.
Forced checks stay one-shot: they never join the SDK's automatic
watch set.

**returns:** true once the socket accepted the query (write-confirmed on
iOS, enqueue-confirmed on Android, the closest OkHttp offers);
false otherwise, an empty `userId` (never sent), or not
connected+authenticated / rate-limiter-deferred past the
force deadline (non-forced: immediately; safe to retry)

**throws:** when the internet transport was never initialized (enable it via
`transports.internet` before calling)

```typescript theme={null}
checkInternetPresence(userId: string, options?: { force?: boolean }): Promise<boolean>
```

## sendTypingIndicator

Sends a typing indicator to a peer.

**returns:** Message ID

```typescript theme={null}
sendTypingIndicator(recipient: string, conversationId: string, isTyping: boolean): Promise<string>
```

## sendReadReceipt

Sends a read receipt to a peer.

**returns:** Message ID

```typescript theme={null}
sendReadReceipt(recipient: string, messageIds: string[]): Promise<string>
```

## blockUser

Blocks a user. Messages from blocked users are silently dropped at the protocol level.

```typescript theme={null}
blockUser(userId: string): Promise<void>
```

## unblockUser

Unblocks a previously blocked user.

```typescript theme={null}
unblockUser(userId: string): Promise<void>
```

## getBlockedUsers

Returns the list of blocked user IDs.

**returns:** Array of blocked user IDs

```typescript theme={null}
getBlockedUsers(): Promise<string[]>
```

## isUserBlocked

Checks if a specific user is blocked.

**returns:** true if the user is blocked

```typescript theme={null}
isUserBlocked(userId: string): Promise<boolean>
```

## Parameter and result types

Import these types from the same SDK package. Event payloads and configuration types are linked below.

### SendMessageParams

```typescript theme={null}
export interface SendMessageParams {
  recipient: string;
  content: string;
  priority?: MessagePriority;
  replyToMsg?: string;
  contentType?: ContentType;
  replyContext?: ReplyContext;
  mediaMetadata?: MediaMetadata;
  forwardInfo?: ForwardInfo;
}
```

### MessagePriority

```typescript theme={null}
export enum MessagePriority {
  Low = 0,
  Medium = 1,
  High = 2,
  Critical = 3,
}
```

### ContentType

```typescript theme={null}
export enum ContentType {
  Text = 'text',
  Image = 'image',
  Video = 'video',
  Audio = 'audio',
  VoiceNote = 'voice_note',
  VideoNote = 'video_note',
  File = 'file',
  FileChunk = 'file_chunk',
  Poll = 'poll',
}
```

### ReplyContext

```typescript theme={null}
export interface ReplyContext {
  sender: string;
  text: string;
  timestamp?: number;
  reply_media_label?: string;
  reply_content_type?: string;
}
```

### MediaMetadata

```typescript theme={null}
export interface MediaMetadata {
  mimeType: string;
  fileName: string;
  fileSize: number;
  durationMs?: number;
  width?: number;
  height?: number;
  thumbnailBase64?: string;
  mediaId?: string;
  downloadUrl?: string;
  thumbnailUrl?: string;
  encryptionKey?: string;
  iv?: string;
  ciphertextHash?: string;
  stickerProvider?: string;
  stickerRemoteId?: string;
  stickerKind?: string;
}
```

### ForwardInfo

```typescript theme={null}
export interface ForwardInfo {
  original_sender: string;
  original_message_id: string;
  original_timestamp: number;
  forward_count: number;
}
```

### ForwardMessageParams

```typescript theme={null}
export interface ForwardMessageParams {
  originalMessageJson: string;
  newRecipient: string;
  priority?: MessagePriority;
}
```

### SendConnectionRequestParams

```typescript theme={null}
export interface SendConnectionRequestParams {
  recipient: string;
  senderName: string;
  keyPackage?: number[];
  initialMessage?: string;
}
```

### AcceptConnectionRequestParams

```typescript theme={null}
export interface AcceptConnectionRequestParams {
  recipient: string;
  accepterName: string;
  keyPackage?: number[];
}
```

### RejectConnectionRequestParams

```typescript theme={null}
export interface RejectConnectionRequestParams {
  recipient: string;
}
```

### CancelConnectionRequestParams

```typescript theme={null}
export interface CancelConnectionRequestParams {
  recipient: string;
}
```

### MessageDeliveryStats

```typescript theme={null}
export interface MessageDeliveryStats {
  message_id: string;
  sender: string;
  recipient: string;
  sent_at: number;
  delivered_at?: number;
  hop_count: number;
  transport?: TransportType;
  retry_count: number;
  latency_ms?: number;
}
```

### TransportType

```typescript theme={null}
export type TransportType = 'ble' | 'internet' | 'wifiDirect' | 'reticulum' | 'nostr';
```

## Related references

* [`message_received`](/docs/mesh-sdk/events#message_received)
* [`message_delivered`](/docs/mesh-sdk/events#message_delivered)
* [`message_failed`](/docs/mesh-sdk/events#message_failed)
* [`message_deferred`](/docs/mesh-sdk/events#message_deferred)
* [`message_retrying`](/docs/mesh-sdk/events#message_retrying)
* [`message_undeliverable`](/docs/mesh-sdk/events#message_undeliverable)
* [Event payloads](/docs/mesh-sdk/events)
* [Configuration types](/docs/mesh-sdk/configuration-types)
* [All public types](/docs/mesh-sdk/public-types)
* [Error codes](/docs/mesh-sdk/errors)
* [All API tasks](/docs/mesh-sdk/reference)
