Docs Go to app →

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.

Packageletterapp on RubyGems
LicenseMIT
RuntimeRuby 3.0+
SizeZero 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

OptionDefaultWhat it does
api_key-Required. lt_live_... from Settings.
base_urlhttps://api.letter.appOverride for self-hosting.
flush_at50Send a batch when this many items are queued.
flush_interval0.1 (seconds)Send queued items at most this often.
max_retries3Retry attempts on 5xx and 429.
open_timeout10 (seconds)Connection open timeout.
read_timeout10 (seconds)Response read timeout.
on_errorwarns to stderrCalled 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. An at_exit hook runs it automatically, but call it explicitly at shutdown so no events are lost.

Retry behavior

  • 429: wait Retry-After seconds, then retry (up to max_retries).
  • 5xx or network errors: exponential backoff (0.25s x 2^attempt + jitter).
  • 4xx other than 429: raised immediately (via on_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.