clicksend-rs 0.1.1

Unofficial ClickSend SDK for Rust (async + optional blocking).
Documentation

clicksend-rs

Unofficial ClickSend SDK for Rust.

  • async-first; optional blocking feature uses reqwest::blocking (no tokio leak)
  • namespaced API: client.sms().send(), client.account().get()
  • redacted Debug (api key never logged)
  • timeouts + user-agent + retry built into the builder
  • tracing spans on every request
  • webhook parsers for inbound SMS / delivery receipts
  • escape hatch: client.raw_request(method, path) for endpoints not yet wrapped

Install

[dependencies]
clicksend-rs = "0.1"

# or with blocking:
clicksend-rs = { version = "0.1", features = ["blocking"] }

Auth

ClickSend uses HTTP basic with username + api_key. Grab both from dashboard → API credentials.

Async

use clicksend_rs::{Client, SmsMessage, SmsMessageCollection};
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let c = Client::builder("user", "key")
        .timeout(Duration::from_secs(15))
        .retry(clicksend_rs::RetryConfig::enabled(3))
        .build()?;

    let coll = SmsMessageCollection::new(vec![
        SmsMessage::new("+1234567890", "hello from rust"),
    ]);

    // free — just prices
    let est = c.sms().price(&coll).await?;
    println!("{:?}", est.data);

    // costs money
    // c.sms().send(&coll).await?;

    let acct = c.account().get().await?;
    println!("balance: {:?}", acct.data.unwrap_or_default().balance);

    Ok(())
}

Blocking

use clicksend_rs::{BlockingClient, SmsMessage, SmsMessageCollection};

let c = BlockingClient::new("user", "key");
let coll = SmsMessageCollection::new(vec![SmsMessage::new("+1234567890", "hi")]);
c.sms().price(&coll)?;

Webhook parsing

use clicksend_rs::webhook::{parse_inbound_sms, parse_delivery_receipt};

// in your HTTP handler:
let inbound = parse_inbound_sms(&body)?;
let receipt = parse_delivery_receipt(&body)?;

Endpoints implemented

Resource Method Path
account().get() GET /account
sms().send() POST /sms/send (costs money)
sms().price() POST /sms/price (free)
sms().history() GET /sms/history
sms().receipts() GET /sms/receipts
sms().inbound() GET /sms/inbound
sms().cancel() PUT /sms/{id}/cancel
sms().cancel_all() PUT /sms/cancel-all
mms().send() POST /mms/send
voice().send() POST /voice/send
email().send() POST /email/send

ClickSend has ~30 more (campaigns, contacts, fax, post mail, subaccounts). For those, use the escape hatch:

let resp: ApiEnvelope<serde_json::Value> = client
    .raw_request(reqwest::Method::GET, "/contacts/lists")
    .send().await?
    .json().await?;

Error handling

match c.sms().send(&coll).await {
    Ok(env) => { /* env.data has results */ }
    Err(ClickSendError::Unauthorized) => { /* bad creds */ }
    Err(ClickSendError::RateLimited { retry_after_secs }) => { /* back off */ }
    Err(ClickSendError::Api { code, message, .. }) => {
        // HTTP 200 but logical error (e.g. bad sender id, blocked country)
    }
    Err(e) => { /* http / decode / other */ }
}

Live tests

cp .env.example .env  # then fill in
cargo test --test live -- --ignored sms_price_async --nocapture

All live tests are #[ignore] so they don't run by accident. sms_send costs money — gated.

License

0BSD.