Case studies›Agency Operating System
AGENCY OPERATING SYSTEM

Compass Project Control Center

How we designed and built our own internal agency operating system — on an event-driven architecture with contract-aware workflow orchestration.

18 min readEvent-Driven ArchitectureWorkflow OrchestrationContract Automation+3 more
5
Automation rules
run by a daily cron
4
Status dimensions
independent dimensions
100%
Audit trail
every business action
~0
Manual deadline tracking
handled by the automation engine

The problem: operational chaos beyond 10+ projects

On the surface, a web agency is a simple business model: proposal, contract, build, handover, payment. In reality, every active project is its own workflow, every client is its own communication thread, and every deadline is a legal obligation. Beyond 10–15 active projects, traditional tools break down. Not because they're bad — but because they weren't built for this. That's exactly what we experienced at Compass. The big picture was fine, but the details were getting harder and harder to manage:
⏱
Asset collection deadlines
Who has sent their materials, who hasn't, and how many days late are they? Someone had to check this by hand every morning.
💳
Payment statuses
Who has paid the deposit, who has an open final invoice, who is overdue? Pieced together from emails and spreadsheets.
⚖️
Legal escalations
The project freeze deadline is written into the contract. But who is watching for day 60?
📬
Client communication
A legally defensible communication trail can't be reconstructed from an inbox.
🔄
Revision rounds
Free or paid? Which round are we on? Who signed off on the change list?
All of this is manageable by hand for 5 projects. Beyond 15, it's 2–3 hours of admin every day — and that's just the tracking. Communication, payments, and legal protection come on top of it.

Why isn't a Kanban board enough?

Trello, Notion, ClickUp, Asana — all excellent task management tools. But task management and workflow orchestration solve different problems.
TASK MANAGEMENT
  • Who's doing what?
  • When will it be done?
  • What's left?
WORKFLOW ORCHESTRATION
  • What's the legal state?
  • What's the next automated step?
  • What's the contractual obligation?
  • What can be proven in an audit?
  • When should an email go out automatically?
In an agency, where every project is backed by a contract, deadlines, and legal obligations, a task management tool isn't enough. You need a workflow orchestration system.

Architecture decisions

We designed the system around three principles: auditability, automation, and scalability.
⚡
Event-Driven Architecture
Every business action writes an event to the project_events table. Status changes, sent emails, file uploads, recorded payments — all logged as audit events with actor, source, visibility, and metadata fields.
Why? The event log is the single source of truth. If a legal question ever comes up, we can replay exactly what happened, when, and who triggered it.
🎛
4 Independent Status Dimensions
project_phase (technical state), payment_status (payment state), legal_status (legal state), operational_status (operational state) — each one fully INDEPENDENT of the others.
Why? A project can be technically finished, legally frozen, and overdue on payment all at once. A single status field can't represent that.
🧩
Service Separation
Every domain lives in its own service: projectEventService, projectPaymentService, projectRevisionService, projectMaterialService, projectHandoverService, projectFileService.
Why? Page components hold no business logic. Each service has a single responsibility — so it stays testable, replaceable, and extensible.
🔄
Status Machine
Legal and operational statuses aren't free-form strings. Validated state transitions, terminal-state handling, and consistency checks run as part of the system.
Why? Invalid state combinations (e.g. a closed project with an open payment) are detected and fixed before they turn into a legal problem.

The 4 status dimensions and the automation engine

The automation engine runs once a day (6:00 AM, Vercel Cron) and evaluates every active project against 5 rules.
4 INDEPENDENT STATUS DIMENSIONS
Project Phase
project_phase
asset_request_sentawaiting_client_assetsin_developmentinternal_reviewfirst_handoverclient_testingrevision_roundmarked_completehanded_overclosed
Legal Status
legal_status
okclient_delayedpayment_overduework_suspendedfrozenin_collections
Payment Status
payment_status
deposit_pendingdeposit_paidinstallment_duefinal_payment_pendingpaid_in_full
Operational Status
operational_status
activepausedclosedarchived
5 AUTOMATION RULES
1
ruleMaterialDeadline
Trigger:Asset request overdue by 3+ days
Action:legal_status → client_delayed + reminder email
Frequency:Every 3 days, up to day 30
2
rulePaymentOverdue
Trigger:Final invoice past due
Action:legal_status → payment_overdue + payment notice email
Frequency:On days 1, 3, 7, and 14
3
rulePriceIncreaseWarning
Trigger:client_delayed + 30/45/55 days without progress
Action:Project freeze warning email
Frequency:On days 30, 45, and 55
4
ruleAutoFreeze
Trigger:client_delayed + 60+ days without progress
Action:legal_status → frozen + operational_status → paused
Frequency:Once, on day 60
5
ruleAutoApproval
Trigger:client_testing phase + 3+ days with no response
Action:project_phase → marked_complete (contractual auto-acceptance)
Frequency:Once, on day 3

Engineering deep-dive

📋
Event Sourcing
  • Every project_events record contains: event_type, title, actor, source, visibility, old_value, new_value, metadata
  • The Communication tab only shows client-facing events (COMM_EVENT_TYPES whitelist)
  • The Audit log tab shows every event — a complete legal trace
  • Replayable state: the event log can reconstruct what happened and when, at any time
⏰
Deadline Engine
  • buildDefaultDeadlines() — the canonical source: 3/30/60-day rules
  • calcOverdue() — calculates how many days each deadline is overdue
  • syncDeadlineOverdueStatus() — daily sync that updates is_overdue and overdue_days
  • Every deadline type has an auto_action_type field — the next automated step
🔒
Cron & Automation Deduplication
  • wasActionDoneToday() — the same action runs at most once a day (dedup)
  • cron_executions table — every run is logged: lock mechanism, duration, error count
  • Duplicate run protection: 409 Conflict if a run is already active
  • Stale lock cleanup: after a 10-minute timeout, a stale lock is marked timed_out
📧
Email Orchestration
  • Email isn't a notification. It's a legal communication layer.
  • Every email is logged against a project_id and lands in the audit trail
  • email_events table: template, recipient, status, retry_count, resend_id
  • sendAutomationEmail() error handling: an email_send_failed project_event on failure

Technical hardening: production-grade stabilization

A workflow orchestration system isn't production-grade because of its core features — it's production-grade because of how it behaves in edge cases, under failure, and over the long run.
Error Handling Layer
  • Every automation rule runs inside try/catch — failures write an automation_failed project_event
  • Email send failure: an email_send_failed project_event + incremented retry_count
  • logAutomationFailedEvent() and logEmailSendFailedEvent() — typed wrappers
Reconciliation Engine
  • 6 impossible-state rules that detect consistency errors
  • Auto-fix: frozen + active → automatically set to paused
  • Runs on the daily cron; every issue writes a state_reconciliation_executed event
Monitoring & Observability
  • Ops Monitoring Dashboard: heartbeat, health scores, failed automations, stuck projects
  • cron_reliability_7d %, email_delivery_rate % — system health metrics
  • Dead-letter queue: emails that still didn't go out after 3 retries
Data Lifecycle
  • cron_executions: 90-day retention — older records are deleted
  • Dismissed notifications: 30-day retention
  • system_errors: 60-day retention
  • project_events and email_events are NEVER deleted — for audit purposes

Key learnings

„The website isn't the product. The operating system is the product."
An agency's value isn't in the deliverable. It's in the system it uses to produce it.
„Email isn't a notification layer. It's a legal communication layer."
Every reminder we send is a provable legal step. If it isn't recorded, it didn't happen.
„No audit trail, no contractual evidence."
In a client dispute, the agency that wins is the one that can prove what it sent, when, and who received it.
„Deadline management isn't task management. It's a legal obligation."
Contractual deadlines are legal obligations — not to-do items. The system needs to know that too.
„Traditional tools track tasks. We needed to orchestrate workflows."
The system is built around the business's complexity — the business doesn't bend to the limits of a tool.
ARCHITECTURE SUMMARY
Event-Driven ArchitectureWorkflow OrchestrationContract AutomationStatus MachineAudit TrailDeadline Engine
COMPASS MARKETING

Want a system like this?

We don't hand you a template. We design production-grade digital infrastructure that fits the way your business actually runs.