Helios Tracker
A Rails engine that exposes your app's user, visit, and email-suppression data as JSON API endpoints for Helios Crusader to consume.
Helios pulls from these endpoints daily to track signups, logins, click-throughs, and blocked emails — automatically moving targets from cold to warm to hot based on engagement.
Requirements
- Rails >= 5.1
- Ruby >= 2.7
- Universal Track Manager (installed automatically)
Installation
Add to your Gemfile:
gem "helios-tracker"
Then run:
bundle install
rails generate helios_tracker:install
rails db:migrate
The install generator will:
- Install and configure Universal Track Manager (visit tracking)
- Add
include UniversalTrackManagerConcernto yourApplicationController - Create a
blocked_emailsmigration - Generate
config/initializers/helios_tracker.rb - Mount the engine in your routes
If Universal Track Manager is already installed, the generator will detect it and skip that step.
Generator Options
# Specify your User model class (default: "User")
rails generate helios_tracker:install --user-class=Account
# Skip UTM installation if already set up
rails generate helios_tracker:install --skip-utm
Configuration
Set your API key in your environment (.env, credentials, or server config):
HELIOS_TRACKER_API_KEY=your_secret_key_here
Then edit config/initializers/helios_tracker.rb:
HeliosTracker.configure do |config|
# ---- Users ----
config.user_class_name = "User"
# Return users updated since query_start.
# Receives (query_start, params). Must return an ActiveRecord relation.
config.user_scope = ->(query_start, params) {
User.where.not(email: "")
.where("updated_at > ?", query_start)
}
# Map API field names to model attributes or lambdas.
# :email is required. All others are optional.
config.user_fields = {
email: :email,
created_at: :created_at,
source_ip: :source_ip,
login_count: :login_count,
accounts_owned_count: ->(user) { user.accounts_owned_count },
free_accounts_count: ->(user) { user.free_accounts_count },
unsubscribe_nonce: :unsubscribe_nonce,
first_unconfirmed_visit_id: :first_unconfirmed_visit_id,
login_attempt_count: :login_attempt_count,
app_open_days_count: :app_open_days_count,
}
# ---- Visits ----
config.visit_scope = ->(query_start, params) {
UniversalTrackManager::Visit.where.not(hmid: nil)
.where("updated_at > ?", query_start)
}
# :hmid is required.
config.visit_fields = {
hmid: :hmid,
visited_download_page: ->(visit) { visit.visited_download_page? },
}
# ---- Blocked Emails ----
config.blocked_email_class_name = "BlockedEmail"
config.blocked_email_scope = ->(query_start, params) {
BlockedEmail.where("created_at > ?", query_start)
}
config.blocked_email_fields = {
email: :email,
source: :source,
}
end
Field Mapping
Each field in user_fields, visit_fields, and blocked_email_fields can be either:
- A symbol — calls that method on the record (e.g.,
:emailcallsuser.email) - A lambda — receives the record and returns a value (e.g.,
->(user) { user.accounts.count })
Fields you don't include in the hash are silently omitted from the API response.
Scoping with Lambdas
The user_scope, visit_scope, and blocked_email_scope lambdas receive two arguments:
| Argument | Description |
|---|---|
query_start |
A YYYY-MM-DD date string sent by Helios |
params |
The full request params (includes domain_name, etc.) |
Use these to filter your records however your app requires:
# Multi-domain example
config.user_scope = ->(query_start, params) {
domain = Domain.find_by(name: params[:domain_name])
User.where.not(email: "")
.where(domain_id: domain&.id)
.where("updated_at > ?", query_start)
}
API Endpoints
All endpoints require authentication via HELIOS_TRACKER_API_KEY, passed as either:
- Header:
Authorization: Bearer <API_KEY> - Query parameter:
?api_key=<API_KEY>
GET /api/all_users.json
Returns users updated since query_start.
| Parameter | Required | Description |
|---|---|---|
query_start |
yes | YYYY-MM-DD — records from this date onward |
domain_name |
no | Domain filter (if your app is multi-domain) |
api_key |
yes | Shared API key |
GET /api/all_visits.json
Returns visits with a non-null hmid since query_start.
| Parameter | Required | Description |
|---|---|---|
query_start |
yes | YYYY-MM-DD — visits from this date onward |
api_key |
yes | Shared API key |
GET /api/blocked_emails.json
Returns suppressed emails (bounces, unsubscribes) since query_start.
| Parameter | Required | Description |
|---|---|---|
query_start |
yes | YYYY-MM-DD — blocks from this date onward |
api_key |
yes | Shared API key |
How It Works
Helios calls your endpoints once per day (9:00 AM UTC). Each call includes a query_start date — your scopes should return records created or updated on or after that date.
Important for window-based fields: login_count, accounts_owned_count, and app_open_days_count should return only counts within the query_start..now window. Helios accumulates these across successive pulls. Returning lifetime totals will result in double-counting.
Helios handles duplicates gracefully (find-or-create logic), so returning the same records on a re-request is safe.
License
MIT License. See LICENSE for details.