Basis Growth Module Architecture
Premium add-on for clinics: AI Website Builder + Lead Generation + Nurturing
Overview
A unified growth platform that enables clinics to:
- Build AI-generated websites with prompt-based editing
- Run Meta & Google ad campaigns with 1-click setup
- Capture and nurture leads through automated messaging
- 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)
Google Ads API
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
- Flat monthly fee + usage overage
- Tiered plans (leads/mo, messages/mo)
- Revenue share on ad spend managed
Open Questions
- Multi-location support: Should each location have its own website/campaigns?
- White-labeling: Will clinics want to remove Basis branding?
- Agency mode: Should clinic marketing agencies be able to manage multiple clinics?
- Template marketplace: Will we create templates or allow third-party templates?
- Ad creative library: Pre-built ad templates for common clinic types?
- Compliance review: Manual review of ads before submission to Meta/Google?