Direct messages are essential for building engagement on Bluesky and helping users grow their following. Whether you're building a full-featured Bluesky client or adding social features to your app, implementing chat functionality can significantly boost user retention and community building. In this tutorial, we'll dive deep into Bluesky's chat API.
Why Chat Features Matter for Bluesky Growth
Before we get into the technical details, let's understand why DMs are crucial for users who want to grow their Bluesky audience:
- Direct engagement - Personal conversations create stronger connections than public replies
- Collaboration opportunities - DMs enable partnerships, guest posts, and cross-promotion
- Community building - Private conversations help foster tight-knit communities
- Networking - Reaching out to influencers and thought leaders directly
- Customer support - Brands can handle inquiries privately
Understanding the Bluesky Chat Architecture
Bluesky's chat system uses a separate set of lexicons under the chat.bsky.* namespace. Unlike the main app.bsky.* lexicons for posts and profiles, chat has its own dedicated infrastructure. Here's what you need to know:
- Conversations (convos) - A thread between two or more participants
- Messages - Individual messages within a conversation
- Participants - The users involved in a conversation
- Read state - Tracking which messages have been read
Setting Up Chat Authentication
Chat APIs require the same authentication as other Bluesky APIs, but you'll be making requests to a different service endpoint. First, authenticate to get your session tokens:
// Authenticate with Bluesky
POST https://bsky.social/xrpc/com.atproto.server.createSession
Content-Type: application/json
{
"identifier": "your-handle.bsky.social",
"password": "your-app-password"
}
// Response
{
"accessJwt": "eyJ...",
"refreshJwt": "eyJ...",
"handle": "your-handle.bsky.social",
"did": "did:plc:xxxxx"
}
Important: Chat requests go to a different endpoint than the main API. You'll typically use api.bsky.chat or the chat proxy endpoint.
Fetching Conversations (List All DMs)
To display a user's inbox with all their conversations, use the chat.bsky.convo.listConvos endpoint:
// List all conversations
GET https://api.bsky.chat/xrpc/chat.bsky.convo.listConvos
Authorization: Bearer {accessJwt}
// Optional parameters:
// ?limit=50 (max 100)
// ?cursor={cursor} (for pagination)
// Response structure
{
"cursor": "next-page-cursor",
"convos": [
{
"id": "convo-id-123",
"rev": "revision-string",
"members": [
{
"did": "did:plc:user1",
"handle": "user1.bsky.social",
"displayName": "User One",
"avatar": "https://..."
},
{
"did": "did:plc:user2",
"handle": "user2.bsky.social",
"displayName": "User Two"
}
],
"lastMessage": {
"id": "msg-id",
"text": "Hey, great post!",
"sender": { "did": "did:plc:user1" },
"sentAt": "2025-12-19T10:30:00Z"
},
"unreadCount": 2,
"muted": false
}
]
}
Loading Messages in a Conversation
Once a user selects a conversation, fetch the message history:
// Get messages in a conversation
GET https://api.bsky.chat/xrpc/chat.bsky.convo.getMessages
?convoId=convo-id-123
&limit=50
Authorization: Bearer {accessJwt}
// Response
{
"cursor": "older-messages-cursor",
"messages": [
{
"id": "msg-001",
"rev": "rev-string",
"text": "Hey! Love your content about Bluesky development.",
"sender": { "did": "did:plc:user1" },
"sentAt": "2025-12-19T10:00:00Z"
},
{
"id": "msg-002",
"rev": "rev-string",
"text": "Thanks! Are you building something on ATProtocol?",
"sender": { "did": "did:plc:user2" },
"sentAt": "2025-12-19T10:05:00Z"
}
]
}
Sending Direct Messages
To send a message, you'll need the conversation ID. If starting a new conversation, you may need to create it first:
// Send a message to an existing conversation
POST https://api.bsky.chat/xrpc/chat.bsky.convo.sendMessage
Authorization: Bearer {accessJwt}
Content-Type: application/json
{
"convoId": "convo-id-123",
"message": {
"text": "Thanks for connecting! I'd love to collaborate."
}
}
// Response includes the sent message with ID and timestamp
Starting a New Conversation
To initiate a DM with someone you haven't messaged before, use getConvoForMembers which will return an existing conversation or create a new one:
// Get or create a conversation with specific users
GET https://api.bsky.chat/xrpc/chat.bsky.convo.getConvoForMembers
?members=did:plc:targetuser
Authorization: Bearer {accessJwt}
// This returns a conversation object
// Then send your first message using sendMessage
Marking Messages as Read
To update the read state and clear unread counts, call the update endpoint after a user views messages:
// Update read state
POST https://api.bsky.chat/xrpc/chat.bsky.convo.updateRead
Authorization: Bearer {accessJwt}
Content-Type: application/json
{
"convoId": "convo-id-123",
"messageId": "latest-read-msg-id"
}
Real-Time Message Updates
For a great user experience, you'll want to show new messages in real-time. Bluesky provides event streams for chat updates. Here's how to subscribe:
// Subscribe to chat events via WebSocket or polling
// The exact implementation depends on your platform
// Polling approach (simpler but less efficient):
// Poll getMessages or listConvos every few seconds
// WebSocket approach (better UX):
// Connect to the chat event stream for instant updates
// Events include: new messages, read receipts, typing indicators
Handling Chat Privacy and Settings
Users can control who can message them. Respect these settings in your app:
- Accept messages from everyone - Anyone can start a conversation
- Accept from followers only - Only people they follow can DM
- Accept from following only - Only people following them can DM
- Muted conversations - User won't get notifications but can still read
// Check if you can message a user
GET https://api.bsky.chat/xrpc/chat.bsky.convo.getConvoAvailability
?members=did:plc:targetuser
Authorization: Bearer {accessJwt}
// Response indicates if messaging is allowed
Building Features That Drive Bluesky Growth
Now that you understand the chat API, here are ideas for features that help users grow their Bluesky presence:
- Smart notifications - Alert users when influential accounts message them
- Message templates - Help users craft professional outreach messages
- Conversation insights - Show response rates and engagement metrics
- Quick replies - Saved responses for common questions
- DM scheduling - Send messages at optimal times for engagement
- Unread highlights - Prioritize conversations with high-follower accounts
Best Practices for Bluesky Chat Implementation
Follow these guidelines to build a great chat experience:
- Cache aggressively - Store conversation lists and messages locally to reduce API calls
- Handle rate limits - Implement exponential backoff for API requests
- Respect user privacy - Never expose message content in logs or analytics
- Optimize for mobile - DMs are often accessed on phones, so optimize performance
- Show typing indicators - Real-time feedback makes conversations feel alive
- Support rich content - Handle links, mentions, and eventually media in messages
Error Handling for Chat APIs
Chat APIs can fail for various reasons. Handle these common errors gracefully:
- User blocked you - Show a friendly message that the user isn't accepting DMs
- Rate limited - Queue messages and retry with backoff
- Conversation not found - The conversation may have been deleted
- Authentication expired - Refresh tokens and retry
What's Next for Bluesky Chat?
The Bluesky team continues to enhance chat features. Stay tuned for:
- Group conversations - Chat with multiple participants
- Media messages - Send images and videos in DMs
- Message reactions - React to messages with emoji
- End-to-end encryption - Enhanced privacy for sensitive conversations
Monitor Bluesky Trends While Building
While you're building chat features, stay on top of what's happening on Bluesky:
- Trending Hashtags - See what topics are hot right now
- Bluesky Tools - Get notified when people discuss your app or topics you care about
Building great chat features will set your Bluesky client apart and help your users build meaningful connections. Good luck, and happy coding!