Skip to content

Sent has a Rust SDK

SMS, WhatsApp, and RCS through one crate, built on Tokio, reqwest and rustls, with serde types throughout.

Emily Zhang

An install bar reading cargo add sent over the Rust crab drawn in orange plus signs

sent is on crates.io. It’s the Sent API in typed async Rust: SMS, WhatsApp, and RCS through one crate.

Shell
cargo add sent tokio --features tokio/full

The crate requires Rust 1.85 or later and runs on Tokio. The client is built on reqwest with rustls, so nothing links against the system’s TLS, and the types are serde.

Send a message

rust
use sent::models::SendMessageV3Request;
use sent::Client;

#[tokio::main]
async fn main() -> sent::Result<()> {
    let client = Client::from_env()?; // reads SENT_API_KEY

    let request: SendMessageV3Request = serde_json::from_value(serde_json::json!({
        "to": ["+14155551234"],
        "channel": ["sms"],
        "template": {
            "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
            "parameters": { "name": "Ada", "order_id": "12345" }
        }
    }))?;

    let sent = client.messages().send(&request).await?;
    println!("{:?}", sent.status);
    Ok(())
}

Every request type is a plain struct with Default, so if you’d rather not go through JSON, SendMessageV3Request { to: vec!["+14155551234".into()], ..Default::default() } builds the same request.

What’s in the crate

Coverage. All 82 operations of API v3 are async methods on the client, and every response is a struct. Resources use the same names as the other Sent SDKs: client.contacts().list(&params), client.webhooks().rotate_secret(id, &request), client.voice().calls().retrieve(id).

Pagination. A list method takes a params struct and returns a Page with the items and what the API says about the pages around it. Its list_all twin walks every page.

rust
let page = client.contacts().list(&params).await?;
println!("{} of {:?}, more: {}", page.items.len(), page.pagination.total_count, page.has_more());

let every_contact = client.contacts().list_all(&params).await?;

Retries and idempotency. Every non-GET request carries an Idempotency-Key the client generates. The client retries on 429 and 5xx, twice by default, honoring Retry-After, and sends the same key on each retry - so a retried write is performed once. max_retries(0) disables retries.

Errors. A refused request is Error::Api, with the status, Sent’s error code, the message, per-field details, and a request ID for support. A request that never got an answer is Error::Transport. A 2xx whose body isn’t what the API documents is Error::Decode.

rust
match client.messages().send(&request).await {
    Ok(sent) => println!("{:?}", sent.status),
    Err(Error::Api(failure)) => eprintln!("{} {}: {}", failure.status, failure.code, failure.message),
    Err(Error::Transport(error)) => eprintln!("could not reach Sent: {error}"),
    Err(error) => eprintln!("{error}"),
}

Forward compatibility. Sent adds fields, enum values and resources without a major version. Structs ignore unknown fields, and every enum has an Other(String) variant, so a value added later decodes instead of failing the response.

Webhooks. Subscriptions are managed through client.webhooks(). Inbound events are typed under sent::models: MessageEvent, InboundMessageEvent, TemplateEvent, ChannelEvent, ContactEvent and CallEvent, each with a typed payload. Decode with serde_json::from_slice::<MessageEvent>(&body).

How it’s built

The crate is rendered from Sent’s published OpenAPI document by our SDK pipeline. Every release passes two checks: the spec’s response examples decode into the crate’s types, and every operation runs against a mock of the spec. Generated files are regenerated on every API change. The client, errors and pagination are hand-written.

Get started

Keep your key on the server, use sk_test_ keys for the sandbox, and tell us how it goes.

Share

Published on

Read More

  • Marathon runners crossing the Brooklyn Bridge, with a text reading: Alex just reached halfway! 13.1 miles down.

    We built an NYC marathon tracker that texts you

    Runners share a link, friends pick the milestones they want, and everyone gets a text as the runner reaches them. Here’s how we built it in an afternoon with the Sent API.

    Joshua Wax and Emily Zhang

  • A Claude chat box with the Sent connector being switched on

    Sent is now an official Claude connector

    Send SMS, WhatsApp, and RCS from Claude, check delivery, look up a number, and see what you need before you text a new country, all through the same gates as the API.

    Emily Zhang

  • The Sent and Zapier app icons side by side on a field of blue dots

    Sent is now a native app on Zapier

    Start a thread off a new database row, send reminders off a calendar event, and route replies into your CRM, with no webhook endpoints to host.

    Emily Zhang

One API. Every channel.

Get Started