Architecture
Currnt was a SailsJS monolith — a single Node.js process tree running on a single host, talking to a second host that held the database and search index. After nine years of growth it was still recognizably the same shape. No microservices, no Kubernetes, no message broker beyond a homegrown HTTP-poll queue. The simplicity is part of the story.
The application layer
A single host runs the entire user-facing application. Nginx fronts five PM2-managed Node workers on a single application port. There is no second app host. There is no autoscaler. Deploys are a manual git pull on master followed by a pm2 reload — no CI, no PR, no feature branches.
┌───────────────────────────┐
traffic ─────────▶│ nginx (5 workers, 80/443) │
└───────────────┬───────────┘
│
▼
┌───────────────────────────┐
│ pm2 cluster (5 workers) │
│ SailsJS on one app port │
└───────────────┬───────────┘
│
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
MySQL 5.5 Elasticsearch Redis
2.4 (sessions,
socket.io)The workflow visible in git log is direct commits to master with the message progress — no PR discipline, no feature branches. The frozen tip is a December 2024 commit titled “Update open_ai.js.”
The data layer
A second host holds both MySQL 5.5 and Elasticsearch 2.4. Both are years past end-of-life. The host runs an older OS that only offers ssh-rsa / ssh-dss host key types — modern OpenSSH rejects it without explicit downgrade flags. The schema is 314 tables and roughly 140 GB of data, dominated by:
prospects— 15 million rows. The CRM/outreach engine’s main table.- A large LLM-trace table — about 12 GB. Logs from the AI co-facilitator (see /features/ai-cofacilitator).
logs_*— assorted activity logs.users— hundreds of thousands of accounts; password column is sized for SHA-256 (40 chars), not bcrypt.
Redis lives on the data host and is used for Sails session storage and Socket.io pub/sub between the cluster workers.
The queue — dispenserd
Async work doesn’t go through Kafka, RabbitMQ, SQS, or Redis pub/sub. It goes through dispenserd, a custom HTTP-poll queue that lives in the codebase. Producers POST jobs to it; long-running worker processes poll it for work over HTTP and execute handlers. There are 5 lanes (priority classes), 13 worker processes managed by Supervisord, and 19 distinct handler scripts.
┌──────────────────────────────────────────┐
│ dispenserd (HTTP) │
│ 5 lanes · in-memory queue · MySQL ack │
└────────────┬─────────────────────────────┘
│ (poll)
┌───────────────────┼───────────────────┐
▼ ▼ ▼
worker A worker B worker C …
(Supervisord) (Supervisord) (Supervisord)
│ │ │
└──── 19 handlers ──┴───── 4,591-line ──┘
worker entryA dedicated page goes deeper on lane semantics, retry policy, and the handler-by-handler topology: /architecture/dispenserd.
The cron layer
The application host’s crontab runs 62 distinct scheduled jobs, ranging from the obvious (digest email batches, metric rollups) to the very specific — including gpt_ai_cofac_nudging, which every five minutes wakes the AI co-facilitator process to draft promotional posts under a single privileged author account. A standalone page tours the strangest one: /features/ai-cofacilitator.
The email layer
177 email templates live in views/emails/. Sending goes through a rotating fleet of SMTP accounts plus a transactional-email provider as a fallback. A provider API key was found in plaintext in an ops-script file on the decommissioned ops host; it is referenced categorically on the security page without being quoted.
The decommissioned third host
A third host exists in the project but is half-decommissioned. No nginx, no Node, no app. Only sshd listens. Bash history shows it was used as a jumpbox to reach the application host, the data host, and the worker fleet. Old artifacts (certificate directories from 2016–2018, a multi-megabyte log from the 2021 datacenter-to-GCP migration, an old Python source tree) suggest it was historically the SSL certificate and ops box. It can probably be shut down without production impact, after a forgotten-secrets audit.
Reading list for this section
Lane semantics, retry policy, and how 19 handlers map across 13 workers.
Supervisord Worker fleetThe full Supervisord topology, what each worker does, and the 4,591-line entrypoint that holds them together.
314 tables Data modelA guided tour of the high-value table clusters: prospects, users, boards, campaigns, logs.
Feature AI co-facilitatorThe strangest cron in the system, end to end.
Feature CRM engineHow 15M prospects ended up in the database, and the rotating fleet that keeps reaching them.