Skip to content

send_direct_message

Send a plain-text Bluesky direct message (chat.bsky.convo.sendMessage). Target either an existing conversation by convoId, or a recipient by handle or DID via member — in that case the conversation is resolved (or created) automatically with chat.bsky.convo.getConvoForMembers, so you can DM someone without listing conversations first. Every call is proxied to the Bluesky chat service (did:web:api.bsky.chat) — DMs are not served by the PDS.

Messages are sent as plain text: mentions, links, and hashtags appear as literal text (no facets are attached).

Authentication

Required: Yes (Private tool)

Direct messages additionally require that the app password used for the session was created with "Allow access to your direct messages" enabled (bsky.app → Settings → Privacy and Security → App Passwords). Without it, the chat service rejects every call with 401 "Bad token scope". The recipient's chat settings must also allow messages from your account.

Parameters

Exactly one of convoId or member must be provided.

convoId (optional)

  • Type: string
  • Description: Id of an existing conversation to send into, from list_conversations or a previous send_direct_message response

member (optional)

  • Type: string
  • Description: Handle (e.g. bob.bsky.social) or DID of the account to message. Handles are resolved to DIDs, then the conversation is looked up (or created) via chat.bsky.convo.getConvoForMembers

text (required)

  • Type: string
  • Constraints: 1-1000 graphemes and at most 10000 UTF-8 bytes (the chat.bsky.convo.defs#messageInput lexicon limits)
  • Description: Plain-text message body

Response

Tool results are returned as stringified JSON text. The shape below is illustrative.

typescript
{
  success: boolean;
  message: string;        // Human-readable status message
  convoId: string;        // Reuse for follow-up messages to skip resolution
  recipientDid?: string;  // Present only when member was used
  sentMessage: {
    id?: string;
    senderDid?: string;
    text?: string;
    sentAt?: string;      // ISO 8601 timestamp
    deleted: boolean;
    hasEmbed: boolean;
  };
}

Examples

Send Into a Known Conversation

json
{
  "convoId": "3kx...abc",
  "text": "On my way!"
}

Message Someone by Handle

json
{
  "member": "bob.bsky.social",
  "text": "Hi Bob — loved your latest post."
}

Response:

json
{
  "success": true,
  "message": "Direct message sent successfully",
  "convoId": "3kx...abc",
  "recipientDid": "did:plc:abc123",
  "sentMessage": {
    "id": "3ky...def",
    "senderDid": "did:plc:self",
    "text": "Hi Bob — loved your latest post.",
    "sentAt": "2026-06-10T12:00:00.000Z",
    "deleted": false,
    "hasEmbed": false
  }
}

Message Someone by DID

json
{
  "member": "did:plc:abc123",
  "text": "Hello!"
}

Error Handling

Common Errors

Authentication Required

json
{
  "error": "Authentication required",
  "code": "AUTHENTICATION_FAILED"
}

App Password Without DM Access

A 401 "Bad token scope" from the chat service means the current app password was created without direct-message access. The error message includes guidance to create a new app password with "Allow access to your direct messages" enabled.

Both or Neither Target Provided

json
{
  "error": "Invalid parameters: : Provide exactly one of convoId or member",
  "code": "VALIDATION_ERROR"
}

Message Too Long

json
{
  "error": "Message text is 1024 graphemes; the direct-message maximum is 1000.",
  "code": "VALIDATION_ERROR"
}

Recipient Cannot Be Messaged

The chat service rejects the call when the recipient's settings do not allow DMs from your account, or when the recipient has chat disabled (their member entry carries chatDisabled: true).

See Also