| Crates.io | mboxlabs-mailbox |
| lib.rs | mboxlabs-mailbox |
| version | 0.1.0 |
| created_at | 2025-11-23 06:23:12.590669+00 |
| updated_at | 2025-11-23 06:23:12.590669+00 |
| description | A lightweight, pluggable mailbox/queue kernel inspired by the Erlang Actor Model |
| homepage | https://github.com/mboxlabs/mailbox.rust |
| repository | https://github.com/mboxlabs/mailbox.rust |
| max_upload_size | |
| id | 1946177 |
| size | 82,811 |
A lightweight, pluggable "mailbox/queue" kernel that treats all communication as "delivering a letter to an address". An address represents a unique mailbox, accessible through different transport protocols (like
mem:,mailto:,slack:) via pluggable Providers. Build fault-tolerant, distributed, human-machine collaborative systems using mailbox-based async communication.
| Traditional Approach | Mailbox Approach |
|---|---|
| โ Shared state + locks | โ Independent mailboxes + messages |
| โ Callback hell | โ
async/await seamless integration |
| โ Complex human-machine collaboration | โ Human = a mailbox address |
| โ Offline scenarios difficult | โ Messages auto-buffered and retried |
๐ Tribute: Erlang's Actor Model "In the 1980s, when computers were as big as rooms, Erlang's creators proposed a revolutionary idea: Each process has its own mailbox, communicates via messages, and crashes are part of the design, not failures" โโ Joe Armstrong, Robert Virding, Mike Williams
Mailbox is deeply inspired by Erlang's Actor model, but with key evolution:
| Erlang (1986) | Mailbox (Today) | Why It Matters |
|---|---|---|
Pid ! Message |
send({ to: 'protocol://address' }) |
Address is identity, protocol is routing: address part is the mailbox's unique ID. protocol (e.g. mem, mailto) determines routing. Same address accessible via different protocols. |
| In-process FIFO mailbox | Pluggable Providers | Transport-agnostic: Seamlessly switch between memory/email/Wechat/Mastodon |
| Same-node communication | Cross-network, cross-organization | Truly distributed: Humans and machines participate equally |
Add to your Cargo.toml:
[dependencies]
mboxlabs-mailbox = { path = "path/to/mailbox" }
tokio = { version = "1", features = ["full"] }
serde_json = "1"
use mboxlabs_mailbox::{Mailbox, OutgoingMail, MailMessage};
use mboxlabs_mailbox::providers::memory::MemoryProvider;
use serde_json::json;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create mailbox instance and register a memory provider
let mut mailbox = Mailbox::new();
mailbox.register_provider(Box::new(MemoryProvider::new()));
// 2. Subscribe to an address and define message handler
let subscription = mailbox.subscribe(
"mem:service@example.com/inbox".parse()?,
Box::new(|message| {
Box::pin(async move {
println!("Received message! From: {}", message.from);
println!("Content: {:?}", message.body);
})
})
).await?;
println!("Mailbox established, listening on 'mem:service@example.com/inbox'...");
// 3. Send a message to that address
mailbox.post(OutgoingMail {
id: None,
from: "mem:client@example.com/user-1".parse()?,
to: "mem:service@example.com/inbox".parse()?,
body: json!({ "text": "Hello, Mailbox!" }),
headers: HashMap::new(),
meta: HashMap::new(),
}).await?;
// Give async tasks time to complete
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
Ok(())
}
Output:
Mailbox established, listening on 'mem:service@example.com/inbox'...
Received message! From: mem:client@example.com/user-1
Content: Object {"text": String("Hello, Mailbox!")}
A MailAddress is the foundation of the entire system, serving as the unique universal identifier for any destination. It follows the RFC 3986 URI specification.
protocol:user@physical_address[/logical_address]mem:api@myservice.com/utils/greeterA mailbox address consists of three parts:
protocol: Specifies the transport method for messages (e.g., mem for memory bus, mailto for email). It tells Mailbox which provider should handle the message.user@physical_address: The globally unique, protocol-agnostic ID of the logical mailbox or service. The same physical address can be accessed via different protocols (e.g., mem:api@myservice.com and mailto:api@myservice.com point to the same logical entity)./logical_address (optional): An optional path for internal routing. For example, when combined with tool-rpc, it can route messages to specific tools within a larger service, allowing one physical address to serve as a unified gateway for multiple logical functions.let subscription = mailbox.subscribe(
"mem:service/inbox".parse()?,
Box::new(|msg| {
Box::pin(async move {
println!("Got: {:?}", msg.body);
})
})
).await?;
// Unsubscribe when done
subscription.unsubscribe().await?;
Auto-acknowledgment:
let msg = mailbox.fetch(
"mem:service/inbox".parse()?,
FetchOptions::default()
).await?;
if let Some(msg) = msg {
println!("Fetched: {:?}", msg.message.body);
// Auto-acknowledged
}
Manual acknowledgment:
let msg = mailbox.fetch(
"mem:service/inbox".parse()?,
FetchOptions {
manual_ack: true,
ack_timeout: Some(5000), // 5 seconds
}
).await?;
if let Some(msg) = msg {
// Process message...
msg.ack().await?; // Acknowledge
// Or: msg.nack(true).await?; // Negative ack with requeue
}
let status = mailbox.status("mem:service/inbox".parse()?).await?;
println!("State: {}", status.state);
println!("Unread: {:?}", status.unread_count);
Implement the MailboxProvider trait to create custom providers:
#[async_trait]
pub trait MailboxProvider: Send + Sync {
fn protocol(&self) -> &str;
async fn send(&self, message: MailMessage) -> Result<MailMessage>;
async fn subscribe(
&self,
address: Url,
callback: Box<dyn Fn(MailMessage) -> BoxFuture<'static, ()> + Send + Sync>
) -> Result<Box<dyn Subscription>>;
async fn fetch(&self, address: Url, options: FetchOptions) -> Result<Option<AckableMessage>>;
async fn status(&self, address: Url) -> Result<MailboxStatus>;
fn generate_id(&self) -> String;
}
This library is WASM-compatible! Build for different targets using wasm-pack:
wasm-pack build --target nodejs
wasm-pack build --target web
wasm-pack build --target bundler
The library uses conditional compilation to support both native and WASM environments:
tokio::spawn for async taskswasm_bindgen_futures::spawn_localjs feature for WASMwasm-bindgen-futuresFor detailed WASM testing instructions, examples, and troubleshooting, see WASM_TESTING.md.
Run the test suite:
cargo test
Tests include:
See examples/p2p_rpc.rs for a complete example of request-reply pattern:
cargo run --example p2p_rpc
This demonstrates:
tokio: Async runtime (native) / minimal features (WASM)serde / serde_json: Serializationurl: URL parsinguuid: UUID v4 generation with WASM supportasync-trait: Async trait supportchrono: Timestamp handlingonce_cell: Lazy static initialization (modern alternative to lazy_static)dashmap: Concurrent hash mapwasm-bindgen: WASM interopFor WASM target, additional dependencies are configured:
wasm-bindgen-futures: Async support for WASMSee WASM_TESTING.md for detailed WASM build and testing instructions.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE-MIT file for details.
Inspired by:
Remember: In the Mailbox world, every mailbox is an independent universe, and messages are messengers traveling through space and time ๐