irondome-sdk 0.2.0

Secure pooled IronDome API client
Documentation

IronDome Rust SDK

Rust integration guide · API reference · Plans and endpoints

An asynchronous client built on reqwest and rustls. Cloning Client shares the connection pool, immutable credentials, and concurrency admission limit. The SDK uses a separate Cargo workspace so native HTTP dependencies do not affect the other workspaces.

Install

Add the SDK to a Tokio application:

[dependencies]
irondome-sdk = "0.2.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
use irondome_sdk::{Client, Options};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(&std::env::var("IRONDOME_API_KEY")?, Options::default())?;
    let result = client.check("person@example.com").await?;
    println!("disposable={} reason={:?}", result.disposable, result.reason);
    Ok(())
}

Use a timer-enabled Tokio runtime. Construct once inside application initialization, clone for concurrent tasks, and retain it across checks. Drop all clones after work completes. Creating a reqwest client per request throws away the pool. There is no blocking Rust wrapper and this native client is not a Fastly/Wasm runtime adapter.

Endpoints and bulk checks

Options::default() targets https://api.irondome.dev for the stable account-key API contract. The stable account-key backend is active in production as of 18 October 2026. One account key remains valid across plan changes; the server applies the current plan. Free accounts share 500 checks per organization per month, while paid accounts bypass that quota. Do not configure the retired free origins (free.irondome.dev or irondome-free.edgecompute.app). For trusted staging or local TLS tests, set Options { base_url: "https://staging.example".into(), ..Options::default() }. Each syntactically valid email in a bulk request counts as one check under the unified API contract.

client.check_bulk(&["person@example.com", "invalid"]).await? accepts 1?100 strings. Results preserve input order as BulkOutcome::Checked or BulkOutcome::Invalid, with checked and failed counts. Single request bodies are limited to 1,024 bytes and bulk bodies to 65,536 bytes. No call retries automatically; a timed-out request might already have been processed.

Cold-start and connection behavior

Construction performs no network I/O. client.warmup().await optionally sends an unauthenticated health GET using the same pool. It does not submit a billable email or validate authorization. Overlap it with independent initialization; do not insert an extra health round trip before every check. True cold connection setup remains necessary, and health cannot guarantee a subsequent request uses the same Fastly sandbox or initialized detector/policy state.

TLS ALPN permits HTTP/2 with HTTP/1.1 fallback. TCP_NODELAY is enabled for small messages; idle connections are retained with a bound. This does not pin a POP or keep a socket alive across process destruction. HTTP/3/0-RTT, active heartbeat pings, IP pinning, and hand-written TLS are intentionally absent.

Configuration

Pass Options { field: value, ..Options::default() }.

Field Default Meaning
base_url https://api.irondome.dev Trusted unified API origin; old free origins are rejected
timeout 5 seconds Total request deadline through response-body completion
connect_timeout 2 seconds Connection establishment cap
idle_timeout 20 seconds Idle connection expiry policy
max_idle_connections 8 Idle pool capacity per host
max_in_flight 64 Shared fail-fast admission limit across clones
http1_only false Explicit HTTP/1.1 diagnostic/fallback mode
additional_root_certificates empty Trusted PEM root certificates merged into trust

Durations must be positive and at most one day. Admission is bounded to 65,536; idle capacity must be positive and no greater than admission. Active connection count is bounded indirectly by admitted operations, not by a separate socket-count option. An idle-pool bound is not an active HTTP/2 stream bound.

For a shorter individual deadline, wrap the operation in tokio::time::timeout. Dropping or aborting the operation cancels its future and releases its admission permit. The server may already have completed a canceled POST. Do not infer that cancellation means no usage event was emitted.

Contract and failure handling

check(&str) serializes a single email field without normalization. The complete UTF-8 JSON body must fit 1,024 bytes. Rust strings already guarantee valid UTF-8; serialization overhead still counts. A verified result includes typed Provider and Reason enums, valid, disposable, and optional request_id and detector_version. Invalid/missing fields or inconsistent reason/disposable combinations are protocol errors. valid means accepted syntax/normalization, not mailbox deliverability.

Error::Api exposes status, allowlisted code, request_id, and optional retry_after. Unknown HTTP errors use http_error; raw body messages are not retained. Other variants are Configuration, Validation, Protocol, Transport, Timeout, and Busy. All variants have safe Display messages. Client Debug output redacts credentials, and the Authorization header is marked sensitive in reqwest.

Requests are HTTPS-only, redirects and implicit environment proxies are disabled, certificate/hostname verification remains enabled, and TLS minimum is 1.2. Explicit tls_sslkeylogfile(false) avoids enabling session-key logging from environment configuration. Custom CA certificates extend trust; they do not disable validation. No cookie jar is enabled. Unexpected compression and bodies over 16 KiB fail closed. Bodies are fully consumed or dropped to release transport resources.

The client explicitly selects reqwest::retry::never(): current reqwest has a protocol-NACK retry policy by default. Do not assume a library has no retries merely because application code has no loop. No Idempotency-Key header, classification cache, or authorization cache is added. A response lost after a successful POST can still be billable, and a new POST is a distinct request.1

Concurrency and deployment

Use client clones inside a bounded task fan-out. Error::Busy signals local admission exhaustion, not a service classification. Keep keys/origins in trusted configuration and create new immutable clients on rotation. Never overwrite one shared credential for simultaneous tenants. Prevent tracing libraries from recording request bodies or headers; redacted SDK errors cannot control external instrumentation.

For a serverless Rust process, preserve a client while its runtime stays alive and set deadlines below invocation expiry. Platform freeze/thaw, idle socket closure, DNS/routing changes, and instance replacement can require new connections. There is no SDK promise of immortal TCP state or a particular regional p99 improvement.

Test and review

cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check

Integration tests use the shared Python loopback TLS fixture with a generated trusted certificate. They cover reuse across clones, trust/hostname failures, redirect/no-replay behavior, body/schema limits, deadlines, and dropped-future cancellation. Tests require Python 3 and OpenSSL as well as Rust. No production credential or network endpoint is used. Testing distinguishes CI verification from hosted-runtime and production latency acceptance.

See reqwest retry::never and the shared research.