Skip to content

klimeapp/klime-elixir

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

klime

Klime SDK for Elixir.

Installation

Add klime to your list of dependencies in mix.exs:

def deps do
  [
    {:klime, "~> 1.0"}
  ]
end

Then run:

mix deps.get

Quick Start

  1. Configure in config/config.exs:
config :klime,
  write_key: System.get_env("KLIME_WRITE_KEY")
  1. Add to your supervision tree in application.ex:
children = [
  Klime.Client
]
  1. Track events anywhere in your app:
# Identify a user
Klime.identify("user_123", %{
  email: "user@example.com",
  name: "Stefan"
})

# Track an event
Klime.track("Button Clicked", %{
  button_name: "Sign up",
  plan: "pro"
}, user_id: "user_123")

# Associate user with a group
Klime.group("org_456", %{
  name: "Acme Inc",
  plan: "enterprise"
}, user_id: "user_123")

Installation Prompt

Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:

Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.

ANALYTICS MODES (determine which applies):
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
  → Use identify() + group() + track()
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
  → Use identify() + track() only (no group() needed)

KEY CONCEPTS:
- Every track() call requires either user_id OR group_id (no anonymous events)
- Use group_id alone for org-level events (webhooks, cron jobs, system metrics)
- group() links a user to a company AND sets company traits (only for Companies & Teams mode)
- Order doesn't matter - events before identify/group still get attributed correctly

SETUP:
1. Add to mix.exs: {:klime, "~> 1.0"}
2. Run: mix deps.get
3. Configure in config/config.exs:
   config :klime, write_key: System.get_env("KLIME_WRITE_KEY")
4. Add Klime.Client to your application.ex supervision tree:
   children = [Klime.Client]

BEST PRACTICES:
- Store write key in KLIME_WRITE_KEY environment variable
- Client automatically handles graceful shutdown when supervisor stops

# Identify users at signup/login:
Klime.identify("usr_abc123", %{email: "jane@acme.com", name: "Jane Smith"})

# Track key activities:
Klime.track("Report Generated", %{report_type: "revenue"}, user_id: "usr_abc123")
Klime.track("Feature Used", %{feature: "export", format: "csv"}, user_id: "usr_abc123")
Klime.track("Teammate Invited", %{role: "member"}, user_id: "usr_abc123")

# If Companies & Teams mode: link user to their company and set company traits
Klime.group("org_456", %{name: "Acme Inc", plan: "enterprise"}, user_id: "usr_abc123")

INTEGRATION WORKFLOW:

Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Phoenix, plug, Bandit, etc.)
2. Where is user identity available? (e.g., conn.assigns.current_user, socket.assigns.current_user)
3. Is this Companies & Teams or Individual Customers?
   - Look for: organization, workspace, tenant, team, account schemas → Companies & Teams (use group())
   - No company/org concept, just individual users → Individual Customers (skip group())
4. Where do core user actions happen? (controllers, live views, channels, contexts)
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, track)
Match your integration style to the framework's conventions.

Phase 2: Instrument
Add these calls using idiomatic patterns for the framework:
- Add Klime.Client to application.ex supervision tree
- identify() in auth/login success handler
- group() when user-org association is established (Companies & Teams mode only)
- track() for key user actions (see below)

WHAT TO TRACK:
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
Do NOT track: every request, health checks, plugs that run on every request, background jobs

Phase 3: Verify
Confirm: client in supervision tree, identify/group/track calls added

Phase 4: Summarize
Report what you added:
- Files modified and what was added to each
- Events being tracked (list event names and what triggers them)
- How user_id is obtained (and group_id if Companies & Teams mode)
- Any assumptions made or questions

API Reference

Configuration

Configure Klime in config/config.exs:

config :klime,
  write_key: System.get_env("KLIME_WRITE_KEY"),        # Required
  endpoint: "https://i.klime.com",                     # Optional (default)
  flush_interval: 2000,                                # Optional: ms between flushes (default: 2000)
  max_batch_size: 20,                                  # Optional: max events per batch (default: 20, max: 100)
  max_queue_size: 1000,                                # Optional: max queued events (default: 1000)
  retry_max_attempts: 5,                               # Optional: max retry attempts (default: 5)
  retry_initial_delay: 1000,                           # Optional: initial retry delay in ms (default: 1000)
  flush_on_shutdown: true,                             # Optional: auto-flush on shutdown (default: true)
  on_error: &MyApp.Analytics.handle_error/2,           # Optional: callback for batch failures
  on_success: &MyApp.Analytics.handle_success/1       # Optional: callback for successful sends

Starting the Client

Add to your supervision tree in application.ex:

children = [
  Klime.Client
]

The client reads configuration from the application environment and registers itself as :klime by default.

Methods

track(event_name, properties \\ %{}, opts \\ [])

Track an event. Events can be attributed in two ways:

  • User events: Provide user_id: to track user activity (most common)
  • Group events: Provide group_id: without user_id: for organization-level events
# User event (most common)
Klime.track("Button Clicked", %{
  button_name: "Sign up",
  plan: "pro"
}, user_id: "user_123")

# Group event (for webhooks, cron jobs, system events)
Klime.track("Events Received", %{
  count: 100,
  source: "webhook"
}, group_id: "org_456")

Note: The group_id: option can also be combined with user_id: for multi-tenant scenarios where you need to specify which organization context a user event occurred in.

Synchronous Methods (Bang Methods)

For cases where you need guaranteed delivery or want to handle errors explicitly, use the synchronous versions that block until the event is sent:

# Sync track - blocks until sent, returns {:ok, response} or {:error, error}
{:ok, response} = Klime.track!("Button Clicked", %{button: "signup"}, user_id: "user_123")

# Sync identify
{:ok, response} = Klime.identify!("user_123", %{email: "user@example.com"})

# Sync group
{:ok, response} = Klime.group!("org_456", %{name: "Acme Inc"}, user_id: "user_123")

These methods:

  • Send the event immediately (no batching)
  • Block until the HTTP request completes
  • Return {:ok, %Klime.BatchResponse{}} on success
  • Return {:error, %Klime.SendError{}} on failure

Use sync methods sparingly - they add latency to your code. The async methods are preferred for most use cases.

identify(user_id, traits \\ %{})

Identify a user with traits.

Klime.identify("user_123", %{
  email: "user@example.com",
  name: "Stefan"
})

group(group_id, traits \\ %{}, opts \\ [])

Associate a user with a group and/or set group traits.

# Associate user with a group and set group traits (most common)
Klime.group("org_456", %{
  name: "Acme Inc",
  plan: "enterprise"
}, user_id: "user_123")

# Just link a user to a group (traits already set or not needed)
Klime.group("org_456", %{}, user_id: "user_123")

# Just update group traits (e.g., from a webhook or background job)
Klime.group("org_456", %{
  plan: "enterprise",
  employee_count: 50
})

flush()

Manually flush queued events immediately.

:ok = Klime.flush()

shutdown()

Gracefully shutdown the client, flushing remaining events.

:ok = Klime.shutdown()

Features

  • Automatic Batching: Events are automatically batched and sent every 2 seconds or when the batch size reaches 20 events
  • Automatic Retries: Failed requests are automatically retried with exponential backoff
  • Async & Sync Methods: Use async methods for fire-and-forget, or sync (track!, identify!, group!) for guaranteed delivery
  • OTP Supervision: GenServer-based client integrates naturally with OTP supervision trees
  • Application Config: Configure once in config.exs, no need to pass client around
  • Plug Middleware: Optional Klime.Plug for per-request flush in Phoenix/Plug apps
  • Graceful Shutdown: Automatically flushes events when the supervisor stops (with flush_on_shutdown: true)
  • Callbacks: on_error and on_success callbacks for monitoring
  • Minimal Dependencies: Only requires jason for JSON encoding (plug optional for middleware)

Performance

When you call track/3, identify/2, or group/3, the SDK:

  1. Adds the event to an in-memory queue (microseconds)
  2. Returns immediately without waiting for network I/O

Events are sent to Klime's servers asynchronously. This means:

  • No network blocking: HTTP requests happen in the GenServer process, not your request handler
  • No latency impact: Tracking calls add < 1ms to your request handling time
  • Automatic batching: Events are queued and sent in batches (default: every 2 seconds or 20 events)
# This returns immediately - no HTTP request is made here
Klime.track("Button Clicked", %{button: "signup"}, user_id: "user_123")

# Your code continues without waiting
json(conn, %{success: true})

The only blocking operation is flush/0, which waits for all queued events to be sent. This is typically only called during graceful shutdown.

Configuration

Default Values

  • flush_interval: 2000ms
  • max_batch_size: 20 events
  • max_queue_size: 1000 events
  • retry_max_attempts: 5 attempts
  • retry_initial_delay: 1000ms
  • flush_on_shutdown: true

Callbacks

# In config/config.exs
config :klime,
  write_key: System.get_env("KLIME_WRITE_KEY"),
  on_error: fn error, _events ->
    Logger.error("Klime error: #{inspect(error)}")
    Sentry.capture_exception(error)
  end,
  on_success: fn response ->
    Logger.info("Sent #{response.accepted} events")
  end

Plug Middleware

For guaranteed per-request delivery, use Klime.Plug to flush events after each request:

# In your Phoenix endpoint.ex or router.ex
plug Klime.Plug, client: :klime

Note: This adds latency to every request as it waits for the flush. Only use this if you need guaranteed per-request delivery. For most use cases, the background worker is sufficient.

Error Handling

The SDK automatically handles:

  • Transient errors (429, 503, network failures): Retries with exponential backoff
  • Permanent errors (400, 401): Logs error and drops event
  • Rate limiting: Respects Retry-After header

Size Limits

  • Maximum event size: 200KB
  • Maximum batch size: 10MB
  • Maximum events per batch: 100

Events exceeding these limits are rejected and logged.

Phoenix Example

# config/config.exs
config :klime,
  write_key: System.get_env("KLIME_WRITE_KEY")
# lib/my_app/application.ex
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyAppWeb.Endpoint,
      Klime.Client
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
# lib/my_app_web/controllers/button_controller.ex
defmodule MyAppWeb.ButtonController do
  use MyAppWeb, :controller

  def click(conn, %{"button_name" => button_name}) do
    user_id = conn.assigns[:current_user] && conn.assigns.current_user.id

    Klime.track("Button Clicked", %{
      button_name: button_name
    }, user_id: user_id)

    json(conn, %{success: true})
  end
end

Phoenix LiveView Example

# lib/my_app_web/live/dashboard_live.ex
defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    user = socket.assigns.current_user

    Klime.track("Dashboard Viewed", %{}, user_id: user.id)

    {:ok, socket}
  end

  def handle_event("export", %{"format" => format}, socket) do
    user = socket.assigns.current_user

    Klime.track("Export Clicked", %{format: format}, user_id: user.id)

    {:noreply, socket}
  end
end

Requirements

  • Elixir 1.15 or higher
  • OTP 25 or higher
  • jason ~> 1.4

License

MIT

About

Klime SDK for Elixir

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages