Rust SDK
The official Rust SDK for Veil Mail with idiomatic async/await, strong error types, and zero-cost abstractions.
- Full async/await support with tokio
- Typed error enum with pattern matching
- Constant-time HMAC-SHA256 webhook verification
- Lifetime-scoped resource accessors (zero cloning)
- Built on reqwest + serde_json
- Rust 2021 edition, Minimum Rust 1.63+
Installation
Add to your Cargo.toml:
Cargo.toml
[dependencies]
veilmail = "0.1"
serde_json = "1"
tokio = { version = "1", features = ["full"] }Quick Start
main.rs
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), veilmail::error::VeilMailError> {
let client = veilmail::VeilMail::new("veil_live_xxxxx")?;
let email = client.emails().send(json!({
"from": "hello@yourdomain.com",
"to": ["user@example.com"],
"subject": "Hello from Rust!",
"html": "<h1>Welcome!</h1>"
})).await?;
println!("Sent: {}", email);
Ok(())
}Configuration
Customize the client with VeilMailOptions:
config.rs
use veilmail::{VeilMail, VeilMailOptions};
let client = VeilMail::with_options("veil_live_xxxxx", Some(VeilMailOptions {
base_url: Some("https://custom-api.example.com"),
timeout_secs: Some(10),
}))?;Resources
The client exposes the same resources as the Node.js SDK:
| Resource | Description |
|---|---|
client.emails() | Send, batch send, list, get, cancel, update emails |
client.domains() | Create, verify, update, list, delete domains |
client.templates() | Create, update, preview, list, delete templates |
client.audiences() | Manage audiences and subscribers |
client.campaigns() | Create, schedule, send, pause, resume, cancel campaigns |
client.webhooks() | Manage webhook endpoints, test, rotate secrets |
client.topics() | Manage subscription topics and preferences |
client.properties() | Manage contact property definitions and values |
Send Emails
emails.rs
use serde_json::json;
// Simple send
let email = client.emails().send(json!({
"from": "hello@yourdomain.com",
"to": ["user@example.com"],
"subject": "Hello!",
"html": "<p>Hello World!</p>",
"tags": ["welcome"]
})).await?;
// With template
let email = client.emails().send(json!({
"from": "hello@yourdomain.com",
"to": ["user@example.com"],
"templateId": "tmpl_xxx",
"templateData": { "name": "Alice" }
})).await?;
// Batch send (up to 100)
let result = client.emails().send_batch(vec![
json!({
"from": "hi@yourdomain.com",
"to": ["user1@example.com"],
"subject": "Hi",
"html": "<p>Hi!</p>"
}),
json!({
"from": "hi@yourdomain.com",
"to": ["user2@example.com"],
"subject": "Hi",
"html": "<p>Hi!</p>"
}),
]).await?;Subscriber Management
subscribers.rs
use serde_json::json;
// Get subscribers for an audience
let subs = client.audiences().subscribers("audience_xxxxx");
// Add a subscriber
let subscriber = subs.add(json!({
"email": "user@example.com",
"firstName": "Alice",
"lastName": "Smith"
})).await?;
// List subscribers
let list = subs.list(Some(&[
("limit", "50"),
("status", "active"),
])).await?;
// Export as CSV
let csv = subs.export(None).await?;Error Handling
Use Rust's pattern matching to handle specific error types:
errors.rs
use veilmail::error::VeilMailError;
match client.emails().send(params).await {
Ok(email) => println!("Sent: {}", email),
Err(VeilMailError::Authentication { message, .. }) => {
eprintln!("Invalid API key: {}", message);
}
Err(VeilMailError::PiiDetected { pii_types, .. }) => {
eprintln!("PII detected: {:?}", pii_types);
}
Err(VeilMailError::RateLimit { retry_after, .. }) => {
if let Some(secs) = retry_after {
eprintln!("Rate limited, retry after {} seconds", secs);
}
}
Err(VeilMailError::Validation { message, details, .. }) => {
eprintln!("Validation: {} ({:?})", message, details);
}
Err(e) => eprintln!("Error: {}", e),
}Webhook Verification
The SDK provides a constant-time HMAC-SHA256 verification function:
webhook_handler.rs
use axum::{extract::Request, http::StatusCode, routing::post, Router};
use veilmail::webhook::verify_signature;
const WEBHOOK_SECRET: &str = "whsec_xxxxx";
async fn webhook_handler(request: Request) -> StatusCode {
let signature = request
.headers()
.get("x-signature-hash")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let body = axum::body::to_bytes(request.into_body(), 1024 * 1024)
.await
.unwrap();
let body_str = String::from_utf8_lossy(&body);
if !verify_signature(&body_str, signature, WEBHOOK_SECRET) {
return StatusCode::UNAUTHORIZED;
}
let event: serde_json::Value = serde_json::from_slice(&body).unwrap();
match event["type"].as_str() {
Some("email.delivered") => println!("Delivered: {}", event["data"]),
Some("email.bounced") => println!("Bounced: {}", event["data"]),
_ => {}
}
StatusCode::OK
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/webhooks/veilmail", post(webhook_handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}Ownership & Lifetimes
Resource accessors like client.emails() borrow the client's HTTP connection. They are lightweight references with no cloning or allocation. You can create them on each call or store them in a variable — either way, the VeilMail client must outlive them.
Required Scopes
The Rust SDK uses the same API scopes as the Authentication system. See the Node.js SDK documentation for the full scope reference.