Ruby SDK
letterapp is the official Ruby client for the ingestion API. It batches
events on a background thread, retries on transient failures, and generates
idempotency keys for you - so the common letter.track(...) call from a
request handler is non-blocking and safe to retry.
If you’d rather call the HTTP API directly, see the Ingestion API.
| Package | letterapp on RubyGems |
| License | MIT |
| Runtime | Ruby 3.0+ |
| Size | Zero runtime dependencies (standard library only) |
Install
bundle add letterapp
# or
gem install letterapp
The gem has no runtime dependencies - HTTP goes over the standard library
net/http, JSON over json. Works in Rails, Sinatra, Sidekiq workers, and
plain scripts.
You’ll need an API key from Dashboard - Settings - API keys before the SDK will do anything useful.
Quick start
require "letterapp"
letter = Letterapp::Client.new(api_key: ENV["LETTER_API_KEY"])
letter.identify(
user_id: user.id,
email: user.email,
traits: { name: user.name, plan: "free" }
)
letter.group(
user_id: user.id,
account_id: workspace.id,
name: workspace.name,
traits: { plan: workspace.plan, mrr: 49 }
)
letter.track(
user_id: user.id,
event: "Workspace Created",
properties: { workspace_id: workspace.id }
)
# Required before the process exits on long-running servers.
letter.close
Create the api_key in Dashboard - Settings - API keys. It’s shown once
on creation and never again - store it somewhere safe.
In Rails, create the client once in an initializer
(config/initializers/letter.rb) and reuse it across requests.
Constructor options
| Option | Default | What it does |
|---|---|---|
api_key | - | Required. lt_live_... from Settings. |
base_url | https://api.letter.app | Override for self-hosting. |
flush_at | 50 | Send a batch when this many items are queued. |
flush_interval | 0.1 (seconds) | Send queued items at most this often. |
max_retries | 3 | Retry attempts on 5xx and 429. |
open_timeout | 10 (seconds) | Connection open timeout. |
read_timeout | 10 (seconds) | Response read timeout. |
on_error | warns to stderr | Called when a background flush fails. |
Methods
-
identify(user_id:, email:, traits:, timezone:, timestamp:, message_id:) -
group(user_id:, account_id:, name:, traits:, timestamp:, message_id:) -
track(user_id:, event:, properties:, timestamp:, message_id:)These enqueue and return immediately. Transport errors surface via
on_error. Fast path for long-running servers. -
flush- send everything queued now; blocks until the request settles. -
close- flush, stop the background thread, refuse new enqueues. Anat_exithook runs it automatically, but call it explicitly at shutdown so no events are lost.
Retry behavior
429: waitRetry-Afterseconds, then retry (up tomax_retries).5xxor network errors: exponential backoff (0.25s x 2^attempt + jitter).4xxother than 429: raised immediately (viaon_error), no retry.
The SDK auto-generates a UUID message_id per call, so retries dedupe at the
server. See Idempotency for the underlying guarantee.
Serverless mode
In serverless / function environments there’s no background time between
requests to drain the queue, so set flush_at: 1 and call flush at the end
of each handler:
letter = Letterapp::Client.new(api_key: ENV["LETTER_API_KEY"], flush_at: 1)
def handler(event:, context:)
letter.track(user_id: user_id, event: "Checkout Started")
letter.flush
end
Errors
Configuration errors and non-retryable API responses raise Letterapp::Error
(carrying #status and #body), which uses the same shape as the HTTP API -
see Error format. Background transport errors are
passed to on_error instead, since they can’t be raised to the caller.
Versioning
The SDK follows semver. While we’re at 0.x:
- patch (
0.1.0 -> 0.1.1) - bug fixes only. - minor (
0.1.0 -> 0.2.0) - new options, new methods, behavior changes. - major (
0.x -> 1.0.0) - only once the HTTP API and signatures are stable. Until then, pin a pessimistic range (gem "letterapp", "~> 0.1").
Every request sends a User-Agent: letterapp-ruby/<version> header so we can
spot outdated clients in server logs.