Quartz
Multi-tenant SaaS for project, financial, and donor management
Running against seeded demo data — an invented foundation, invented staff, invented funders.
Quartz is a multi-tenant SaaS platform: many customer organizations share one running application, and each organization's data is isolated from every other one's. Projects are the central entity — they carry milestones, resources, members, expense and income requests, tickets, and financials — and around them sit a donor management module, a billing system, and twelve outside integrations.
It is the largest thing here: 63 models, policies on everything, and a documented security posture written for the people who ask for one.
Tenancy is enforced twice
The active organization is chosen per session rather than by subdomain or URL path, which means the tenant is not something a URL can assert. TenantScoped resolves it on every request and validates it against the user's memberships; Tenantable stamps organization_id on creation from a thread-safe Current context.
Login is the other half. A Warden hook normalizes the session's organization at authentication time: if the stored value is not one of the user's memberships, it is reset before the first request runs. Between the two, a user can never operate under an organization they don't belong to — not by tampering, and not by holding a stale session from a revoked membership.
Twelve integrations, two auth patterns
OAuth for QuickBooks, HubSpot, Zoho, Salesforce, FreshBooks, and Xero; API keys for Stripe, Monday, Zendesk, FreshDesk, and Bill.com. Every token and key is encrypted at rest, and connections are unique per organization.
The FreshDesk sync is the one that earned its complexity. It is two-way, and the interesting parts are all about not leaking things:
- Only public replies are stored.
public_reply?fails closed — a conversation must be explicitly non-private to be imported, so a renamed or missing flag upstream hides a thread rather than exposing agent-only notes to the client. Private notes never reach the database. - One evaluator, three callers. The sync, the create path, and the retroactive purge all run the same
PrivacyRule, so they cannot drift apart and disagree about what counts as private. - Purging is a confirmed action, never a side effect. Applying a new privacy rule to already-imported tickets is its own explicit request, because it is irreversible.
- Watermarks per concern. FreshDesk's list endpoint hides anything older than 30 days unless asked; the sync walks oldest-first against a stored cursor, and replies keep a separate cursor because a thread costs one API call per ticket.
Rules that encode who may do what
The submitter of an expense or income request can never approve it — project admins, org admins, and superadmins alike. Deny, mark-paid, and mark-processed are deliberately not gated: refusing your own request is no conflict of interest, and post-decision bookkeeping already required someone else's approval.
The one carve-out is the project with a single person on it, where there is nobody to hand the decision to. When the bar does apply, the page renders an explicit notice rather than silently dropping the approval panel, so the rule is visible instead of looking like a bug.
Billing, and the second Stripe
Quartz bills through its own Stripe account, which is deliberately distinct from the customer-owned Stripe integration — two namespaces, two credential sets, no chance of one being mistaken for the other. It is sales-led: superadmins manage subscriptions internally and all customer-facing billing UI is Stripe-hosted.
The Subscription record is an opaque mirror of Stripe's state, with a plain string status and no enum, because inventing a local state machine for someone else's system is how the two fall out of sync. Organizations with no subscription at all are grandfathered rather than locked out.
Stack and infrastructure
| Framework | Rails 8.1 on Ruby 3.4, Hotwire over importmap, Tailwind, Propshaft |
|---|---|
| Data | PostgreSQL; Active Record encryption for every third-party token |
| Auth | Devise with 2FA; Pundit policies with role-based access |
| Background work | Solid Queue, with a superadmin-only jobs dashboard |
| Notifications | noticed for in-app and email, plus outgoing interceptors for bounces, opt-outs, and suspended accounts |
| Hardening | Rack::Attack, Brakeman and bundler-audit on pre-push, RSpec with Pundit and Shoulda matchers |
| Hosting | Heroku, with an AWS sidecar providing S3, SES, and a narrowly scoped IAM user |
| Alternative | A parked Terraform stack for an all-AWS deploy — ECS Fargate, RDS, ALB, ACM — kept for the day it is needed |