A self-hosted product analytics service. Track events, identify users, and visualize trends — without paying $1000+/month for Mixpanel or Amplitude.
Built for SaaS apps that only need the 20% of analytics features that matter: event tracking, funnels, retention, and a dashboard.
- Event Tracking — Track custom events with flexible JSONB properties
- User Identification — Identify users, merge anonymous sessions, track user journeys
- Funnels — Multi-step conversion funnels with time windows and property filters
- Retention — Weekly/daily cohort retention grids with segment filtering
- Real-time Dashboard — Live event feed, KPI cards, charts (Hotwire Turbo)
- Error Tracking — JS errors, promise rejections, server errors with business impact analysis
- Form Analytics — Field-level drop-off funnels, time-per-field heatmaps
- User Paths — Session flow analysis and navigation patterns
- Anomaly Detection — Z-score based spike/drop alerts with webhook notifications
- Segments — Behavioral + property-based user cohorts
- Webhooks — HMAC-signed HTTP webhooks with event filtering and auto-disable
- CSV Export — Export events, users, funnels, and retention data
- Multi-SDK Support — Ruby, JavaScript, Python, and Node.js SDKs (all zero-dependency)
- Backend: Rails 8, PostgreSQL, Solid Queue
- Frontend: Hotwire (Turbo + Stimulus), Tailwind CSS, Chart.js
- Deploy: Kamal 2 + Thruster
- Auth:
has_secure_passwordwith role-based access (admin/member, owner/editor/viewer)
- Partitioned events table — Monthly partitions for partition pruning, O(1) data retention, and faster vacuuming
- Pre-aggregated rollups —
EventDailyRolluptable for O(1) dashboard reads via atomic upserts - Identity resolution — Anonymous-to-identified user merging via background jobs
- Rate limiting — Rack::Attack throttling on write and read endpoints
- Idempotency — Partial unique index on
(project_id, idempotency_key, occurred_at)for safe retries - Zero-dependency SDKs — All SDKs use only stdlib (no gem/package conflicts with host apps)
Write endpoints (authenticated via X-API-Key header):
POST /api/v1/track— Track an eventPOST /api/v1/batch— Batch track eventsPOST /api/v1/identify— Identify a userPOST /api/v1/alias— Link anonymous ID to user
Read endpoints (authenticated via Bearer token):
GET /api/v1/queries/event_counts— Event counts over timeGET /api/v1/queries/top_events— Top events by countGET /api/v1/queries/user_timeline— User event timeline
git clone https://github.com/almokhtarbr/tally.git
cd tally
bin/setup
bin/devSeed demo data:
bin/rails db:seedVisit http://localhost:3000 and follow the setup wizard.
Ruby:
TallyAnalytics.configure do |c|
c.api_key = "pk_your_key"
c.endpoint = "https://tally.yourdomain.com"
end
TallyAnalytics.track("user@example.com", "purchase", plan: "pro", amount: 99)JavaScript:
<script src="https://tally.yourdomain.com/sdk/tally.js"></script>
<script>
tally('init', 'pk_your_key', { endpoint: 'https://tally.yourdomain.com' });
tally('track', 'button_clicked', { page: '/pricing' });
</script>bundle exec rspec82 specs covering models, services, jobs, and API endpoints.
Configured for Kamal 2. Update config/deploy.yml with your server details and run:
bin/kamal setupSee the docs/ directory for detailed documentation:
- Architecture — System design and scaling considerations
- API Reference — Full API documentation
- SDK Guide — SDK integration details
- Implementation — Build order and design decisions
MIT