blooio 0.1.0

Typed, low-overhead Rust client for the Blooio API (iMessage/SMS automation), with sync and async surfaces.
Documentation

blooio

Crates.io Docs.rs License

Typed, low-overhead Rust bindings for the Blooio API (iMessage / SMS automation), exposing both an async and a blocking surface from a single sans-IO core. Sync users pull no async runtime.

Features

Feature Default Description
async The async Client executor (reqwest).
sync The blocking BlockingClient executor (ureq), no tokio.
rustls TLS via rustls.
native-tls TLS via the system's native stack.
webhooks Typed webhook payloads + HMAC signature verification.
tracing Secret-redacted request instrumentation.

At least one of async / sync must be enabled (enforced at compile time).

Install

[dependencies]
blooio = "0.1"

Blocking client only, no async runtime:

[dependencies]
blooio = { version = "0.1", default-features = false, features = ["sync", "rustls", "webhooks"] }

Quick start (async)

use blooio::Client;

#[tokio::main]
async fn main() -> blooio::Result<()> {
    let client = Client::new(std::env::var("BLOOIO_API_KEY").unwrap())?;

    // Who am I?
    let me = client.account().get().await?;

    // Send a message.
    let chat = client.chat("chat-id");
    chat.send_text("hello from rust").await?;

    Ok(())
}

Quick start (blocking)

use blooio::BlockingClient;

fn main() -> blooio::Result<()> {
    let client = BlockingClient::new(std::env::var("BLOOIO_API_KEY").unwrap())?;
    client.chat("chat-id").send_text("hello from rust")?;
    Ok(())
}

The async and blocking surfaces are mirror images: the same resource handles and method names, differing only by .await.

Resources

Resource handles hang off the client and group the endpoints:

Handle Highlights
account() get
chats() / chat(id) list, send/send_text, messages, reactions, polls, typing, read receipts, backgrounds
contacts() list, create, get, update, delete, capabilities, tags
groups() list, create, get, update, delete, icons, members(id)
contact_card() get, update
facetime() call
location() list, get, refresh
numbers() list
phone_numbers() lookup, lookup_post, batch
webhooks() list, create, get, update, delete, rotate_secret, logs(id)

Builders

Endpoints with many optional fields use a fluent builder. For example, sending a message:

# async fn demo(client: blooio::Client) -> blooio::Result<()> {
let message = client
    .chat("chat-id")
    .message()
    .text("hi")
    .effect("slam")
    .use_typing_indicator(true)
    .idempotency_key("abc-123");
client.chat("chat-id").send(message).await?;
# Ok(()) }

Pagination

List endpoints expose a *_all paginator that fetches successive pages lazily:

# async fn demo(client: blooio::Client) -> blooio::Result<()> {
let mut pages = client.chats().list_all();
while let Some(page) = pages.next_page().await {
    for chat in page? {
        // ...
    }
}
// or drain everything:
let all = client.contacts().list_all().collect_all().await?;
# Ok(()) }

In the blocking client, the paginator also implements Iterator.

Escape hatch

Every endpoint is described once as a public Operation. Anything not covered by a convenience method can be sent directly:

# async fn demo(client: blooio::Client, op: impl blooio::Operation<Output = ()>) -> blooio::Result<()> {
let out = client.send(op).await?;
# Ok(()) }

Configuration

Client::new(key) uses production defaults. For more control, build a ClientConfig:

use blooio::{Client, ClientConfig};
use std::time::Duration;

# fn demo() -> blooio::Result<()> {
let config = ClientConfig::new("my-api-key")
    .with_base_url("https://backend.blooio.com/v2/api")
    .with_timeout(Duration::from_secs(10))
    .with_user_agent("my-app/1.0");
let client = Client::from_config(config)?;
# Ok(()) }

The API key is wrapped in a Secret, which zeroizes on drop and redacts itself in Debug output (api_key: [REDACTED]) — it is never logged or serialized in cleartext.

Errors

All fallible calls return blooio::Result<T>. The Error enum distinguishes Api (non-2xx, with a machine-readable code), Transport, Encode, Decode, and (with webhooks) Webhook. Match on the stable code for programmatic handling:

# fn handle(err: blooio::Error) {
if err.code() == Some("outbound_limit_reached") {
    // back off and retry later
}
# }

Webhooks

With the webhooks feature, verify and parse incoming events. The module is framework-agnostic — it does not run a server:

use blooio::webhook::{self, WebhookEvent};

# fn handle(secret: &[u8], sig_header: &str, raw_body: &[u8]) -> blooio::Result<()> {
// Verify the signature (constant-time, with replay protection).
webhook::verify_default(secret, sig_header, raw_body)?;

// Parse the typed payload.
let event = WebhookEvent::parse(raw_body)?;
if let Some(kind) = event.kind() {
    // dispatch on the message event kind
}
# Ok(()) }

Tracing

With the tracing feature, each request emits a blooio.request span carrying the method, path, status, and elapsed time. The API key is never recorded.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.