Skip to Content
Dial v1 live 🎉
SdkMessaging

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.

Next Steps

Last updated on