Overview
The Rust SDK is designed for native applications where you need tracking outside of a browser context:
- Desktop apps - Tauri, native GUI applications
- CLI tools - Command-line applications
- Embedded systems - IoT devices, kiosks
- Backend services - Rust-based APIs
For anonymous tracking before user sign-in, use fingerprint. See Device Tracking for details.
Installation
The SDK is async and requires a tokio runtime. Most async frameworks (Tauri, Axum, Actix) already include tokio.
Quick Start
use outlit::{Outlit, email, fingerprint};
#[tokio::main]
async fn main() -> Result<(), outlit::Error> {
let client = Outlit::builder("pk_your_public_key")
.build()?;
// Track with email (resolves immediately)
client.track("feature_used", email("user@example.com"))
.property("feature", "export")
.send()
.await?;
// Flush before exit
client.shutdown().await?;
Ok(())
}
Configuration
use std::time::Duration;
let client = Outlit::builder("pk_xxx")
.api_host("https://app.outlit.ai") // optional
.flush_interval(Duration::from_secs(10))
.max_batch_size(100)
.timeout(Duration::from_secs(10))
.build()?;
Options
Your organization’s public key.
api_host
&str
default:"https://app.outlit.ai"
API endpoint for sending events.
How often to automatically flush events.
Maximum events per batch before auto-flush.
API Reference
client.track(event_name, identity)
Track a custom event.
// With email
client.track("subscription_upgraded", email("user@example.com"))
.property("plan", "pro")
.property("mrr", 99)
.send()
.await?;
// With user_id
client.track_by_user_id("feature_used", user_id("usr_123"))
.email("user@example.com") // optional, for linking
.property("feature", "export")
.send()
.await?;
// With fingerprint (device tracking)
client.track_by_fingerprint("app_opened", fingerprint("device_abc123"))
.property("version", "1.2.0")
.send()
.await?;
Builder Methods
Add email to the event (if not primary identity).
Add user ID to the event.
Add device fingerprint for linking.
Add a property to the event. Call multiple times for multiple properties.
Set custom timestamp (milliseconds since epoch).
client.identify(identity)
Identify a user and optionally link device events.
client.identify(email("user@example.com"))
.user_id("usr_123")
.fingerprint("device_abc123") // Links this device's events
.trait_("name", "Jane Doe")
.trait_("plan", "enterprise")
.send()
.await?;
Device identifier. All events tracked with this fingerprint are retroactively linked to the user.
Add a trait to the user profile.
Track contact progression through your product lifecycle.
// Mark user as activated
client.user()
.activate(email("user@example.com"))
.property("flow", "onboarding")
.send()
.await?;
// Mark user as engaged
client.user()
.engaged(email("user@example.com"))
.send()
.await?;
// Mark user as inactive
client.user()
.inactive(email("user@example.com"))
.property("reason", "no_login_30_days")
.send()
.await?;
// With fingerprint
client.user()
.activate_by_fingerprint(fingerprint("device_abc123"))
.send()
.await?;
Account Billing Methods
Track account billing status by domain.
// Mark account as paid
client.customer()
.paid("acme.com")
.property("plan", "pro")
.property("amount", 99)
.send()
.await?;
// Mark account as trialing
client.customer()
.trialing("acme.com")
.property("trial_ends", "2024-02-15")
.send()
.await?;
// Mark account as churned
client.customer()
.churned("acme.com")
.property("reason", "cancelled")
.send()
.await?;
client.flush()
Immediately send all queued events.
client.shutdown()
Gracefully shutdown: flushes remaining events and stops the timer.
client.shutdown().await?;
Always call shutdown() or flush() before your application exits to ensure all events are sent.
Tauri Integration
For Tauri desktop apps, initialize the client in your Rust backend.
This example uses the uuid crate for generating device IDs. Add it with cargo add uuid --features v4.
use outlit::{Outlit, email, fingerprint};
use tauri::Manager;
use std::sync::Arc;
// Store device fingerprint persistently
fn get_or_create_device_id(app: &tauri::AppHandle) -> String {
let config_dir = app.path().app_config_dir().unwrap();
let id_file = config_dir.join("device_id");
if let Ok(id) = std::fs::read_to_string(&id_file) {
return id;
}
let id = uuid::Uuid::new_v4().to_string();
std::fs::create_dir_all(&config_dir).ok();
std::fs::write(&id_file, &id).ok();
id
}
#[tauri::command]
async fn track_event(
app: tauri::AppHandle,
event_name: String,
properties: serde_json::Value,
email: Option<String>,
) -> Result<(), String> {
let client = app.state::<Arc<Outlit>>();
let device_id = get_or_create_device_id(&app);
if let Some(email) = email {
// User is signed in
client.track(&event_name, outlit::email(&email))
.fingerprint(&device_id)
.property("data", properties)
.send()
.await
.map_err(|e| e.to_string())?;
} else {
// Anonymous tracking
client.track_by_fingerprint(&event_name, fingerprint(&device_id))
.property("data", properties)
.send()
.await
.map_err(|e| e.to_string())?;
}
Ok(())
}
fn main() {
let client = Arc::new(
Outlit::builder("pk_xxx")
.build()
.expect("Failed to create Outlit client")
);
tauri::Builder::default()
.manage(client.clone())
.invoke_handler(tauri::generate_handler![track_event])
.build(tauri::generate_context!())
.expect("error building tauri app")
.run(|app, event| {
if let tauri::RunEvent::Exit = event {
// Flush on exit
let client = app.state::<Arc<Outlit>>();
tauri::async_runtime::block_on(client.shutdown()).ok();
}
});
}
Error Handling
The SDK returns Result<(), outlit::Error> for operations that can fail:
match client.track("event", email("user@test.com")).send().await {
Ok(()) => println!("Event tracked"),
Err(outlit::Error::Network(e)) => eprintln!("Network error: {}", e),
Err(outlit::Error::Shutdown) => eprintln!("Client was shutdown"),
Err(e) => eprintln!("Error: {}", e),
}
Next Steps