> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outlit.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Outlit is the product that monitors customers and completes approved customer work. The customer context interfaces are the API, CLI, MCP server, and skills. Canonical references on this site: /api-reference/introduction for API concepts and authentication, /openapi.json for the public API contract, /cli/overview for the CLI, /ai-integrations/mcp for MCP. Prefer the `outlit` CLI or MCP tools for agent tasks; cite page sources when answering.

# Outlit Rust SDK

> Track product events from Rust desktop apps, CLI tools, and backend services.

The Outlit Rust SDK records product activity in native applications and backend services:

* **Desktop apps** - Tauri, native GUI applications
* **CLI tools** - Command-line applications
* **Backend services** - Rust-based APIs

For anonymous tracking before user sign-in, use `fingerprint`. See [Device Tracking](/concepts/website-visitors#device-tracking-non-browser) for details. To capture how someone first arrived and browsed your website, use the [Outlit Browser SDK](/tracking/browser/script) and identify that browser visitor when their email becomes available. An email-only server identify cannot link an otherwise anonymous browser visit.

## Installation

```bash theme={null}
cargo add outlit
```

The SDK is async and requires a tokio runtime. Most async frameworks (Tauri, Axum, Actix) already include tokio.

## Quick start

```rust theme={null}
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

```rust theme={null}
use std::time::Duration;

let client = Outlit::builder("pk_xxx")
    .flush_interval(Duration::from_secs(10))
    .max_batch_size(100)
    .timeout(Duration::from_secs(10))
    .build()?;
```

### Options

<ParamField path="public_key" type="&str" required>
  Your organization's public key.
</ParamField>

<ParamField path="api_host" type="&str" default="https://app.outlit.ai">
  API endpoint for sending events.
</ParamField>

<ParamField path="flush_interval" type="Duration" default="10s">
  How often to automatically flush events.
</ParamField>

<ParamField path="max_batch_size" type="usize" default="100">
  Maximum events per batch before auto-flush.
</ParamField>

<ParamField path="timeout" type="Duration" default="10s">
  HTTP request timeout.
</ParamField>

## API reference

### `client.track(event_name, identity)`

Track a custom event.

```rust theme={null}
// 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

<ParamField path=".email()" type="&str">
  Add email to the event (if not primary identity).
</ParamField>

<ParamField path=".user_id()" type="&str">
  Add user ID to the event.
</ParamField>

<ParamField path=".fingerprint()" type="&str">
  Add device fingerprint for linking.
</ParamField>

<ParamField path=".property()" type="(&str, impl Into<Value>)">
  Add a property to the event. Call multiple times for multiple properties.
</ParamField>

<ParamField path=".timestamp()" type="i64">
  Set custom timestamp (milliseconds since epoch).
</ParamField>

### `client.identify(identity)`

Identify a user and optionally link device events.

```rust theme={null}
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?;
```

<ParamField path=".fingerprint()" type="&str">
  Device identifier. All events tracked with this fingerprint are retroactively linked to the user.
</ParamField>

<ParamField path=".user_id()" type="&str">
  Your internal user ID.
</ParamField>

<ParamField path=".trait_()" type="(&str, impl Into<Value>)">
  Add a trait to the user profile.
</ParamField>

### Lifecycle and billing

Track your value milestone as an ordinary event once it succeeds:

```rust theme={null}
client.track("onboarding_completed", email("user@example.com"))
    .property("flow", "onboarding")
    .send()
    .await?;
```

Choose this event as your activation event in Outlit. The first matching event records activation once for each eligible resolved person and company, independently. Billing status comes from verified integrations such as Stripe. See [Customer journey](/concepts/customer-journey).

### `client.flush()`

Immediately send all queued events.

```rust theme={null}
client.flush().await?;
```

### `client.shutdown()`

Shut down gracefully: flushes remaining events and stops the timer.

```rust theme={null}
client.shutdown().await?;
```

<Warning>
  Always call `shutdown()` or `flush()` before your application exits to ensure all events are sent.
</Warning>

## 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`.

```rust theme={null}
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:

```rust theme={null}
match client.track("event", email("user@test.com")).send().await {
    Ok(()) => println!("Event tracked"),
    Err(outlit::Error::Http(e)) => eprintln!("HTTP request failed: {}", e),
    Err(outlit::Error::Api { status, message }) => {
        eprintln!("API error (status {}): {}", status, message)
    }
    Err(outlit::Error::Shutdown) => eprintln!("Client was shutdown"),
    Err(e) => eprintln!("Error: {}", e),
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Device tracking" icon="mobile" href="/concepts/website-visitors#device-tracking-non-browser">
    Anonymous tracking before user identification
  </Card>

  <Card title="Identity resolution" icon="fingerprint" href="/concepts/identity-resolution">
    How activity links across identifiers
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.