Sent has a Rust SDK
SMS, WhatsApp, and RCS through one crate, built on Tokio, reqwest and rustls, with serde types throughout.
Emily Zhang

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


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

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

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
sentis on crates.io. It’s the Sent API in typed async Rust: SMS, WhatsApp, and RCS through one crate.The crate requires Rust 1.85 or later and runs on Tokio. The client is built on
reqwestwithrustls, so nothing links against the system’s TLS, and the types areserde.Send a message
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(¶ms),client.webhooks().rotate_secret(id, &request),client.voice().calls().retrieve(id).Pagination. A list method takes a params struct and returns a
Pagewith the items and what the API says about the pages around it. Itslist_alltwin walks every page.let page = client.contacts().list(¶ms).await?; println!("{} of {:?}, more: {}", page.items.len(), page.pagination.total_count, page.has_more()); let every_contact = client.contacts().list_all(¶ms).await?;Retries and idempotency. Every non-GET request carries an
Idempotency-Keythe client generates. The client retries on 429 and 5xx, twice by default, honoringRetry-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 isError::Transport. A 2xx whose body isn’t what the API documents isError::Decode.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 undersent::models:MessageEvent,InboundMessageEvent,TemplateEvent,ChannelEvent,ContactEventandCallEvent, each with a typed payload. Decode withserde_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.