Agency Operations Platform
A management platform that replaces a scattered set of SaaS tools and a legacy system: sales from three checkout platforms, a CRM mirror, WhatsApp, HR and a unified customer base — with AI agents that work on the same data and permissions.
- Client
- Digital-marketing & education agency (name withheld)
- Role
- Architecture, backend (Java/Spring), frontend (Angular), CI/CD and deploy
- Year
- 2026
- Status
- In production since October 2026
20
Spring Modulith modules, boundaries enforced by tests
≈420
GraphQL queries and mutations
1,330+
backend tests on real PostgreSQL and Redis
45 s → 18 ms
dashboard query after profiling
Context
The agency ran its operations across several SaaS platforms — checkout providers, a CRM, WhatsApp, quiz funnels — plus a first-generation internal system whose database had grown to 20 GB, mostly raw JSON. Data was duplicated, permissions were coarse and reports were stitched together by hand.
The problem
Build a second-generation system from scratch that ingests everything reliably, merges it into one customer base, gives each team exactly the access it needs, and makes AI useful on real data without opening security holes.
What I did
- 01
Designed the architecture: a Spring Modulith modular monolith (20 modules) with a DDD-style layered structure and one use case per action.
- 02
Built the GraphQL API, the integrations and the AI layer in Java 25 and Spring Boot 4.1.
- 03
Built the Angular 21 frontend (zoneless, signals) with a typed GraphQL client and real-time updates over Server-Sent Events.
- 04
Set up CI/CD and production: Docker, Caddy and GitHub Actions, with health-checked deploys and automatic rollback.
What it does
- Mirrors of three checkout platforms — sales, subscriptions, abandoned carts — with resumable, rate-limited historical loads
- CRM mirror and WhatsApp conversation archive
- Unified customer base with identity resolution and audited, reversible merges
- Sales cycles with targets, seller attribution, live dashboards and period closing
- HR records with role and compensation history
- Internal workflow engine for scheduled reports by email and WhatsApp
- AI assistant and AI customer-service agents for WhatsApp
- MCP server so external AI clients can query the system under OAuth 2.1
Engineering highlights
- 01
Boundaries the build enforces
Modules talk only through named interfaces and events. A modularity test fails the build on any boundary violation, and a code-standards test parses every Java file with the javac tree API to enforce architecture rules.
- 02
Authorization as data
A custom @AccessRoute method-security interceptor covers 649 routes with all/team/own scopes, and roles are live templates edited in the UI. Sessions live in Redis with email MFA and Google sign-in; sensitive fields use AES-256-GCM with a blind index.
- 03
Webhooks that are never lost
An inbox acknowledges only after a group commit. Workers claim batches with FOR UPDATE SKIP LOCKED, keep per-key ordering, retry with backoff and verify HMAC signatures — some providers never resend a failed webhook.
- 04
AI that respects permissions
Spring AI agents use 122 tools that run with the conversation owner's own permissions, and writes need an explicit confirmation. RAG on pgvector (HNSW), Redis chat memory, validated structured output, and every model call costed and reconciled with cloud billing.
- 05
GraphQL at scale
≈200 queries and ≈220 mutations over 183 JPA entities, with DataLoader batching, keyset pagination, depth and complexity limits, and per-account rate limiting in Redis.
- 06
Deploys that don't drop work
Caddy retries during restarts, graceful shutdown drains in-flight AI replies, and replies are checkpointed in PostgreSQL so they survive a deploy.
Hard parts
- 01
Concurrency in ingestion
PostgreSQL advisory locks, optimistic-lock retries and a rule of no external calls inside transactions keep parallel webhooks from corrupting the customer base.
- 02
Identity resolution
Merging customers by email, phone and tax ID can chain into huge false matches. Key-strength rules and a shared-key threshold stop runaway merges, and any merge can be undone.
- 03
Third-party rate limits
Historical loads are split into daily units claimed with SKIP LOCKED, capped per minute, and resumable after a 429 or a manual pause.
Results
- In production since October 2026, replacing the first-generation system.
- Dashboard “today” query: 45 s → 18 ms. Team home: 37.6 s → 47 ms.
- Export of 31 days of messages (1.1 M messages, from an 8 M-message test dataset) in 32–64 s of database time.
- ≈1,330 backend tests on Testcontainers and ≈790 frontend tests; CI deploys with health checks and automatic rollback.
Figures come from the repository: tests, measurements and documented reports.
Stack
- Java 25
- Spring Boot 4.1
- Spring Modulith
- Spring Security 7
- Spring AI 2.0
- GraphQL
- PostgreSQL · pgvector
- Redis
- Flyway
- Testcontainers
- Docker
- Caddy
- GitHub Actions
- Angular 21
- TypeScript
- Tailwind CSS 4
ProcessBuilt with an AI-assisted workflow that I directed: I defined the architecture and contracts, wrote and approved the plan for each stage, and reviewed every change before it merged — about 1,800 backend and 590 frontend commits.
Code and client are private. Details are shared to the extent the work allows.