Messaging
Send and receive wallet-to-wallet messages with support for text, media, and reactions. All users are identified by their wallet addresses—no phone numbers or centralized accounts required.
Messaging Modalities
Dial supports multiple messaging providers for different use cases:
🗄️ Bucket Messaging (Current)
Powered by s3worm
Basic messaging features for simple applications:
- Direct messages (DMs) between wallet addresses
- Topic-based or peer-based threading
- Group messaging with invites
- Media attachments
- Thread management by platform developers
💬 In-Room Messaging
For party lines and video conferencing
Real-time chat within active video calls and conference rooms:
- Ephemeral messaging during calls
- Share links, media, and reactions in real-time
- Integrated with video conferencing features
🔐 Matrix Messaging (Coming Soon)
Powered by Matrix.org
Next-generation end-to-end encrypted messaging:
- ✨ Full E2EE for private and secure conversations
- 🔒 Decentralized, federated messaging
- 👥 Rich group messaging and rooms
- 🌐 Interoperable with the Matrix ecosystem
- 🔑 Wallet-based identity and authentication
Roadmap: Q2 2025
Threading Models
Peer-Based Threading (Default)
One unified conversation thread between two wallet addresses:
// Alice and Bob have ONE conversation thread
const conversation = await userDialer.messages.getConversation({
with: '0xBob...',
threadModel: 'peer' // default
});
// All messages between Alice and Bob go here
await userDialer.messages.send({
to: '0xBob...',
content: 'Hello!'
});Topic-Based Threading
Multiple conversation threads (topics) between the same users:
// Alice and Bob can have MULTIPLE topic threads
const workThread = await userDialer.messages.createThread({
participants: ['0xBob...'],
topic: 'Work Project',
threadModel: 'topic'
});
const casualThread = await userDialer.messages.createThread({
participants: ['0xBob...'],
topic: 'Weekend Plans',
threadModel: 'topic'
});
// Send to specific topic
await userDialer.messages.send({
threadId: workThread.id,
content: 'Updated the proposal'
});Sending Messages
Text Messages
const message = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
content: 'Hey! How are you?',
type: 'text'
});
console.log('Message sent:', message.id);Media Messages
// Image
const imageMessage = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
type: 'image',
media: {
file: imageFile,
caption: 'Check this out!'
}
});
// Video
const videoMessage = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
type: 'video',
media: {
file: videoFile,
thumbnail: thumbnailFile,
caption: 'My latest video'
}
});
// Audio
const audioMessage = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
type: 'audio',
media: {
file: audioFile,
duration: 45 // seconds
}
});Message Object
interface Message {
id: string;
from: string; // Sender wallet address
to: string; // Recipient wallet address
content: string;
type: 'text' | 'image' | 'video' | 'audio';
media?: {
url: string;
thumbnailUrl?: string;
size: number;
mimeType: string;
};
timestamp: Date;
status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
reactions?: Reaction[];
}Receiving Messages
userDialer.on('message:received', (message) => {
console.log(`New message from ${message.from}:`, message.content);
});Conversations
Get Conversations
// Get all conversations
const conversations = await userDialer.messages.getConversations();
// Get conversation with specific wallet
const conversation = await userDialer.messages.getConversation({
with: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
});Conversation Object
interface Conversation {
id: string;
participants: string[]; // Wallet addresses
lastMessage: Message;
unreadCount: number;
updatedAt: Date;
}Message History
// Get messages with pagination
const messages = await userDialer.messages.getMessages({
with: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
limit: 50,
offset: 0
});
// Load older messages
const olderMessages = await userDialer.messages.getMessages({
with: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
before: messages[0].timestamp,
limit: 50
});Read Receipts
Mark as Read
await userDialer.messages.markAsRead(message.id);Listen for Read Status
userDialer.on('message:read', (message) => {
console.log(`Message ${message.id} was read`);
});Reactions
Add Reaction
await userDialer.messages.addReaction({
messageId: message.id,
emoji: '👍'
});Remove Reaction
await userDialer.messages.removeReaction({
messageId: message.id,
emoji: '👍'
});Reaction Object
interface Reaction {
emoji: string;
from: string; // Wallet address
timestamp: Date;
}Typing Indicators
Send Typing Status
// Start typing
await userDialer.messages.startTyping({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
});
// Stop typing
await userDialer.messages.stopTyping({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
});Listen for Typing
userDialer.on('typing:start', ({ from }) => {
console.log(`${from} is typing...`);
});
userDialer.on('typing:stop', ({ from }) => {
console.log(`${from} stopped typing`);
});Message Encryption
Bucket Messaging (s3worm)
Messages are stored securely but not end-to-end encrypted by default:
const message = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
content: 'Secure message',
provider: 'bucket'
});Matrix Messaging (E2EE - Coming Soon)
Full end-to-end encryption for maximum privacy:
const message = await userDialer.messages.send({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
content: 'Private encrypted message',
provider: 'matrix', // E2EE enabled by default
encrypted: true
});Group Messaging
Create Group
const group = await userDialer.messages.createGroup({
name: 'Web3 Builders',
participants: [
'0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
'0x8ba1f109551bD432803012645Ac136ddd64DBA72'
],
avatar: avatarFile,
provider: 'bucket' // 's3worm' bucket messaging or 'matrix' (coming soon)
});Invite to Group
// Invite additional members
await userDialer.messages.inviteToGroup({
groupId: group.id,
addresses: ['0x...', '0x...']
});Send to Group
await userDialer.messages.send({
to: group.id,
content: 'Hello group!',
type: 'text'
});Group Management
// Add member
await userDialer.messages.addGroupMember({
groupId: group.id,
address: '0x...'
});
// Remove member
await userDialer.messages.removeGroupMember({
groupId: group.id,
address: '0x...'
});
// Leave group
await userDialer.messages.leaveGroup(group.id);
// Update group info
await userDialer.messages.updateGroup({
groupId: group.id,
name: 'Updated Name',
avatar: newAvatarFile
});Complete Example: React Chat Component
import { useState, useEffect } from 'react';
import { DialClient, Message } from '@dial/sdk';
export function ChatInterface({ userDialer, recipient }) {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
useEffect(() => {
// Load message history
loadMessages();
// Listen for new messages
userDialer.on('message:received', (message) => {
if (message.from === recipient) {
setMessages(prev => [...prev, message]);
}
});
// Listen for typing
userDialer.on('typing:start', ({ from }) => {
if (from === recipient) setIsTyping(true);
});
userDialer.on('typing:stop', ({ from }) => {
if (from === recipient) setIsTyping(false);
});
return () => {
userDialer.off('message:received');
userDialer.off('typing:start');
userDialer.off('typing:stop');
};
}, [userDialer, recipient]);
const loadMessages = async () => {
const history = await userDialer.messages.getMessages({
with: recipient,
limit: 50
});
setMessages(history);
};
const sendMessage = async () => {
if (!input.trim()) return;
await userDialer.messages.send({
to: recipient,
content: input,
type: 'text'
});
setInput('');
};
const handleInputChange = async (value: string) => {
setInput(value);
// Send typing indicator
if (value.length > 0) {
await userDialer.messages.startTyping({ to: recipient });
// Auto-stop after 3 seconds
setTimeout(async () => {
await userDialer.messages.stopTyping({ to: recipient });
}, 3000);
}
};
return (
<div className="chat-interface">
<div className="messages">
{messages.map(msg => (
<div
key={msg.id}
className={msg.from === recipient ? 'received' : 'sent'}
>
<p>{msg.content}</p>
<span>{msg.timestamp.toLocaleTimeString()}</span>
</div>
))}
{isTyping && <div className="typing">Typing...</div>}
</div>
<div className="input-area">
<input
value={input}
onChange={(e) => handleInputChange(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
);
}Message Deletion
// Delete for yourself
await userDialer.messages.delete(message.id, { forEveryone: false });
// Delete for everyone (within 1 hour)
await userDialer.messages.delete(message.id, { forEveryone: true });Search Messages
const results = await userDialer.messages.search({
query: 'hello',
with: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
limit: 20
});Platform Developer Features
Thread/Room Management
Platform developers can manage threads and rooms for their users:
// Create managed thread
const thread = await userDialer.messages.createManagedThread({
participants: ['0xAlice...', '0xBob...'],
topic: 'Support Ticket #123',
metadata: {
ticketId: 123,
status: 'open'
}
});
// List all managed threads
const threads = await userDialer.messages.listManagedThreads({
filters: {
status: 'open'
}
});
// Archive thread
await userDialer.messages.archiveThread(thread.id);Provider Selection
Choose your messaging provider based on your needs:
// Use bucket messaging (default)
const bucketConv = await userDialer.messages.getConversation({
with: '0x...',
provider: 'bucket'
});
// Use Matrix (coming soon)
const matrixConv = await userDialer.messages.getConversation({
with: '0x...',
provider: 'matrix'
});Wallet-Based Identity
All messaging is tied to wallet addresses:
- ✅ No phone numbers required
- ✅ No email addresses required
- ✅ Native Web3 identity
- ✅ Cross-platform compatibility
- ✅ Self-sovereign identity
Users are identified by their wallet address, and their Dial Profile provides display names, avatars, and other metadata.