Skip to main content

Basis Growth Module Architecture

Premium add-on for clinics: AI Website Builder + Lead Generation + Nurturing

Overview

A unified growth platform that enables clinics to:

  1. Build AI-generated websites with prompt-based editing
  2. Run Meta & Google ad campaigns with 1-click setup
  3. Capture and nurture leads through automated messaging
  4. Track full-funnel attribution from impression → member LTV

Module Structure (Basis Flow)

/app/growth/
├── page.tsx # Dashboard overview
├── website/
│ ├── page.tsx # Website builder
│ ├── editor/page.tsx # Visual/prompt editor
│ ├── templates/page.tsx # Template gallery
│ └── settings/page.tsx # Domain, SEO, analytics
├── campaigns/
│ ├── page.tsx # Campaign list
│ ├── create/page.tsx # Campaign wizard
│ ├── [id]/page.tsx # Campaign detail/edit
│ └── analytics/page.tsx # Cross-campaign performance
├── leads/
│ ├── page.tsx # Lead pipeline/CRM
│ ├── [id]/page.tsx # Lead detail
│ └── segments/page.tsx # Audience segments
├── messaging/
│ ├── page.tsx # Message sequences
│ ├── templates/page.tsx # Email/SMS templates
│ ├── automations/page.tsx # Drip campaign builder
│ └── inbox/page.tsx # Conversation view
└── analytics/
├── page.tsx # Full funnel metrics
├── attribution/page.tsx # Source attribution
└── ltv/page.tsx # Customer lifetime value

Data Models (Firestore)

Website

// Collection: clinicsv2/{clinicId}/websites/{websiteId}
interface Website {
id: string;
clinicId: string;

// Domain
subdomain: string; // "drsebis" → drsebis.basis.health
customDomain?: string; // "www.drsebiscellfoods.com"
domainVerified: boolean;
sslStatus: 'pending' | 'active' | 'error';

// Content
templateId: string;
pages: WebsitePage[];
globalStyles: {
primaryColor: string;
secondaryColor: string;
fontHeading: string;
fontBody: string;
logoUrl: string;
faviconUrl: string;
};

// SEO
seo: {
title: string;
description: string;
ogImage: string;
keywords: string[];
};

// ICP Context (for AI generation)
icpProfile: {
targetAudience: string;
painPoints: string[];
desiredOutcomes: string[];
competitors: string[];
uniqueValue: string;
};

// State
status: 'draft' | 'published' | 'archived';
publishedAt?: Timestamp;
lastEditedAt: Timestamp;
lastEditedBy: string;

// Analytics
analyticsId?: string; // GA4 measurement ID
metaPixelId?: string;

createdAt: Timestamp;
updatedAt: Timestamp;
}

interface WebsitePage {
id: string;
slug: string; // "about", "services", "contact"
title: string;
type: 'home' | 'about' | 'services' | 'pricing' | 'contact' | 'blog' | 'landing' | 'custom';

// Content blocks (structured for AI editing)
blocks: ContentBlock[];

// Page-level SEO override
seo?: {
title?: string;
description?: string;
noIndex?: boolean;
};

isPublished: boolean;
sortOrder: number;
}

interface ContentBlock {
id: string;
type: 'hero' | 'features' | 'testimonials' | 'pricing' | 'cta' | 'text' | 'image' | 'video' | 'form' | 'faq' | 'team' | 'gallery' | 'custom';

// Block-specific content (varies by type)
content: Record<string, any>;

// Styling
styles?: {
backgroundColor?: string;
padding?: string;
customCss?: string;
};

// Visibility
isVisible: boolean;
visibleOnMobile: boolean;
}

Ad Campaigns

// Collection: clinicsv2/{clinicId}/campaigns/{campaignId}
interface AdCampaign {
id: string;
clinicId: string;

// Basic info
name: string;
objective: 'grow_sales' | 'acquisition' | 'win_back';
status: 'draft' | 'pending_review' | 'active' | 'paused' | 'completed' | 'rejected';

// Budget
budget: {
type: 'daily' | 'lifetime';
amount: number; // cents
currency: 'USD' | 'CAD';
maxCostPerLead?: number; // cents
targetOrderValue?: number; // cents
};

// Targeting
targeting: {
countries: string[]; // ['US', 'CA']
ageRange?: { min: number; max: number };
genders?: ('male' | 'female' | 'all')[];
interests?: string[];
customAudiences?: string[]; // audience IDs from platform
lookalikes?: string[];
};

// Channels
channels: {
meta: boolean;
google: boolean;
// Future: tiktok, linkedin, etc.
};

// Platform-specific IDs (after creation)
externalIds?: {
metaCampaignId?: string;
metaAdSetId?: string;
googleCampaignId?: string;
};

// Creative
creatives: AdCreative[];

// Landing
landingPageId?: string; // links to Website page
leadFormId?: string; // links to LeadForm

// Schedule
schedule: {
startDate: Timestamp;
endDate?: Timestamp;
dayParting?: { // optional time-of-day targeting
days: number[]; // 0-6, Sunday = 0
hours: number[]; // 0-23
};
};

// Performance (aggregated, updated periodically)
metrics: CampaignMetrics;

createdAt: Timestamp;
updatedAt: Timestamp;
createdBy: string;
}

interface AdCreative {
id: string;
type: 'image' | 'video' | 'carousel';

// Content
headline: string;
primaryText: string;
description?: string;
callToAction: 'learn_more' | 'sign_up' | 'book_now' | 'get_offer' | 'contact_us';

// Media
mediaUrls: string[]; // Storage URLs
thumbnailUrl?: string; // For video

// A/B testing
isControl: boolean;
testVariant?: string; // 'A', 'B', etc.

// Platform-specific IDs
externalIds?: {
metaAdId?: string;
googleAdId?: string;
};

// Performance
metrics: CreativeMetrics;
}

interface CampaignMetrics {
impressions: number;
clicks: number;
ctr: number; // click-through rate
spend: number; // cents
leads: number;
costPerLead: number; // cents
conversions: number; // leads → members
conversionRate: number;
revenue: number; // from converted members
roas: number; // return on ad spend
lastUpdated: Timestamp;
}

interface CreativeMetrics {
impressions: number;
clicks: number;
ctr: number;
spend: number;
leads: number;
costPerLead: number;
}

Lead Forms

// Collection: clinicsv2/{clinicId}/leadForms/{formId}
interface LeadForm {
id: string;
clinicId: string;

name: string;
type: 'inline' | 'popup' | 'embedded' | 'meta_native' | 'google_native';

// Fields
fields: FormField[];

// Styling
styles: {
backgroundColor: string;
textColor: string;
buttonColor: string;
buttonTextColor: string;
borderRadius: number;
};

// Content
headline?: string;
description?: string;
submitButtonText: string;
successMessage: string;

// Behavior
settings: {
doubleOptIn: boolean;
redirectUrl?: string;
webhookUrl?: string;
notifyEmails: string[];
};

// Attribution tracking
trackingParams: string[]; // UTM params to capture

// Stats
submissions: number;
conversionRate: number;

createdAt: Timestamp;
updatedAt: Timestamp;
}

interface FormField {
id: string;
type: 'text' | 'email' | 'phone' | 'select' | 'checkbox' | 'textarea' | 'date' | 'hidden';
label: string;
placeholder?: string;
required: boolean;
options?: string[]; // for select
defaultValue?: string;
validation?: {
pattern?: string;
minLength?: number;
maxLength?: number;
};
mapToLeadField?: string; // auto-map to Lead model field
}

Leads & CRM

// Collection: clinicsv2/{clinicId}/leads/{leadId}
interface Lead {
id: string;
clinicId: string;

// Contact info
email: string;
phone?: string;
firstName?: string;
lastName?: string;

// Status & Pipeline
status: 'new' | 'contacted' | 'qualified' | 'nurturing' | 'ready' | 'converted' | 'lost';
temperature: 'cold' | 'warm' | 'hot';
score: number; // 0-100 lead score

// Attribution
source: {
channel: 'meta' | 'google' | 'organic' | 'referral' | 'direct' | 'email' | 'other';
campaignId?: string;
creativeId?: string;
landingPageId?: string;
formId?: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
utmContent?: string;
utmTerm?: string;
referrer?: string;
};

// Custom fields (from form)
customFields: Record<string, any>;

// Engagement tracking
engagement: {
emailsOpened: number;
emailsClicked: number;
smsReplied: number;
websiteVisits: number;
lastEngagedAt?: Timestamp;
};

// AI context
aiContext: {
interests: string[];
painPoints: string[];
objections: string[];
notes: string[]; // AI-generated summaries
};

// Conversion
convertedAt?: Timestamp;
memberId?: string; // links to member after conversion

// Assignment
assignedTo?: string; // staff user ID

// Segments
segmentIds: string[];

// Lifecycle
createdAt: Timestamp;
updatedAt: Timestamp;
lastContactedAt?: Timestamp;
nextFollowUpAt?: Timestamp;
}

// Collection: clinicsv2/{clinicId}/leadActivities/{activityId}
interface LeadActivity {
id: string;
leadId: string;
clinicId: string;

type: 'form_submit' | 'email_sent' | 'email_opened' | 'email_clicked' |
'sms_sent' | 'sms_received' | 'call' | 'appointment_booked' |
'page_view' | 'status_change' | 'note' | 'ai_response';

// Activity details
data: Record<string, any>; // type-specific data

// Who/what triggered
triggeredBy: 'system' | 'ai' | 'staff';
userId?: string;
automationId?: string;

createdAt: Timestamp;
}

Messaging & Automations

// Collection: clinicsv2/{clinicId}/messageTemplates/{templateId}
interface MessageTemplate {
id: string;
clinicId: string;

name: string;
channel: 'email' | 'sms';

// Content
subject?: string; // email only
body: string; // supports {{variables}}

// Email-specific
emailSettings?: {
fromName: string;
replyTo?: string;
preheaderText?: string;
htmlTemplate?: string; // full HTML override
};

// Variables available
variables: string[]; // ['firstName', 'clinicName', etc.]

// Stats
stats: {
sent: number;
opened: number; // email only
clicked: number;
replied: number; // SMS only
};

createdAt: Timestamp;
updatedAt: Timestamp;
}

// Collection: clinicsv2/{clinicId}/automations/{automationId}
interface Automation {
id: string;
clinicId: string;

name: string;
description?: string;
status: 'draft' | 'active' | 'paused';

// Trigger
trigger: {
type: 'lead_created' | 'form_submitted' | 'status_changed' |
'tag_added' | 'time_based' | 'engagement' | 'manual';
conditions: TriggerCondition[];
};

// Steps
steps: AutomationStep[];

// Stats
stats: {
enrolled: number;
completed: number;
active: number;
converted: number;
};

createdAt: Timestamp;
updatedAt: Timestamp;
}

interface AutomationStep {
id: string;
type: 'send_email' | 'send_sms' | 'wait' | 'condition' |
'update_lead' | 'notify_staff' | 'ai_response' | 'book_appointment';

// Step config (varies by type)
config: Record<string, any>;

// For branching
nextStepId?: string;
branches?: {
condition: string;
nextStepId: string;
}[];

// Position in visual builder
position: { x: number; y: number };
}

interface TriggerCondition {
field: string;
operator: 'equals' | 'not_equals' | 'contains' | 'greater_than' | 'less_than';
value: any;
}

Analytics & Attribution

// Collection: clinicsv2/{clinicId}/growthAnalytics/{periodId}
// periodId format: "2024-01" (monthly), "2024-W01" (weekly), "2024-01-15" (daily)
interface GrowthAnalytics {
id: string;
clinicId: string;
period: 'daily' | 'weekly' | 'monthly';
startDate: Timestamp;
endDate: Timestamp;

// Funnel metrics
funnel: {
impressions: number;
clicks: number;
pageViews: number;
formViews: number;
leads: number;
qualifiedLeads: number;
appointments: number;
conversions: number; // → members
};

// Financial
financial: {
adSpend: number;
costPerLead: number;
costPerConversion: number;
revenueFromConverted: number;
roas: number;
};

// By source breakdown
bySource: Record<string, {
impressions: number;
clicks: number;
leads: number;
conversions: number;
spend: number;
revenue: number;
}>;

// By campaign breakdown
byCampaign: Record<string, CampaignMetrics>;

// Website metrics
website: {
visitors: number;
uniqueVisitors: number;
pageViews: number;
avgSessionDuration: number;
bounceRate: number;
topPages: { path: string; views: number }[];
};

createdAt: Timestamp;
}

// For LTV tracking (linked to members after conversion)
// Stored on member document or separate collection
interface MemberLTV {
memberId: string;
leadId: string; // original lead
clinicId: string;

// Attribution
acquisitionSource: Lead['source'];
acquisitionCost: number; // ad spend attributed

// Value
totalSpend: number;
totalVisits: number;
membershipValue: number; // recurring
servicesValue: number; // one-time purchases

// Retention
firstPurchaseAt: Timestamp;
lastPurchaseAt: Timestamp;
churnedAt?: Timestamp;

// Calculated
ltv: number; // lifetime value
predictedLtv: number; // AI predicted
paybackPeriodDays: number; // days to recoup acquisition cost

updatedAt: Timestamp;
}

Platform Integrations

Meta Marketing API

interface MetaIntegration {
clinicId: string;

// OAuth
accessToken: string; // encrypted, stored in Secret Manager
tokenExpiresAt: Timestamp;
refreshToken?: string;

// Business assets
businessId: string;
adAccountId: string;
pageId: string;
pixelId: string;

// Permissions
scopes: string[];

// Webhooks
webhookVerifyToken: string;

status: 'connected' | 'expired' | 'error';
lastSyncAt: Timestamp;
}

// Required scopes:
// - ads_management
// - ads_read
// - business_management
// - pages_read_engagement
// - leads_retrieval (for lead forms)
interface GoogleAdsIntegration {
clinicId: string;

// OAuth
accessToken: string;
refreshToken: string;
tokenExpiresAt: Timestamp;

// Account
customerId: string; // Google Ads customer ID
managerId?: string; // MCC ID if applicable

// Tracking
conversionTrackingId?: string;

status: 'connected' | 'expired' | 'error';
lastSyncAt: Timestamp;
}

Twilio (SMS)

interface TwilioIntegration {
clinicId: string;

// Account
accountSid: string;
authToken: string; // encrypted

// Phone numbers
phoneNumbers: {
number: string; // E.164 format
capabilities: ('sms' | 'mms' | 'voice')[];
isPrimary: boolean;
}[];

// Messaging service (for high volume)
messagingServiceSid?: string;

// Webhook
webhookUrl: string; // for incoming messages

status: 'active' | 'suspended' | 'error';
}

Email (SendGrid or existing SMTP)

interface EmailIntegration {
clinicId: string;

provider: 'sendgrid' | 'smtp' | 'mailgun';

// SendGrid
sendgridApiKey?: string;

// SMTP
smtp?: {
host: string;
port: number;
username: string;
password: string; // encrypted
secure: boolean;
};

// Sending identity
fromEmail: string;
fromName: string;
replyTo?: string;

// Domain verification
domain?: string;
domainVerified: boolean;

status: 'active' | 'unverified' | 'error';
}

Firebase Functions (Python)

# functions/src/functions_growth.py

# Website
async def publish_website(clinic_id: str, website_id: str) -> dict
async def generate_website_content(clinic_id: str, prompt: str, context: dict) -> dict
async def verify_custom_domain(clinic_id: str, domain: str) -> dict

# Campaigns
async def create_meta_campaign(clinic_id: str, campaign: dict) -> dict
async def create_google_campaign(clinic_id: str, campaign: dict) -> dict
async def sync_campaign_metrics(clinic_id: str, campaign_id: str) -> dict
async def pause_campaign(clinic_id: str, campaign_id: str, platform: str) -> dict

# Leads
async def process_lead_webhook(platform: str, payload: dict) -> dict # Meta/Google lead forms
async def score_lead(clinic_id: str, lead_id: str) -> dict
async def update_lead_status(clinic_id: str, lead_id: str, status: str) -> dict

# Messaging
async def send_message(clinic_id: str, lead_id: str, template_id: str, channel: str) -> dict
async def process_incoming_sms(clinic_id: str, from_number: str, body: str) -> dict
async def ai_generate_response(clinic_id: str, lead_id: str, context: dict) -> dict

# Automations
async def trigger_automation(clinic_id: str, automation_id: str, lead_id: str) -> dict
async def process_automation_step(enrollment_id: str, step_id: str) -> dict

# Analytics
async def aggregate_growth_analytics(clinic_id: str, period: str) -> dict
async def calculate_member_ltv(clinic_id: str, member_id: str) -> dict
async def sync_all_campaign_metrics() -> dict # scheduled function

AI Components

Website Generation

// Prompt structure for website generation
interface WebsiteGenerationContext {
clinic: {
name: string;
tagline?: string;
description: string;
services: string[];
membershipTypes: MembershipType[];
locations: Location[];
team?: TeamMember[];
};

icp: {
targetAudience: string;
painPoints: string[];
desiredOutcomes: string[];
objections: string[];
};

brand: {
primaryColor: string;
secondaryColor: string;
tone: 'professional' | 'friendly' | 'luxury' | 'wellness';
logoUrl: string;
};

template: string; // template ID to start from

userPrompt: string; // what the user wants to change/create
}

AI Lead Response

interface AILeadContext {
lead: Lead;
conversationHistory: Message[];

clinic: {
name: string;
services: string[];
faqAnswers: Record<string, string>;
bookingAvailability: TimeSlot[];
policies: string[];
};

// Guardrails
constraints: {
canBookAppointments: boolean;
canQuotePrices: boolean;
escalationTriggers: string[]; // when to hand off to human
maxResponseLength: number;
};
}

Hosting Architecture

Website Hosting (Firebase)

Subdomain: {clinicSlug}.basis.health
Custom Domain: www.{customdomain}.com

Firebase Hosting multisite:
├── basis-growth-sites (hosting target)
│ ├── Dynamic SSR via Cloud Functions
│ ├── Static assets from Storage
│ └── Edge caching via CDN

Site Generation:
1. Website config stored in Firestore
2. On publish: generate static HTML + dynamic routes
3. Deploy to Firebase Hosting programmatically
4. Custom domain: DNS verification + SSL provisioning

Custom Domain Flow

1. Clinic adds custom domain in Basis Flow
2. We generate DNS records to add:
- CNAME: www → {clinicSlug}.basis.health
- TXT: _acme-challenge → verification token
3. Background job checks DNS propagation
4. Once verified: provision SSL certificate
5. Update Firebase Hosting config

Webhook Endpoints

# Incoming webhooks (Cloud Functions HTTP)

# Meta Lead Forms
POST /webhooks/meta/leads
# Receives real-time lead submissions from Meta Lead Ads

# Meta Ad Events
POST /webhooks/meta/events
# Receives campaign status changes, spend updates

# Google Lead Forms
POST /webhooks/google/leads
# Receives leads from Google Ads lead form extensions

# Twilio SMS
POST /webhooks/twilio/sms
# Incoming SMS messages

# Twilio Status
POST /webhooks/twilio/status
# Message delivery status updates

# Stripe (for tracking member LTV)
POST /webhooks/stripe/events
# Payment events for converted leads

Security & Compliance

Data Privacy

  • All lead data encrypted at rest
  • PII access logged
  • GDPR/CCPA compliant data deletion
  • Consent tracking for SMS/email

API Keys & Secrets

  • All API keys in Firebase Secret Manager
  • OAuth tokens encrypted, auto-refresh
  • Clinic-isolated credentials

Rate Limiting

  • Per-clinic API quotas
  • SMS/email daily limits
  • AI generation throttling

Implementation Phases

Phase 1: Foundation (4-6 weeks)

  • Firestore data models
  • Growth module shell in Basis Flow
  • Basic CRM/lead management UI
  • Manual lead entry & status tracking

Phase 2: Website Builder (6-8 weeks)

  • Template system
  • Visual block editor
  • AI prompt-based editing
  • Firebase Hosting multi-tenant setup
  • Subdomain provisioning
  • Custom domain support

Phase 3: Lead Capture (4-6 weeks)

  • Form builder UI
  • Embeddable forms
  • Website integration
  • Lead scoring
  • Pipeline visualization

Phase 4: Messaging (4-6 weeks)

  • Twilio integration
  • Email template builder
  • Manual send capability
  • Basic automation builder
  • AI response generation

Phase 5: Ad Platform Integration (6-8 weeks)

  • Meta API integration
  • Google Ads API integration
  • Campaign creation wizard
  • Creative management
  • Performance sync
  • Lead form sync

Phase 6: Analytics & LTV (4 weeks)

  • Full funnel dashboard
  • Attribution modeling
  • LTV calculation
  • Campaign optimization recommendations

Cost Considerations

Per-Clinic Costs

  • Twilio SMS: ~$0.0079/message
  • Email (SendGrid): ~$0.001/email
  • OpenAI API: ~$0.01-0.03/AI generation
  • Firebase Hosting: minimal (usage-based)
  • Meta/Google API: free (ad spend separate)

Pricing Model Options

  1. Flat monthly fee + usage overage
  2. Tiered plans (leads/mo, messages/mo)
  3. Revenue share on ad spend managed

Open Questions

  1. Multi-location support: Should each location have its own website/campaigns?
  2. White-labeling: Will clinics want to remove Basis branding?
  3. Agency mode: Should clinic marketing agencies be able to manage multiple clinics?
  4. Template marketplace: Will we create templates or allow third-party templates?
  5. Ad creative library: Pre-built ad templates for common clinic types?
  6. Compliance review: Manual review of ads before submission to Meta/Google?