Send email with Rust

reqwest and serde, with the payload typed at compile time.

You need a verified sending domain and an API key from Settings › API Keys. If neither exists yet, the quickstart covers both in a couple of minutes.

Install

reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

Send

use serde::Serialize;
 
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Email<'a> {
    from: &'a str,
    to: &'a str,
    subject: &'a str,
    html: &'a str,
    idempotency_key: &'a str,
}
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("SENDING_API_KEY")?;
    let res = reqwest::Client::new()
        .post("https://sending.dev/api/v1/emails")
        .bearer_auth(key)
        .json(&Email {
            from: "Acme <[email protected]>",
            to: "[email protected]",
            subject: "Welcome",
            html: "<p>You're in.</p>",
            idempotency_key: "welcome-sara-001",
        })
        .send()
        .await?;
    println!("{}", res.status());
    Ok(())
}

A successful call answers 202 with the message id: accepted and queued, not yet delivered. Delivery, opens, bounces and complaints arrive later, on webhooks or in the dashboard.

What changes in Rust

serde(rename_all = "camelCase") is what turns idempotency_key into idempotencyKey. Without it the field is simply absent and the request is refused.

The thing that catches people

reqwest::Client is meant to be built once and reused: constructing one per send creates a new connection pool every time, and it shows up as latency long before it shows up as an error.

Next

  • All the fields: cc and bcc, reply-to, attachments, templates, scheduling.
  • Webhooks: delivery, bounce and complaint events, signed with HMAC.
  • Domains: SPF, DKIM and MAIL FROM, and why sending is refused until they are in place.
  • MCP: the same operations as tools, when the one writing the code is an agent.

Nearby: Compiled languages

All 30 stacks