AI- Automated Google Ads at RexAI

A full-stack SaaS platform that connects users' Google Ads accounts via OAuth2, generates complete SKAG campaigns with Claude AI, and deploys them live — from business signup to running ads in minutes.
Express.js
MongoDB
React
Ant Design
Google 0Auth2
Stripe Webhooks
Agenda.js
Swagger / OpenAPI
Anthropic Claude-Sonnet-4

10-20 mins

Sync Time After
↓ 95% reduction (was 4.5 hrs)

-75%

AI Token Cost Reduction
via prompt caching + unified calls

Zero

Silent AI Features Post-Fix
real retry with exponential backoff

Overview

A Multi-Portal Workforce Platform for the Healthcare Industry

Physician scheduling in the US still runs on text threads, spreadsheets, and informal word-of-mouth. Shiftolic was built to replace this with a structured, real-time marketplace — connecting doctors who need coverage with hospitals and staffing companies that need reliable providers.

THE PROBLEM

Physicians needing shift coverage have no centralized platform. Locum companies manually manage pools of providers across spreadsheets and email chains. There is no structured way to post, discover, or claim shifts — resulting in last-minute scrambles and poor visibility for all parties.

The Solution

A four-portal SaaS platform where doctors can post and claim shifts, join specialty groups, verify credentials, and sync their calendar — while locum companies manage provider pools, post open shifts, and build out their facilities. One shared backend serves all portals.

Technology Stack

A four-portal SaaS platform where doctors can post and claim shifts, join specialty groups, verify credentials, and sync their calendar — while locum companies manage provider pools, post open shifts, and build out their facilities. One shared backend serves all portals.
NestJS
MongoDB
Mongoose
JWT Auth
React Native
React Native
Expo
Ant Design
Redux
AWS S3
Sendgrid
Twilio
Stripe
OpenAI
Google OAuth

SYSTEM ARCHITECTURE

How The Platform Works

A single Node.js/Express backend serves both the marketing website and the full SaaS dashboard. Users connect their Google Ads accounts via OAuth2, Claude generates all campaign assets, and the backend deploys everything through the Google Ads API - no manual steps.

ONBOARDING

Signup to Live Ads in a Single Gated Pipeline

5 steps
From account creation to first live Google Ads campaign - fully gated by OnboardingEnum state on every auth check.

RexAI's onboarding is a strictly ordered, gated pipeline. Each step in the OnboardingEnum acts as a gate — the frontend reads the user's onboardingStep field on every auth check and routes accordingly, preventing dashboard access until all steps are complete. The backend enforces the same order — any out-of-sequence request returns a 403..

After email verification, the user is directed to Stripe Checkout. The backend listens for the checkout.session.completed webhook event and advances onboardingStep only after Stripe confirms payment — client-side state is never trusted for subscription gating.

Business Information Model

The BusinessInformation schema captures name, street address, city, state, zip, phone, website URL, and geo-coordinates sourced via Google Places API autocomplete. A pre-save middleware runs against a requiredFields array and sets isComplete: true only when all fields pass. This flag gates progression to the Google Ads connection step.

Master MCC Account Linking

After business info, the user connects their Google Ads account via OAuth2. A single master MCC (My Client Center) account is configured at the platform level. The OAuth callback stores the user's access_token, refresh_token, and tokenExpiresAt in the GoogleAdsManager collection. A corresponding CustomerAccount document is then created, referencing both the master manager and the user's specific customerId.

Resumable State Machine

The user's onboardingStep in MongoDB is the single source of truth. A user who drops off mid-onboarding resumes exactly where they left off on next login - the checkAccountStatus endpoint returns their current step and the frontend routes accordingly. No step can be re-entered or skipped once completed.

login_customer_id Header
Every Google Ads API request uses the master manager's account ID in the login_customer_id header while targeting the user's specific customer account ID. This is the standard MCC sub-account access pattern - one developer token, centralised quota management, full audit visibility across all user accounts.

AI CONTENT GENERATION

Claude Builds Every Campaign Asset

The core of RexAI is ClaudeAIService, which calls claude-sonnet-4-20250514 with an 8,000-token budget to generate a complete Google Ads campaign structure from business data. The system prompt designates Claude as a "master Google Ads expert and copywriter specializing in SKAG architecture" that must always respond in valid JSON.

SKAG Methodology

The platform enforces Single Keyword Ad Group (SKAG) architecture as a hard constraint in the system prompt. Every campaign contains exactly one ad group per target keyword. Each ad group is named keyword (phrase) to make match type explicit. Campaign names follow the static convention: Search | {Service} | Phrase. This maximises Quality Score and simplifies performance attribution by ensuring each ad is written around one search intent.

The prompt runs a three-step process: Business Research (website analysis, USPs, CTAs, brand voice), Keyword Development (service-level campaigns with SKAG grouping), and Ad Copy Creation (all headlines, descriptions, and extensions per ad group). Character limits are enforced inside the prompt itself — if any field exceeds its maximum, Claude is instructed to truncate at a semantic break.

Existing Campaign Awareness

The prompt receives the user's existingUserCampaigns array — Claude will not regenerate campaigns for services that already have live entries, preventing duplicates and respecting the current account state across multiple generation runs.

JSON - only output contract
The system prompt explicitly forbids preamble, commentary, or any text outside the JSON schema. Combined with max_tokens: 8000, this eliminates the need for any post-processing or sanitisation layer before the response is written to AdGeneration.

AD APPROVAL FLOW

Pending → Approved → Deployed

Two-state
approval model
Generated content starts as pending in AdGeneration. No campaign is deployed to Google Ads until a human approves it.

After Claude generates the campaign structure, every document is written to the AdGeneration collection with status: 'pending'. The Ad Copy Review dashboard page surfaces all generated content — every headline, description, keyword, extension, budget recommendation, and bid strategy reasoning — for user review before any Google Ads API call is made.

Inline Editing with Validation

All fields are editable in-place. The adApprovalController handles granular field-level updates. Client-side validation enforces Google Ads character limits before each save — headlines cannot exceed 30 characters, descriptions 90 — mirroring the Google Ads API's own validation to prevent deployment failures from policy violations.

Deployment Pipeline

Once the user approves, the backend transitions AdGeneration.status to 'approved' and sets approvedAt. The campaignService then runs a sequential orchestration against Google Ads API v21: create campaign with budget and bid strategy → create ad group → create responsive search ad → attach callouts → attach sitelinks → attach structured snippets → attach call extensions. Each step creates a corresponding document (Campaign, AdGroup, Ad) in MongoDB with the returned Google resource IDs.

Marketing Information Sync
A post('save') and post('findOneAndUpdate') middleware on AdGeneration calls MarketingInformation.syncFromAdGeneration() whenever an existing generation is modified. This keeps the marketing profile and campaign data in sync without additional reconciliation jobs.

GOOGLE ADS INTEGRATION

Master MCC Controls All Sub-Accounts

The Google Ads integration is the most technically complex layer of the platform. A single Master MCC is configured once at the platform level. Every user's Google Ads account connects under this manager via OAuth2, enabling the backend to act on any account using one set of manager-level credentials and a single developer token.

OAuth2 Token Lifecycle

The frontend redirects to Google's OAuth2 consent screen via generateAuthUrl() with the https://www.googleapis.com/auth/adwords scope. The backend callback calls getTokensFromCode() to exchange the authorisation code for access_token and refresh_token, stored in GoogleAdsManager alongside a tokenExpiresAt timestamp. Before every API request, shouldRefreshToken() checks whether the token expires within the threshold — if so, refreshAccessToken() is called proactively, and the new token is written back before the API call proceeds.

GAQL Reporting

The Reporting section executes GAQL (Google Ads Query Language) queries against the Google Ads API — pulling impressions, clicks, CTR, cost, conversions, and average CPC at the campaign, ad group, and keyword level. Date-range parameters are passed as GAQL WHERE segments.date BETWEEN clauses. Results are returned directly to the frontend with no intermediate cache layer.

login_customer_id on every request
All Google Ads API requests use the master manager's ID in the login_customer_id header while targeting the user's specific customerId. This is the required pattern for MCC-managed sub-account access and enables the platform to operate across all accounts from a single developer token without per-user token escalation.

BILLING

Stripe Subscription Billing with Webhooks

Subscription management is handled exclusively through Stripe webhooks — the backend never trusts client-side state for subscription status. Every event is validated with stripe.webhooks.constructEvent() against the STRIPE_ENDPOINT_SECRET before processing. The Stripe webhook route is registered before any global express.json() middleware to preserve the raw body required for signature verification.

The Plan model defines available tiers with associated feature flags. When a user's subscription changes, the ActiveSubscription document is updated and downstream access-control gates (campaign limits, reporting access) reflect the new state immediately on the next protected request.

Payment Retry Flow

If a payment fails during onboarding or on renewal, the user is redirected to /payment-retry. A new Stripe checkout session is created for the same plan. The checkAccountStatus endpoint validates subscription and onboardingStep state on every protected page load, routing users to the retry page if their subscription is in a failed state.

Raw Body Parser Required
Express's express.json() middleware modifies the request body, which invalidates Stripe's HMAC signature. The webhook endpoint uses a dedicated express.raw() parser, registered before the global JSON middleware, to ensure the unmodified payload is available for stripe.webhooks.constructEvent().

OPTIMIZATION SYSTEMS

Automated Bidding & Negative Keyword Management

Beyond campaign creation, RexAI includes two automated optimisation layers running on schedule via Agenda.js — a MongoDB-backed job queue. Jobs are defined once and scheduled per-user on account activation. History, retry logic, and failure tracking live in the same Atlas cluster as application data.

Bidding Optimization

The biddingService and budgetOptimizationService pull campaign-level metrics from the Google Ads API — impressions, CTR, conversion rate, cost-per-conversion — and pass them through the budgetOptimizer.js multi-factor scoring model. The bidStrategy.js utility maps AI-recommended BidStrategyType enum values to Google Ads API enum values and validates compatibility with the campaign's current settings before submitting any mutation.

Negative Keyword Service

NegativeKeywordService queries the last 30 days of search term data via GAQL, identifies terms with clicks but zero conversions, and adds them as phrase-match negatives at the campaign level. It first runs checkAccountConversionsLast30d() - if the account has fewer than 1 conversion in the period, negative keywords are not added, preventing premature exclusions on new accounts. Every addition is recorded in NegativeKeywordAudit with timestamp, campaign, term, and reason.

Budget Optimizer Scoring
budgetOptimizer.js weights three factors per campaign: conversion rate, impression share lost to budget, and actual vs. target CPA. The resulting score drives reallocation recommendations surfaced in the dashboard and applied in one click.
Auto
Optimised
Bidding, budgets, and negative keywords managed via scheduled Agenda.js jobs with MongoDB-backed retry and audit logs.

ENGINEEING DECISIONS

Key Technical Decisions

Non-trivial architectural and implementation choices driven by real constraints encountered with the Google Ads API, Stripe, and the Anthropic integration.

Strict JSON-Only Claude Prompt

The Claude system prompt explicitly forbids preamble, commentary, or any text outside the JSON schema. Character limits are enforced inside the prompt — if any field exceeds its max, Claude truncates at a semantic break. This eliminated the need for any post-processing or sanitisation layer before writing to AdGeneration.

SKAG Architecture as a Hard Constraint

Rather than letting Claude choose campaign structure, SKAG is enforced as a non-negotiable constraint in the system prompt. Every service produces exactly one campaign; every campaign has one ad group per keyword. Maximises Quality Score and simplifies performance attribution by ensuring each ad maps to exactly one search intent.

Master MCC for Multi-Tenancy

A single master MCC mediates all sub-account access via login_customer_id. One developer token covers all user accounts, API quota is managed centrally, and all mutations are auditable from a single source. Users connect via OAuth2 and grant access — no credential sharing occurs between the platform and user accounts.

Proactive Token Refresh on Every API Call

shouldRefreshToken() compares tokenExpiresAt against a threshold before every Google Ads API request. Proactive refresh prevents mid-operation token failures during campaign deployment, which would otherwise leave campaigns in a partially created state with no clean rollback path.

Agenda.js Over Cron / Lambda

Optimisation jobs use Agenda.js because it stores job definitions, status, and retry history in the existing MongoDB Atlas cluster. No additional infrastructure — same database, same Atlas console, same monitoring. Per-user jobs are scheduled at account activation and survive server restarts via the MongoDB-persisted job queue.

Onboarding as a DB State Machine

The user's onboardingStep field is the single source of truth for funnel position. Frontend reads it on every auth check; backend validates it on every onboarding endpoint. No client manipulation can skip a step. A user who drops off mid-onboarding resumes exactly where they left off — zero re-entry of already-completed data.

OUTCOMES

Delivered

01

Full AI Campaign Generation

Claude generates complete SKAG campaigns with headlines, descriptions, all extension types, budget recommendations, and bid strategy reasoning from a single business profile.

02

Google Ads OAuth2 Linking

One-click OAuth2 flow connects user ad accounts to the master MCC. shouldRefreshToken() handles proactive token refresh — users never need to reconnect.

03

Master MCC Architecture

Single manager account controls all user sub-accounts via login_customer_id. Centralised quota, one developer token, full audit visibility across every account.

04

Human-in-the-Loop Approval

Every generated campaign enters status: pending. Inline editing with Google Ads char-limit validation before approval. Zero unreviewed content ever reaches the API.

05

Stripe Webhook Billing

Six webhook event types handled. Raw body parser preserves signature integrity. Subscription state drives all feature access. Payment retry flow on failed checkouts.

06

GAQL Reporting Dashboard

Live impressions, clicks, CTR, cost, and conversions from Google Ads via GAQL queries. Filterable by campaign, ad group, keyword, and date range.

07

Automated Bidding Optimisation

Agenda.js jobs run multi-factor scoring across campaign metrics and adjust bid strategies and budgets continuously — no manual tuning required post-launch.

08

Negative Keyword Automation

GAQL search term analysis with conversion-based gating before additions. Full NegativeKeywordAudit trail in MongoDB for complete transparency.

09

Gated Onboarding State Machine

onboardingStep as single source of truth. Frontend + backend both enforce step order. Fully resumable — drop-off and re-entry with zero data re-entry.

Your next product runs on AI. Let's build it.

Tell us what you're building and we'll show you how AI can make it faster, smarter, and built to last.
Let's Talk