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.
| Agent | Job | Triggered by |
|---|---|---|
| 1: Search | Logs every directory query to Supabase and Slack | A directory search |
| 2: Scraper | Finds BPO companies on Google Maps, crawls their sites and extracts contact emails | A search that returns zero results |
| 3: Scoring | Asks Claude for an AI Readiness Score | Every profile update |
| 5: Claims | Checks ownership claims by email domain | An ownership claim |
| 6: News | Turns BPO and AI headlines into draft articles with Claude | Weekly cron, Monday morning |
| 7: Analytics | Posts a weekly stats digest to Slack | Weekly cron, Monday morning |
| 8 | Notifies people who requested a scan, and stores email replies | The scraper, or an incoming reply |
| 9: Outreach | Runs a three-email drip | A 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:
- Find companies Apify's
compass/crawler-google-placesactor finds matching BPO companies on Google Maps. - Read their websites The
apify/website-content-crawleractor pulls the text from each company site. - Extract emails A regex-based extractor harvests contact emails, preferring role inboxes like
info@andsales@on the matching domain. - Save the leads New leads are de-duplicated and written to Supabase.
- 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.
- Zero-result search
- Scraper (Apify)
- Email extraction
- Leads in Supabase
- Notify and outreach
An automated email drip that stops when someone replies
Agent 9 (Outreach) runs a three-email drip.
| When it goes out | What sends it | |
|---|---|---|
| First email | The moment the lead is created | Agent 9, chained from the scraper |
| Follow-up 2 | +24 hours | Hourly cron at /webhook/cron/outreach |
| Follow-up 3 | +48 hours | Hourly 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.
- Recipient replies
- Resend email.received webhook
- Agent 8 stores the reply
- Agent 9 marks it replied
- Drip stops
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.
| Domain check | What happens |
|---|---|
| Domains match | The claim is approved automatically and the user gets an email |
| Domains do not match | The 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.
Making a multi-agent backend safe to leave running
Three decisions did the heavy lifting:
- 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. - 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.
- 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