Case study · Wealth Management

Seven automation agents replace hours of weekly busywork.

A wealth management firm running on a legacy SaaS platform needed AI not for chat, but for grinding work: monthly cashflow reports, bank-statement export across 121 client accounts, deposits reports, credit-card statement extraction, and reconciliation. We built seven specialized agents, each focused, each callable from a different surface (SMS, REST API, cron), and quietly retired hundreds of monthly hours of manual click-work.

7

specialized workflow agents

121

client accounts in scope

4 hr

cron cadence for reconciliation

SMS

text-in command interface

The problem

A senior team spending its best hours on tasks that should run themselves.

A wealth management firm administering family-office and high-net-worth client accounts ran on a legacy practice-management SaaS, robust, regulated, mission-critical, and a constant source of repetitive click-work. Every month, partners and staff manually:

  • Pulled cashflow reports and "statement of deposits and payments" reports per client, on demand, often from a mobile phone
  • Downloaded monthly bank statements for all 121 client accounts and uploaded them to a separate reconciliation database
  • Ran reconciliation reports on a roughly four-hour cadence to keep the accounting system current
  • Fished invoices and statements out of a shared mailbox
  • Performed standard reconciliation for accounts that fell outside the bank's auto-feed
  • Generated and uploaded deposit reports per client engagement
  • Mass-downloaded credit-card statements per client, sorted by issuer

All of it was real work that mattered. None of it was work that needed a CPA's brain. The firm wanted these tasks to run themselves, but the legacy SaaS had no proper API. UI automation was the only path.

The approach

Seven specialized agents, each focused on one workflow.

We resisted the temptation to build "one big agent that does everything." Instead, each workflow got its own agent: a focused script with its own service port, its own logs, its own restart policy, its own failure mode. Composable, debuggable, independently scalable. They all share a Playwright browser layer that drives the legacy SaaS, a common SMS gateway, and a common deployment harness on Azure.

Agent 01 · Cashflow Report

Text the AI from your phone, get a PDF emailed back.

A partner texts the firm's Twilio number: "Run cashflow report for client 01048 from 03/01 to 03/31." The agent authenticates VIP senders by phone number, logs into the legacy SaaS, runs the named report, downloads the PDF or Excel output, and emails it to the requested recipient. Round-trip: under 90 seconds.

Agent 02 · Bank Statement Mass Export

All 121 client statements, one HTTP call.

A POST to /api/run-bank-statements with a date range triggers a mass export. The agent iterates through all 121 clients, downloads each statement PDF, sanitizes filenames for Windows compatibility, zips the entire package as cnb-statements.zip, and uploads it to the firm's reconciliation database via API.

Agent 03 · Report Automation Cron

Runs every four hours. Pages a human if anything fails.

Triggers the "Report Automation Index" jobs in the legacy SaaS on a four-hour cron. Date normalization (ISO → MM/DD/YYYY), auto-upload of the resulting ZIP to a central processing API, and, critically, a safety lock: if a run fails, the agent emails the team and pauses the schedule until a human clears the lock file. Failure should always be loud.

Agent 04 · Email Attachment Downloader

Pull every attachment from the shared inbox.

Microsoft Graph API integration to quantilus@agsny.com. Recursively extracts attachments from all new mail (including from forwarded threads), saves to a structured download folder by sender and date, and feeds them into downstream reconciliation pipelines.

Agent 05 · Standard Reconciliation (non-CNB)

Reconciliation for accounts that don't fit the standard flow.

For accounts outside the primary banking partner, the agent logs into the legacy SaaS's Standard Bank Recon module, fuzzy-matches bank-account names, inputs end-dates and statement balances, and downloads the generated Excel output. Disabled by default; activated per-engagement when a discrepancy needs handling.

Agent 06 · Deposits Report API

Per-client deposits reports, auto-uploaded to production.

REST API at /api/run-deposits-report. Accepts a list of clients and a date range, navigates to the Deposits Report page in the legacy SaaS, selects the right output template, downloads per-client Excel files, packages them in the expected deposit/ folder structure, and POSTs the ZIP to the firm's production API with a reference ID passed straight through for traceability.

Agent 07 · Mass Credit-Card Statement Export

CHASE, AMEX, Citi, Barclays, sorted, named, downloaded.

For specified clients and date ranges, the agent navigates to the Image Search interface, queries the Invoices category, uses robust keyboard navigation to select the right clients (UI flakiness was a real issue here), and filters the results grid for credit-card vendors. Critically, it bypasses the visual popup UI and downloads the underlying PDFs directly via the internal ShowImages.ashx endpoint, much faster, much more reliable than driving the popup.

Architecture

One VM, seven services, three callable surfaces.

  • Browser automation core. Playwright on Chromium drives the legacy SaaS UI. Persistent browser profile per workflow reduces MFA prompts on recurring runs.
  • Service surface. Three trigger types: SMS (Twilio webhook on port 3000), REST API (per-workflow ports 3001 / 3002 / 3003), and cron (every 4 hours). Each surface authenticates differently, SMS by phone-number whitelist, REST by shared secret, cron by environment.
  • Failure handling. Every workflow writes structured logs. The high-frequency cron has a safety-lock pattern: on failure, it emails the team, drops a .lock_report_failure file, and refuses to run again until a human clears the lock. Silent failure in financial workflows is unacceptable.
  • Process management. PM2 supervises long-running services (auto-restart, log rotation, process-list health). For workflows we want to manage explicitly outside PM2, plain nohup with structured log files.
  • Hosting. Single Azure VM (Ubuntu 22.04 x64), east-US region. Migrated from an earlier Ubuntu ARM64 host that broke under snap-confined Chromium. We document those migrations explicitly in the team's run-book.
  • Connectivity. SSH access via shared key; deploy pipeline packages updates locally and SCPs to the VM. Health-check endpoints on every service for monitoring.

The result

The team got their week back.

Two specific shifts mattered most. First, partners no longer spend the late afternoon waiting on a report to compile, they text it from a meeting, and the PDF lands in their inbox before they walk back to their desk. Second, the four-hour reconciliation loop runs without a person sitting in front of the legacy SaaS clicking through screens; the team now reviews exceptions, not every transaction.

Across the seven workflows, the firm reclaimed multiple full days of senior staff time every week. The new bottleneck isn't downloading statements, it's deciding what to do with them, which is the work that actually requires their expertise.

One subtle win worth calling out: the safety-lock pattern on the cron-triggered agent saved the team twice in the first quarter alone. A schema change in the legacy SaaS caused the report to download an empty file. The agent noticed, paused, emailed the team. Without the lock, four hours later the same broken run would have fired again, and again, and silently corrupted the downstream reconciliation database.

Tech stack

Automation

Playwright (Chromium), Node.js services, persistent browser profiles, structured logging

Surfaces & integrations

Twilio SMS (webhook), Microsoft Graph API (mailbox), REST APIs per workflow, cron, central upload API

Infrastructure

Azure VM (Ubuntu 22.04 x64), PM2 for long-running services, SSH-based deploy, health-check endpoints, safety-lock failure handling

Patterns worth stealing

Three lessons from this engagement.

  • Browser automation is still the right answer when the target SaaS has no API. A lot of mission-critical regulated systems don't expose what you need. Playwright + persistent profiles + structured selectors is a perfectly respectable production pattern when used with the same rigor as API integration.
  • Multiple specialized agents beat one general one. Each agent has one job, one port, one log file, one failure mode. We could have built an "operations agent" that did everything. We'd be debugging it now.
  • Loud failure beats quiet failure. The safety lock is unglamorous, but it's the single feature that made stakeholders comfortable letting the agent run unattended on a 4-hour cron in a financial workflow. Build the failure mode first.

Have a legacy SaaS draining your senior team's time?

If the system runs your business but its API is "log in and click," the workflow can probably still be automated. Bring us the workflow.

Start a Conversation