How to Build a SaaS with AI Agents: Architecture for Founders

ACAbhishek Chauhan··7 min read
How to Build a SaaS with AI Agents: Architecture for Founders

You have a SaaS idea that uses AI agents. Maybe it's a RevOps platform that autonomously manages your sales pipeline, or an inventory system that forecasts demand, or a procurement tool that matches tenders to company profiles. How do you actually architect it?

After building three AI-powered SaaS products — RevAgent (autonomous RevOps), BandiFinder (procurement matching), and FiGo (AI fitness marketplace) — here's the architecture that works, the decisions that matter, and the traps that waste months.

The Stack Decision

Before writing code, you need to decide on four layers:

Layer Options My Default
Frontend Next.js, Remix, SvelteKit Next.js (App Router) — best ecosystem for AI features
API Next.js API routes, Hono, Express, FastAPI Hono for agent APIs, Next.js routes for frontend APIs
Database Supabase (Postgres), PlanetScale, Neon Supabase — auth + DB + realtime + storage in one
AI Orchestration LangGraph, Vercel AI SDK, custom LangGraph for complex agents, Vercel AI SDK for chat UIs

Why these defaults:

Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                     Next.js Frontend                      │
│  (Dashboard, Settings, Chat UI, Billing Portal)           │
└──────────────┬────────────────────────┬──────────────────┘
               │                        │
        Vercel AI SDK              REST/tRPC
        (streaming chat)           (CRUD, config)
               │                        │
┌──────────────▼────────────────────────▼──────────────────┐
│                      API Layer (Hono)                      │
│  /v1/agents, /v1/deals, /v1/settings, /v1/billing         │
└──────┬──────────┬──────────┬──────────┬──────────────────┘
       │          │          │          │
┌──────▼───┐ ┌───▼────┐ ┌───▼────┐ ┌───▼────────┐
│ LangGraph│ │Supabase│ │ Stripe │ │ CRM/Email  │
│ Agents   │ │Postgres│ │Billing │ │ Integrations│
│          │ │+ Auth  │ │        │ │ (MCP)      │
└──────────┘ └────────┘ └────────┘ └────────────┘

Multi-Tenancy: The Decision That Shapes Everything

Multi-tenancy is the most consequential early architectural decision. You have three options:

Option 1: Shared Database + Row Level Security (Recommended for 0→PMF)

Every tenant's data lives in the same Postgres database. Supabase RLS policies enforce isolation:

-- Every table has a tenant_id column
ALTER TABLE deals ENABLE ROW LEVEL SECURITY;
 
CREATE POLICY "Tenants see only their data"
  ON deals FOR ALL
  USING (tenant_id = auth.jwt() ->> 'tenant_id');

Pros: Simple, cheap, one database to manage. Supabase handles auth + RLS together. Cons: Noisy neighbor risk at scale (one tenant's heavy query affects others).

This is what RevAgent and BandiFinder use. It works until you have ~500 tenants or a tenant processing millions of records.

Option 2: Schema-Per-Tenant

Each tenant gets their own Postgres schema within the same database:

CREATE SCHEMA tenant_acme;
CREATE TABLE tenant_acme.deals (...);

Pros: Better isolation, per-tenant backups possible, no RLS overhead. Cons: Migration complexity (run ALTER TABLE across N schemas), connection pooling challenges.

Option 3: Database-Per-Tenant

Each tenant gets their own database. Maximum isolation, maximum operational overhead. Only for enterprise with strict compliance requirements (healthcare, finance).

My advice: Start with Option 1. You can always migrate to Option 2 when you hit scaling limits. Don't over-engineer isolation before you have paying customers.

Agent Architecture

The Agent Layer

Your AI agents are the core value proposition. Architect them as independent, composable services — not monoliths.

# Each agent is a separate LangGraph graph
from langgraph.graph import StateGraph, START, END
 
# Risk Agent — scores deal health
risk_graph = StateGraph(RiskState)
risk_graph.add_node("fetch_data", fetch_deal_data)
risk_graph.add_node("analyze", analyze_signals)
risk_graph.add_node("score", compute_risk_score)
# ... edges, compile
 
# Forecast Agent — predicts revenue
forecast_graph = StateGraph(ForecastState)
forecast_graph.add_node("gather_pipeline", gather_pipeline_data)
forecast_graph.add_node("calculate", run_forecast_model)
# ... edges, compile
 
# Orchestrator — chains agents based on events
async def on_deal_updated(deal_id: str):
    risk = await risk_agent.invoke({"deal_id": deal_id})
    if risk["risk_score"] > 0.7:
        await escalation_agent.invoke({"deal_id": deal_id, "risk": risk})
    await hygiene_agent.invoke({"deal_id": deal_id})

Why separate agents instead of one big agent?

  1. Independent deployment: Update the risk scoring logic without touching forecasting.
  2. Independent scaling: The chat agent handles 100x more requests than the weekly brief generator.
  3. Independent testing: Each agent has its own evaluation dataset and quality metrics.
  4. Independent cost tracking: Know exactly which agent is burning tokens.

Connecting Agents to External Systems (MCP)

Use MCP (Model Context Protocol) servers for CRM, email, and other integrations:

from langchain_mcp_adapters.client import MultiServerMCPClient
 
client = MultiServerMCPClient({
    "hubspot": {"transport": "http", "url": "https://hubspot-mcp.yourapp.com/mcp"},
    "gmail": {"transport": "http", "url": "https://gmail-mcp.yourapp.com/mcp"},
})
 
tools = await client.get_tools()
agent = create_agent("gpt-4o-mini", tools)

Build MCP servers once, share across all your agents. When you add Salesforce support, you add one MCP server — every agent gets it automatically.

The Hybrid Pattern: LLM + Rules

Don't use LLMs for everything. Rule-based logic is faster, cheaper, and deterministic:

async def score_risk(deal: Deal) -> float:
    # Rule-based signals (instant, free)
    score = 0.0
    if deal.days_open > 90: score += 0.3
    if deal.last_activity_days > 14: score += 0.2
    if deal.stage == "negotiation" and deal.days_in_stage > 30: score += 0.2
 
    # LLM-based signals (only when rules aren't enough)
    if deal.has_email_threads:
        sentiment = await llm_analyze_sentiment(deal.email_threads)
        score += (1 - sentiment) * 0.3
 
    return min(score, 1.0)

RevAgent uses LLMs for email sentiment analysis and natural language queries. Everything else — risk scoring heuristics, forecast calculations, CRM field validation — is deterministic code. This keeps costs 80% lower than an all-LLM approach.

Billing: Stripe + Usage-Based Pricing

AI SaaS has a unique billing challenge: your costs scale with usage (LLM tokens, API calls) but customers expect predictable pricing.

The 5-Tier Model

Pilot     → Free  → 5 deals, basic risk detection
Starter   → $49/mo → 50 deals, full pipeline
Growth    → $149/mo → Forecast agent, weekly briefs
Enterprise → $399/mo → Chat agent, SAML SSO
Enterprise+ → $799/mo → Dedicated support, custom integrations

Implementation:

// Stripe Checkout + webhooks
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: plan.stripePriceId, quantity: 1 }],
  subscription_data: { trial_period_days: 7 },
  success_url: `${BASE_URL}/settings/billing?success=true`,
  cancel_url: `${BASE_URL}/settings/billing`,
});
 
// Webhook: provision tenant on successful payment
app.post('/v1/billing/webhook', async (c) => {
  const event = stripe.webhooks.constructEvent(body, sig, secret);
  if (event.type === 'checkout.session.completed') {
    await provisionTenant(event.data.object);
  }
});

Feature Gating

Gate agent features by plan, not by seat count:

async function canUseAgent(tenantId: string, agentName: string): Promise<boolean> {
  const plan = await getTenantPlan(tenantId);
  const agentAccess: Record<string, string[]> = {
    'pilot': ['risk'],
    'starter': ['risk', 'hygiene', 'follow_up'],
    'growth': ['risk', 'hygiene', 'follow_up', 'forecast', 'brief'],
    'enterprise': ['risk', 'hygiene', 'follow_up', 'forecast', 'brief', 'chat'],
  };
  return agentAccess[plan]?.includes(agentName) ?? false;
}

Authentication & Authorization

Supabase Auth + RBAC

Supabase Auth handles signups, logins, and OAuth. Add RBAC with a simple roles table:

CREATE TABLE user_roles (
  user_id UUID REFERENCES auth.users(id),
  tenant_id UUID REFERENCES tenants(id),
  role TEXT CHECK (role IN ('admin', 'member', 'viewer')),
  PRIMARY KEY (user_id, tenant_id)
);
 
-- RLS policy: only admins can modify settings
CREATE POLICY "Admins only" ON tenant_settings
  FOR ALL USING (
    EXISTS (
      SELECT 1 FROM user_roles
      WHERE user_id = auth.uid()
      AND tenant_id = tenant_settings.tenant_id
      AND role = 'admin'
    )
  );

For Enterprise: SAML SSO

Enterprise customers expect SSO. Supabase supports SAML natively. RevAgent gates SSO behind the Enterprise plan — it's a zero-cost upsell that enterprise buyers require.

Deployment

The Simple Path: Vercel + Supabase

Frontend (Next.js) → Vercel
API (Hono)         → Vercel Functions (Fluid Compute)
Agents (LangGraph) → LangSmith Deployments or Vercel Functions
Database           → Supabase
Billing            → Stripe

Why Vercel: Zero-config deployments, preview URLs for every PR, automatic scaling. Fluid Compute reuses function instances across concurrent requests — significantly better cold start behavior than traditional serverless.

Scheduled Jobs

AI agents often need scheduled execution (daily risk scans, weekly briefs, data syncing):

Daily risk scan    → 9am UTC  → POST /v1/jobs/daily-risk-scan
Weekly brief       → Mon 8am  → POST /v1/jobs/deliver-weekly-brief
CRM sync           → 6am UTC  → POST /v1/jobs/sync-deals-from-crm

Use Vercel Cron Jobs or Supabase pg_cron — both work. For RevAgent, cron jobs trigger the API which invokes the relevant LangGraph agent.

The Build-vs-Buy Matrix

Not everything should be custom-built:

Component Build Buy Why
Agent orchestration Build (LangGraph) Core IP, needs full control
Auth Buy (Supabase Auth) Commodity, security-critical
Billing Buy (Stripe) Commodity, compliance-heavy
CRM integration Build (MCP server) Custom to your data model
Email/Slack notifications Buy (Resend, Slack API) Commodity
Monitoring Buy (LangSmith) Specialized for LLM observability
Vector store Depends Supabase pgvector Build if scale demands Pinecone

Rule: Build what differentiates your product. Buy everything else.

Common Mistakes

1. Building the agent before the product. Start with a manual version — even if it's you doing the work. Validate that customers want the outcome before automating with AI.

2. One mega-agent. Split into focused agents from day one. A monolithic agent is impossible to debug, test, and scale independently.

3. No fallback when the LLM is down. Every agent path needs a deterministic fallback. LLM APIs have outages. Your customers don't care why.

4. Charging per seat instead of per value. AI SaaS costs scale with usage, not users. Price by the value delivered (deals managed, forecasts generated) not by headcount.

5. Skipping evaluation datasets. You can't improve what you can't measure. Build 50 test cases before your first customer.

6. Over-engineering multi-tenancy. Start with shared DB + RLS. You can always migrate later. Don't build database-per-tenant before you have 10 customers.

Related Posts


Building an AI-powered SaaS? I've architected and shipped three from scratch. Get in touch or book a call.