Multi-Agent AI Backend: How I Built BPOAI's Nine-Agent Server

Behind the BPOAI directory runs a separate Express server with nine agents. They find new companies, score them, verify ownership claims, follow up by email and draft weekly news, all triggered by webhooks and cron.

By Wasif Rehman · · AI agents

In short

  • Run agents on their own server behind a webhook contract, and have the main site swallow agent errors so an outage cannot break it.
  • Respond to webhooks instantly and do the real work in background tasks with error guards.
  • Give every recurring job exactly one driver, so nothing is sent twice.
  • Use Claude where judgment helps and plain code where a simple check is enough.
  • Close the loop on outreach: a reply should stop the drip automatically.

An AI agent server kept apart from the main site

BPOAI is a directory and intelligence hub for the BPO industry that I led at AIDEVGEN. The public Next.js site is only half the story. Behind it runs a separate agent server: a small Express app whose whole job is to make the directory feel alive.

The main site never runs an agent inline. It just fires a webhook. A small helper POSTs to the agent server with a shared-secret header and swallows every error, so an agent outage can never break the main site.

POST ${AGENT_SERVER}/webhook/{event}
x-agent-secret: <secret>

The Express app exposes routes such as /webhook/search, /webhook/claim-submit, /webhook/profile-update and a few /webhook/cron/* endpoints. That clean contract lets the agents evolve, crash and redeploy independently of the customer-facing site.

The agents in this walkthrough
AgentJobTriggered by
1: SearchLogs every directory query to Supabase and SlackA directory search
2: ScraperFinds BPO companies on Google Maps, crawls their sites and extracts contact emailsA search that returns zero results
3: ScoringAsks Claude for an AI Readiness ScoreEvery profile update
5: ClaimsChecks ownership claims by email domainAn ownership claim
6: NewsTurns BPO and AI headlines into draft articles with ClaudeWeekly cron, Monday morning
7: AnalyticsPosts a weekly stats digest to SlackWeekly cron, Monday morning
8Notifies people who requested a scan, and stores email repliesThe scraper, or an incoming reply
9: OutreachRuns a three-email dripA new lead with an email, then an hourly cron

Scraping leads with Apify when a search finds nothing

The most interesting flow starts with a failed search. Agent 1 (Search) logs every directory query to Supabase and Slack. If a search returns zero results, it sets a flag that fires Agent 2 (Scraper) in the background. The site gets its response immediately, and the scrape runs after.

Agent 2 is the workhorse:

  1. Find companies Apify's compass/crawler-google-places actor finds matching BPO companies on Google Maps.
  2. Read their websites The apify/website-content-crawler actor pulls the text from each company site.
  3. Extract emails A regex-based extractor harvests contact emails, preferring role inboxes like info@ and sales@ on the matching domain.
  4. Save the leads New leads are de-duplicated and written to Supabase.
  5. Chain two more agents Agent 8 notifies anyone who requested that scan, and Agent 9 starts an outreach campaign for every lead that has an email.
  1. Zero-result search
  2. Scraper (Apify)
  3. Email extraction
  4. Leads in Supabase
  5. Notify and outreach
The search, scrape and outreach chain

An automated email drip that stops when someone replies

Agent 9 (Outreach) runs a three-email drip.

The outreach schedule
EmailWhen it goes outWhat sends it
First emailThe moment the lead is createdAgent 9, chained from the scraper
Follow-up 2+24 hoursHourly cron at /webhook/cron/outreach
Follow-up 3+48 hoursHourly cron at /webhook/cron/outreach
  • A unique database index prevents the same lead being enrolled twice.
  • An admin master switch can pause all outreach at once.

The clever part is the feedback loop. When a recipient replies, Resend's email.received webhook hits the server. Agent 8 stores the reply and calls back into Agent 9, which marks the campaign replied and stops the drip.

  1. Recipient replies
  2. Resend email.received webhook
  3. Agent 8 stores the reply
  4. Agent 9 marks it replied
  5. Drip stops
How a reply stops the drip

AI readiness scoring with Claude, and when a domain check is enough

Agent 3 (Scoring) fires on every profile update. It asks Claude to compute an AI Readiness Score from 0 to 100, based on the company's listed tools, services and profile completeness.

Agent 5 (Claims) handles ownership claims with a plain check: it compares the claimant's email domain to the company's website domain.

How ownership claims are decided
Domain checkWhat happens
Domains matchThe claim is approved automatically and the user gets an email
Domains do not matchThe claim goes to an admin Slack channel for manual review

Weekly cron agents for AI news and analytics

Every Monday morning, two agents wake up.

  • Agent 6 (News) scrapes BPO and AI headlines across a set of topics with Apify's Google search scraper and content crawler. Claude turns each article into an SEO title, a summary and a clean HTML body, saved as a pending draft for an admin to approve. It also writes one original "trending" article naming the week's top-scoring and newly added companies.
  • Agent 7 (Analytics) compiles weekly stats, such as new companies, claims, top searches and highest scores, and posts a digest to Slack.

I wrote more about where to keep people in the loop in When not to let the AI finish the job.

Shared libraries that keep each AI agent small

Four small libraries keep the agents thin.

The shared toolbelt
LibraryWhat it does
claude.tsWraps the Claude integration and exposes typed helpers: calculateAIScore, generateArticleSummary and extractBPOData
apify.tsWraps the Google Maps and content-crawler actors
slack.tsGives every agent one consistent way to post execution logs, lead cards and error alerts to dedicated channels
emailExtract.tsDoes the unglamorous but vital work of finding a real, reachable email on a company website

Each agent reads almost like a recipe, because the messy parts live here.

Making a multi-agent backend safe to leave running

Three decisions did the heavy lifting:

  1. Fire and forget by default Webhooks respond instantly, and the real work runs in background tasks with .catch() guards. A slow scrape never times out a user request.
  2. One driver per job Outreach follow-ups are driven only by the external cron, never by a second in-process timer, to avoid double-sending.
  3. Humans gate anything public AI-written articles and ambiguous claims wait for approval.

Nine agents, one webhook contract and a handful of shared tools turn a static directory into something that grows, scores and follows up by itself. It is the part of BPOAI users never see, and the part I had the most fun building.

Frequently asked questions

How do you connect AI agents to a Next.js site without slowing it down?

Keep them on a separate server. The BPOAI site only fires a webhook with a shared-secret header, the helper swallows every error, and the agent server responds instantly and does the work in background tasks.

How does BPOAI find new BPO companies?

When a directory search returns zero results, the scraper agent uses Apify to find matching companies on Google Maps, crawls their websites, extracts contact emails and saves de-duplicated leads to Supabase.

How do you stop an email drip when someone replies?

Listen for replies. Resend's email.received webhook reaches the agent server, Agent 8 stores the reply, and it calls the outreach agent to mark the campaign replied, which stops the follow-ups.

How is the AI Readiness Score calculated?

Claude computes a score from 0 to 100 from the company's listed tools, services and profile completeness. It runs on every profile update, and a score of 70 or more earns an "AI-Ready" badge.

Does BPOAI publish AI-written news automatically?

No. The news agent saves Claude-written articles as pending drafts, and an admin approves them. No content auto-publishes.

Case study: BPOAI

All posts